~jan0sch/smederee
Showing details for patch 85356c9326a1104b5f3a625f38b209f63b0ef87a.
diff -rN -u old-smederee/CHANGELOG.md new-smederee/CHANGELOG.md --- old-smederee/CHANGELOG.md 2025-02-01 13:03:32.204941216 +0000 +++ new-smederee/CHANGELOG.md 2025-02-01 13:03:32.204941216 +0000 @@ -20,6 +20,10 @@ ## Unreleased +### Changed + +- store the CSRF key on disk and load it if present + ## 0.3.0 (2022-11-16) ### Added diff -rN -u old-smederee/modules/hub/src/main/resources/reference.conf new-smederee/modules/hub/src/main/resources/reference.conf --- old-smederee/modules/hub/src/main/resources/reference.conf 2025-02-01 13:03:32.204941216 +0000 +++ new-smederee/modules/hub/src/main/resources/reference.conf 2025-02-01 13:03:32.204941216 +0000 @@ -30,6 +30,10 @@ # files of repositories). download-directory = /var/tmp/smederee/download download-directory = ${?SMEDEREE_DOWNLOAD_DIR} + # A file which contains the key used to build the CSRF protection. + # If it does not exist then it should be created with sensible permissions. + csrf-key-file = /var/tmp/smederee/csrf-key.bin + csrf-key-file = ${?SMEDEREE_CSRF_KEY_FILE} # Settings affecting how the service will communicate several information to # the "outside world" e.g. if it runs behind a reverse proxy. diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/config/SmedereeHubConfig.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/config/SmedereeHubConfig.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/config/SmedereeHubConfig.scala 2025-02-01 13:03:32.204941216 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/config/SmedereeHubConfig.scala 2025-02-01 13:03:32.204941216 +0000 @@ -265,6 +265,9 @@ * The hostname on which the service shall listen for requests. * @param port * The TCP port number on which the service shall listen for requests. + * @param csrfKeyFile + * A file which contains the key used to build the CSRF protection. If it does not exist then it should be created + * with sensible permissions. * @param downloadDirectory * A directory into which files are written that are supposed to be downloaded by users (e.g. distribution files of * repositories). @@ -287,6 +290,7 @@ final case class ServiceConfig( host: Host, port: Port, + csrfKeyFile: Path, downloadDirectory: DirectoryPath, authentication: AuthenticationConfiguration, billing: BillingConfiguration, @@ -318,9 +322,10 @@ ) given ConfigReader[ServiceConfig] = - ConfigReader.forProduct10( + ConfigReader.forProduct11( "host", "port", + "csrf-key-file", "download-directory", AuthenticationConfiguration.parentKey.toString, BillingConfiguration.parentKey.toString, diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala 2025-02-01 13:03:32.204941216 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala 2025-02-01 13:03:32.204941216 +0000 @@ -18,7 +18,7 @@ package de.smederee.hub import java.nio.charset.StandardCharsets -import java.nio.file._ +import java.nio.file.Files import java.nio.file.attribute.PosixFilePermissions import cats.arrow.FunctionK @@ -42,6 +42,7 @@ import org.http4s.server.staticcontent.resourceServiceBuilder import org.slf4j.LoggerFactory import pureconfig._ +import scodec.bits.ByteVector /** This is the main entry point for the hub service. * @@ -63,6 +64,30 @@ CSRF.defaultOriginCheck(request, linkConfig.host.toString, linkConfig.scheme, linkConfig.port.map(_.value)) } + /** Try to load the CSRF key from the given path. If it doesn't exist or fails then a new key is generated and stored + * in the file. + * + * @param csrfKeyFile + * A file which contains the key used to build the CSRF protection. + * @return + * A byte vector containing the key used for CSRF protection. + */ + private def loadOrCreateCsrfKey(csrfKeyFile: java.nio.file.Path): IO[ByteVector] = + for { + _ <- IO( + Files.createDirectories( + csrfKeyFile.getParent, + PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-xr-x")) + ) + ) + key <- Files.exists(csrfKeyFile) match { + case false => CSRF.generateSigningKey[IO]() + case true => IO(ByteVector(Files.readAllBytes(csrfKeyFile))) + } + _ <- IO(Files.write(csrfKeyFile, key.toArray)) + _ <- IO(Files.setPosixFilePermissions(csrfKeyFile, PosixFilePermissions.fromString("rw-------"))) + } yield key + def run(args: List[String]): IO[ExitCode] = { val databaseMigrator = new DatabaseMigrator[IO] for { @@ -107,7 +132,7 @@ configuration.database.pass ) cryptoClock = java.time.Clock.systemUTC - csrfKey <- CSRF.generateSigningKey[IO]() + csrfKey <- loadOrCreateCsrfKey(configuration.service.csrfKeyFile) csrfOriginCheck = createCsrfOriginCheck(configuration.service.external) csrfBuilder = CSRF[IO, IO](csrfKey, csrfOriginCheck) /* The idea behind the `onFailure` part of the CSRF protection middleware is