diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9911d8d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,41 +0,0 @@ -sudo: required - -services: -- docker - -language: scala - -scala: -- 2.13.1 - -env: - global: - - DOCKER_REPO: wbaa/rokku-sts - - secure: 4Lp+bz7To+oBWw7T/y9XRb46opqxp9NvtDcT0OYGvnkbuMhLLzL5E9P9VhM0PujjB12dmm2zLP8tB1rHVsn7g1LzSILjiES7q53C5bt9wcwF0e7T885+cas4cwgHz8IoLewgG0TOwnZIsyAAwgSQMWbqU3neF7MApdiSQNQ2BI/NW/UO51MJ9RP3Qld0HzbJEZA6x0r5YGQN4JPLcoz9SI9VWIAmMz2ciy4kRgVpOBnWkWPLyOLXcRdcMyfjHBm4iLi4yazigN/G7erU21NFPbHgxEZwj8c4MuSKWuwbmTFAEaT3LkGnstZ8sYdLgF/d3a0z8U7v7Ul955sVM4iJRciUKUJpFgqrMDET7zC7jGihJc2tL2qFQoHiKN9LFv88wRDyprxDK13A9BptdO9fr401qI/PngXJNNfgeo083yZE+zK91oqtLBK9AHboCiSKnl30qKWUOczlpeoUMl5eZyv4IV8xK7YG9KOGkx4kglh00VZ815p9GpubwZ2PhbUWX6zFs3qMjmUgwQnmtLoZTKqUxoyu+OemTJw24wYp58NU5tHM85kQS0SLwP1zUTa5GLm8rxdd7+iPHF2Jr2TDs2eS1C6JtQoTL/l80rI1Kfu8tlndGEZg0xh8BegaQ6gKIYGXcZb9y32yBDkH1snA+pmh/4QnaZogyyuhykx2adA= - - secure: NfG4oBVLO+f8cUw9fLmIPDpK1Y1i9Y6iGAzE0Ug6YzCKw6WeVqrUrTYfDoXfRDIXoRSJ8tSufoLql1+qBix9LWRb41p+O1rb5v+IdBqZI6D3f0Y2r8uFjs0HvkVwDDTAiUNPueRPcdH8CK0xaTBTu0ITxB9PyaoPTAoL1RjxrN2DGg75jwFJ89ViuE8f5zPXvD7eoa2bmNMqhquDXv/QR56zdK+m3sKEV7SeIbeKIekiwP/bqcTZ/Jon/PxVntfET04nneaXLbMICoyszHRv2zxg2YEpf15OlT8b0JT/9Gs7xLD5gDJ+P2geuN5rJKiRgIl+yzeTL3SN0HT6kAO2JkqSjLjWLFeul33ax39nukv8+Hx+SLOh0qD0UTLWsM9EdyKQqi56MNzJPJt3PUElVeD1Bdl/26IpLO70btleyHBPXXjtxPQArYSmPwNy0uC+E7kqquKrnvVsQ+lAE9VgznEUHqj5mmUIl9XO2eHq2F5wls7ter9nagM0VyrDnQFmZBt6KO3BfnOCAnIm9wMIWy2kak+BIkJ6lJjL+PHraK0hIFGLGY+bx/OyRGQNHeRsAWjEV1QAjJ1lWmHjgumahK7d+D5LLcicUto36BQ6G8BH4ZgMODxhE0T8mB7nEMLDMkNJnsXIsbi1kiOaSXZVrdMn2Gs9iFU9dQFS2mdoyO0= - -before_script: -- echo "Running pipeline for branch ${TRAVIS_BRANCH}" -- echo "Starting dependent containers for testing" -- docker-compose up -d -- echo "Compile the project while containers are starting up" -- sbt ++$TRAVIS_SCALA_VERSION clean compile -- echo "Wait for containers to be up and running" -- bash waitForContainerSetup.sh - -script: -- echo "Running pipeline for branch ${TRAVIS_BRANCH}" -- sbt ++$TRAVIS_SCALA_VERSION clean coverage test it:test coverageReport - -after_success: -- bash <(curl -s https://codecov.io/bash) -- export ROKKU_STS_VERSION="${TRAVIS_BRANCH/\//_}" -- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin -- if [ "${TRAVIS_BRANCH}" != "master" ]; then - echo "Build image for with name $DOCKER_REPO:$ROKKU_STS_VERSION"; - sbt clean docker:publish; - fi -- if [ -n "$TRAVIS_TAG" ]; then - docker tag $DOCKER_REPO:$ROKKU_STS_VERSION $DOCKER_REPO:latest; - docker push $DOCKER_REPO:latest; - fi diff --git a/build.sbt b/build.sbt index 11b95c6..1ce6fbf 100644 --- a/build.sbt +++ b/build.sbt @@ -6,52 +6,53 @@ val rokkuStsVersion = scala.sys.env.getOrElse("ROKKU_STS_VERSION", "SNAPSHOT") name := "rokku-sts" version := rokkuStsVersion -scalaVersion := "2.13.1" +scalaVersion := "2.13.8" scalacOptions := Seq( "-unchecked", "-deprecation", "-encoding", "utf-8", - "-target:jvm-1.8", + "-target:11", "-feature", "-Xlint", - "-Xfatal-warnings" + "-Xfatal-warnings", ) // Experimental: improved update resolution. -updateOptions := updateOptions.value.withCachedResolution(cachedResoluton = true) +updateOptions := updateOptions.value.withCachedResolution(true) assemblyJarName in assembly := "rokku-sts.jar" -val akkaVersion = "2.6.3" -val akkaHttpVersion = "10.1.11" -val keycloakVersion = "8.0.2" +val akkaVersion = "2.6.19" +val akkaHttpVersion = "10.2.9" +val keycloakVersion = "16.1.1" val logbackJson = "0.1.5" libraryDependencies ++= Seq( - "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, - "com.typesafe.akka" %% "akka-stream" % akkaVersion, - "ch.megard" %% "akka-http-cors" % "0.4.2", - "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion, - "com.typesafe.akka" %% "akka-http-xml" % akkaHttpVersion, - "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2", - "ch.qos.logback" % "logback-classic" % "1.2.3", - "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, - "org.keycloak" % "keycloak-core" % keycloakVersion, - "org.keycloak" % "keycloak-adapter-core" % keycloakVersion, - "org.keycloak" % "keycloak-admin-client" % keycloakVersion, - "org.jboss.logging" % "jboss-logging" % "3.3.2.Final", - "org.apache.httpcomponents" % "httpclient" % "4.5.6", - "org.mariadb.jdbc" % "mariadb-java-client" % "2.3.0", - "ch.qos.logback.contrib" % "logback-json-classic" % logbackJson, - "ch.qos.logback.contrib" % "logback-jackson" % logbackJson, - "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.9", - "org.scalatest" %% "scalatest" % "3.1.0" % "test, it", - "com.auth0" % "java-jwt" % "3.8.0", - "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test, - "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion % Test, - "com.amazonaws" % "aws-java-sdk-sts" % "1.11.720" % IntegrationTest, - "com.bettercloud" % "vault-java-driver" % "5.1.0") + "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, + "com.typesafe.akka" %% "akka-stream" % akkaVersion, + "ch.megard" %% "akka-http-cors" % "1.1.3", + "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion, + "com.typesafe.akka" %% "akka-http-xml" % akkaHttpVersion, + "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2", + "ch.qos.logback" % "logback-classic" % "1.2.11", + "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, + "org.keycloak" % "keycloak-core" % keycloakVersion, + "org.keycloak" % "keycloak-adapter-core" % keycloakVersion, + "org.keycloak" % "keycloak-admin-client" % keycloakVersion, + "org.jboss.logging" % "jboss-logging" % "3.5.0.Final", + "org.apache.httpcomponents" % "httpclient" % "4.5.13", + "org.mariadb.jdbc" % "mariadb-java-client" % "2.3.0", + "ch.qos.logback.contrib" % "logback-json-classic" % logbackJson, + "ch.qos.logback.contrib" % "logback-jackson" % logbackJson, + "com.fasterxml.jackson.core" % "jackson-databind" % "2.13.3", + "com.auth0" % "java-jwt" % "4.0.0", + "com.bettercloud" % "vault-java-driver" % "5.1.0", + "org.scalatest" %% "scalatest" % "3.2.13" % "test, it", + "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test, + "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion % Test, + "com.amazonaws" % "aws-java-sdk-sts" % "1.12.278" % IntegrationTest, +) configs(IntegrationTest) @@ -61,7 +62,7 @@ Defaults.itSettings parallelExecution in IntegrationTest := false javaOptions in Universal ++= Seq( - "-Dlogback.configurationFile=/rokku/logback.xml" + "-Dlogback.configurationFile=/rokku/logback.xml", ) enablePlugins(JavaAppPackaging) @@ -70,7 +71,7 @@ fork := true dockerExposedPorts := Seq(12345) dockerCommands += ExecCmd("ENV", "PROXY_HOST", "0.0.0.0") -dockerBaseImage := "openjdk:8u171-jre-slim-stretch" +dockerBaseImage := "openjdk:8u171-jre-slim-buster" dockerAlias := docker.DockerAlias(Some("docker.io"), Some("wbaa"), "rokku-sts", Some(rokkuStsVersion)) scalariformPreferences := scalariformPreferences.value diff --git a/docker-compose.yml b/docker-compose.yml index 3ceb407..536714e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: "2" services: keycloak: - image: wbaa/rokku-dev-keycloak:0.0.9 + build: ./keycloak environment: - KEYCLOAK_USER=admin - KEYCLOAK_PASSWORD=admin diff --git a/keycloak/Dockerfile b/keycloak/Dockerfile new file mode 100644 index 0000000..25866fb --- /dev/null +++ b/keycloak/Dockerfile @@ -0,0 +1,5 @@ +FROM jboss/keycloak:16.1.1 + + +COPY data /opt/jboss/keycloak/standalone/data +USER root \ No newline at end of file diff --git a/keycloak/data/kernel/process-uuid b/keycloak/data/kernel/process-uuid new file mode 100644 index 0000000..6ba51bb --- /dev/null +++ b/keycloak/data/kernel/process-uuid @@ -0,0 +1 @@ +6895c06c-d735-46a1-90d7-c0c2c1ac0f80 diff --git a/keycloak/data/keycloak.lock.db b/keycloak/data/keycloak.lock.db new file mode 100644 index 0000000..afe0090 --- /dev/null +++ b/keycloak/data/keycloak.lock.db @@ -0,0 +1,6 @@ +#FileLock +#Fri Aug 12 09:55:34 GMT 2022 +server=172.26.0.3\:40845 +hostName=d0d26d21a3d6 +method=file +id=182917b0236589229c0cb40987878a043daa48ed417 diff --git a/keycloak/data/keycloak.mv.db b/keycloak/data/keycloak.mv.db new file mode 100644 index 0000000..1957cf2 Binary files /dev/null and b/keycloak/data/keycloak.mv.db differ diff --git a/keycloak/data/keycloak.trace.db b/keycloak/data/keycloak.trace.db new file mode 100644 index 0000000..88d690f --- /dev/null +++ b/keycloak/data/keycloak.trace.db @@ -0,0 +1,9 @@ +2022-08-10 10:08:21 jdbc[3]: exception +org.h2.jdbc.JdbcSQLException: Table "DATABASECHANGELOGLOCK" not found; SQL statement: +select count(*) from PUBLIC.DATABASECHANGELOGLOCK [42102-197] +2022-08-10 10:08:25 jdbc[4]: exception +org.h2.jdbc.JdbcSQLException: Table "DATABASECHANGELOG" not found; SQL statement: +select count(*) from PUBLIC.DATABASECHANGELOG [42102-197] +2022-08-10 10:08:29 jdbc[4]: exception +org.h2.jdbc.JdbcSQLException: Table "DATABASECHANGELOG" not found; SQL statement: +select count(*) from PUBLIC.DATABASECHANGELOG [42102-197] diff --git a/keycloak/data/tx-object-store/ShadowNoFileLockStore/defaultStore/EISNAME/0_ffffc0a87003_7d92f3f6_62f38382_20 b/keycloak/data/tx-object-store/ShadowNoFileLockStore/defaultStore/EISNAME/0_ffffc0a87003_7d92f3f6_62f38382_20 new file mode 100644 index 0000000..9b67a85 Binary files /dev/null and b/keycloak/data/tx-object-store/ShadowNoFileLockStore/defaultStore/EISNAME/0_ffffc0a87003_7d92f3f6_62f38382_20 differ diff --git a/project/build.properties b/project/build.properties index dcfaffb..d738b85 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.3.8 +sbt.version = 1.7.1 diff --git a/scripts/rokku-assume-role.sh b/scripts/rokku-assume-role.sh index c0920ce..8ee79cf 100644 --- a/scripts/rokku-assume-role.sh +++ b/scripts/rokku-assume-role.sh @@ -1,4 +1,4 @@ -token_session=$(curl -s -X POST http://localhost:8080/auth/realms/auth-rokku/protocol/openid-connect/token -H "Content-Type: application/x-www-form-urlencoded" -d "username=userone" -d "password=password" -d 'grant_type=password' -d 'client_id=sts-rokku' | jq -r '.access_token') +token_session=$(curl -s -X POST http://localhost:8080/auth/realms/auth-rokku/protocol/openid-connect/token -H "Content-Type: application/x-www-form-urlencoded" -d "username=userone" -d "password=password" -d 'grant_type=password' -d 'client_id=sts-rokku' -d 'client_secret=q4dHVTDyViys4T0njCSSoS5Xto4GjA12' | jq -r '.access_token') echo "Read keycloak token: $token_session" if [ ${#token_session} -gt 10 ] then diff --git a/scripts/rokku-get-session-token.sh b/scripts/rokku-get-session-token.sh index a155d44..35c8ba2 100644 --- a/scripts/rokku-get-session-token.sh +++ b/scripts/rokku-get-session-token.sh @@ -1,4 +1,4 @@ -token_session=$(curl -s -X POST http://localhost:8080/auth/realms/auth-rokku/protocol/openid-connect/token -H "Content-Type: application/x-www-form-urlencoded" -d "username=testuser" -d "password=password" -d 'grant_type=password' -d 'client_id=sts-rokku' | jq -r '.access_token') +token_session=$(curl -s -X POST http://localhost:8080/auth/realms/auth-rokku/protocol/openid-connect/token -H "Content-Type: application/x-www-form-urlencoded" -d "username=testuser" -d "password=password" -d 'grant_type=password' -d 'client_id=sts-rokku' -d 'client_secret=q4dHVTDyViys4T0njCSSoS5Xto4GjA12' | jq -r '.access_token') echo "Read keycloak token: $token_session" if [ ${#token_session} -gt 10 ] then diff --git a/src/it/scala/com/ing/wbaa/rokku/sts/StsServiceItTest.scala b/src/it/scala/com/ing/wbaa/rokku/sts/StsServiceItTest.scala index 58ef2de..a0e9978 100644 --- a/src/it/scala/com/ing/wbaa/rokku/sts/StsServiceItTest.scala +++ b/src/it/scala/com/ing/wbaa/rokku/sts/StsServiceItTest.scala @@ -28,7 +28,14 @@ class StsServiceItTest extends AsyncWordSpec with Diagrams override implicit val testSystem: ActorSystem = ActorSystem.create("test-system") override implicit val exContext: ExecutionContextExecutor = testSystem.dispatcher - private val validCredentials = Map("grant_type" -> "password", "username" -> "userone", "password" -> "password", "client_id" -> "sts-rokku") + val keycloakSettings: KeycloakSettings = new KeycloakSettings(testSystem.settings.config) + private val validCredentials = Map( + "grant_type" -> "password", + "username" -> "userone", + "password" -> "password", + "client_id" -> keycloakSettings.resource, + "client_secret" -> keycloakSettings.clientSecret, + ) private val invalidCredentials = validCredentials + ("password" -> "xxx") private val validAdminArn = "arn:aws:iam::account-id:role/admin" private val forbiddenSuperUserArn = "arn:aws:iam:account-id:role/superuser" @@ -38,10 +45,6 @@ class StsServiceItTest extends AsyncWordSpec with Diagrams override val httpBind: String = "127.0.0.1" } - override val keycloakSettings: KeycloakSettings = new KeycloakSettings(testSystem.settings.config) { - override val realmPublicKeyId: String = "FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE" - } - def withOAuth2TokenRequest(formData: Map[String, String])(testCode: KeycloackToken => Assertion): Future[Assertion] = { keycloackToken(formData).map(testCode(_)) } @@ -59,10 +62,7 @@ class StsServiceItTest extends AsyncWordSpec with Diagrams override protected[this] def httpSettings: HttpSettings = rokkuHttpSettings - override protected[this] def keycloakSettings: KeycloakSettings = new KeycloakSettings(testSystem.settings.config) { - override val realmPublicKeyId: String = "FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE" - override val issuerForList: Set[String] = Set("sts-rokku") - } + val keycloakSettings: KeycloakSettings = new KeycloakSettings(testSystem.settings.config) override protected[this] def stsSettings: StsSettings = StsSettings(testSystem) @@ -101,8 +101,8 @@ class StsServiceItTest extends AsyncWordSpec with Diagrams "return credentials for valid token" in withAwsClient { stsAwsClient => withOAuth2TokenRequest(validCredentials) { keycloakToken => val credentials = stsAwsClient.getSessionToken(new GetSessionTokenRequest() - .withTokenCode(keycloakToken.access_token)) - .getCredentials + .withTokenCode(keycloakToken.access_token)) + .getCredentials assert(!credentials.getAccessKeyId.isEmpty) assert(!credentials.getSecretAccessKey.isEmpty) diff --git a/src/it/scala/com/ing/wbaa/rokku/sts/keycloak/KeycloakClientItTest.scala b/src/it/scala/com/ing/wbaa/rokku/sts/keycloak/KeycloakClientItTest.scala index ae20683..28f3647 100644 --- a/src/it/scala/com/ing/wbaa/rokku/sts/keycloak/KeycloakClientItTest.scala +++ b/src/it/scala/com/ing/wbaa/rokku/sts/keycloak/KeycloakClientItTest.scala @@ -15,10 +15,7 @@ class KeycloakClientItTest extends AsyncWordSpec with Diagrams with OAuth2TokenR override implicit val testSystem: ActorSystem = ActorSystem.create("test-system") override implicit val exContext: ExecutionContextExecutor = testSystem.dispatcher - override val keycloakSettings: KeycloakSettings = new KeycloakSettings(testSystem.settings.config) { - override val realmPublicKeyId: String = "FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE" - override val issuerForList: Set[String] = Set("sts-rokku") - } + val keycloakSettings: KeycloakSettings = new KeycloakSettings(testSystem.settings.config) "Keycloak client" should { val username = "test" diff --git a/src/it/scala/com/ing/wbaa/rokku/sts/keycloak/KeycloakTokenVerifierTest.scala b/src/it/scala/com/ing/wbaa/rokku/sts/keycloak/KeycloakTokenVerifierTest.scala index 5c6a33e..3607df1 100644 --- a/src/it/scala/com/ing/wbaa/rokku/sts/keycloak/KeycloakTokenVerifierTest.scala +++ b/src/it/scala/com/ing/wbaa/rokku/sts/keycloak/KeycloakTokenVerifierTest.scala @@ -17,17 +17,26 @@ class KeycloakTokenVerifierTest extends AsyncWordSpec with Diagrams with OAuth2T override implicit val testSystem: ActorSystem = ActorSystem.create("test-system") override implicit val exContext: ExecutionContextExecutor = testSystem.dispatcher - override val keycloakSettings: KeycloakSettings = new KeycloakSettings(testSystem.settings.config) { - override val realmPublicKeyId: String = "FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE" - override val issuerForList: Set[String] = Set("sts-rokku") - } + val keycloakSettings: KeycloakSettings = new KeycloakSettings(testSystem.settings.config) private def withOAuth2TokenRequest(formData: Map[String, String])(testCode: KeycloackToken => Assertion): Future[Assertion] = { keycloackToken(formData).map(testCode) } - private val validCredentialsUser1 = Map("grant_type" -> "password", "username" -> "userone", "password" -> "password", "client_id" -> "sts-rokku") - private val validCredentialsUser2 = Map("grant_type" -> "password", "username" -> "testuser", "password" -> "password", "client_id" -> "sts-rokku") + private val validCredentialsUser1 = Map( + "grant_type" -> "password", + "username" -> "userone", + "password" -> "password", + "client_id" -> keycloakSettings.resource, + "client_secret" -> keycloakSettings.clientSecret, + ) + private val validCredentialsUser2 = Map( + "grant_type" -> "password", + "username" -> "testuser", + "password" -> "password", + "client_id" -> keycloakSettings.resource, + "client_secret" -> keycloakSettings.clientSecret, + ) "Keycloak verifier" should { "return verified token for user 1" in withOAuth2TokenRequest(validCredentialsUser1) { keycloakToken => diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index 00f6920..b91914c 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -7,16 +7,16 @@ rokku { } # Default keycloak configuration file and realm public key id keycloak { - realmPublicKeyId = "FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE" + realmPublicKeyId = "71Fo4peRHRc7XjPQMEMWFne3F_Hug3O-NT9bMP9YoQg" realm = "auth-rokku" resource = "sts-rokku" url = "http://127.0.0.1:8080" - clientSecret = "" + clientSecret = "q4dHVTDyViys4T0njCSSoS5Xto4GjA12" adminUsername = "rokkuadmin" adminPassword = "password" verifyToken { checkRealmUrl = true - issuerForList = "" + issuerForList = "sts-rokku" } } diff --git a/src/main/scala/com/ing/wbaa/rokku/sts/RokkuStsService.scala b/src/main/scala/com/ing/wbaa/rokku/sts/RokkuStsService.scala index 6df1b17..60472d6 100644 --- a/src/main/scala/com/ing/wbaa/rokku/sts/RokkuStsService.scala +++ b/src/main/scala/com/ing/wbaa/rokku/sts/RokkuStsService.scala @@ -27,8 +27,6 @@ trait RokkuStsService protected[this] def httpSettings: HttpSettings - import com.ing.wbaa.rokku.sts.handler.StsExceptionHandlers.exceptionHandler - // The routes we serve final val allRoutes: Route = toStrictEntity(3.seconds) { @@ -40,7 +38,7 @@ trait RokkuStsService // Details about the server binding. final val startup: Future[Http.ServerBinding] = { - Http(system).bindAndHandle(allRoutes, httpSettings.httpBind, httpSettings.httpPort) + Http().newServerAt(httpSettings.httpBind, httpSettings.httpPort).bind(allRoutes) .andThen { case Success(binding) => logger.info(s"Sts service started listening: ${binding.localAddress}") case Failure(reason) => logger.error("Sts service failed to start.", reason) @@ -48,7 +46,7 @@ trait RokkuStsService } def shutdown(): Future[Done] = { - startup.flatMap(_.unbind) + startup.flatMap(_.unbind()) .andThen { case Success(_) => logger.info("Sts service stopped.") case Failure(reason) => logger.error("Sts service failed to stop.", reason) diff --git a/src/main/scala/com/ing/wbaa/rokku/sts/api/AdminApi.scala b/src/main/scala/com/ing/wbaa/rokku/sts/api/AdminApi.scala index 39f40ca..366b7c0 100644 --- a/src/main/scala/com/ing/wbaa/rokku/sts/api/AdminApi.scala +++ b/src/main/scala/com/ing/wbaa/rokku/sts/api/AdminApi.scala @@ -53,7 +53,7 @@ trait AdminApi extends LazyLogging with Encryption with JwtToken { def addNPA: Route = logRequestResult("debug") { post { path("npa") { - formFields((Symbol("npaAccount"), Symbol("safeName"), Symbol("awsAccessKey"), Symbol("awsSecretKey"))) { (npaAccount, safeName, awsAccessKey, awsSecretKey) => + formFields("npaAccount", "safeName", "awsAccessKey", "awsSecretKey") { (npaAccount, safeName, awsAccessKey, awsSecretKey) => authorizeToken(verifyAuthenticationToken) { keycloakUserInfo => if (userInAdminGroups(keycloakUserInfo.userGroups)) { val awsCredentials = AwsCredential(AwsAccessKey(awsAccessKey), AwsSecretKey(awsSecretKey)) @@ -81,7 +81,7 @@ trait AdminApi extends LazyLogging with Encryption with JwtToken { def addServiceNPA: Route = logRequestResult("debug") { post { path("service" / "npa") { - formFields((Symbol("npaAccount"), Symbol("safeName"), Symbol("awsAccessKey"), Symbol("awsSecretKey"))) { (npaAccount, safeName, awsAccessKey, awsSecretKey) => + formFields("npaAccount", "safeName", "awsAccessKey", "awsSecretKey") { (npaAccount, safeName, awsAccessKey, awsSecretKey) => headerValueByName("Authorization") { bearerToken => if (verifyInternalToken(bearerToken)) { val awsCredentials = AwsCredential(AwsAccessKey(awsAccessKey), AwsSecretKey(awsSecretKey)) @@ -149,7 +149,7 @@ trait AdminApi extends LazyLogging with Encryption with JwtToken { path("keycloak" / "user") { formFields((Symbol("username"))) { username => authorizeToken(verifyAuthenticationToken) { keycloakUserInfo => - extractUri { uri => + extractUri { _ => if (userInAdminGroups(keycloakUserInfo.userGroups)) { onComplete(insertUserToKeycloak(UserName(username))) { case Success(_) => complete(ResponseMessage(s"Add user ok", s"$username added", "keycloak")) diff --git a/src/main/scala/com/ing/wbaa/rokku/sts/api/STSApi.scala b/src/main/scala/com/ing/wbaa/rokku/sts/api/STSApi.scala index 8d8ef83..49567e2 100644 --- a/src/main/scala/com/ing/wbaa/rokku/sts/api/STSApi.scala +++ b/src/main/scala/com/ing/wbaa/rokku/sts/api/STSApi.scala @@ -33,8 +33,7 @@ trait STSApi extends LazyLogging with TokenXML { } private val assumeRoleInputs = { - val inputList = (Symbol("RoleArn"), Symbol("RoleSessionName"), "DurationSeconds".as[Int].?) - (parameters(inputList) | formFields(inputList)).tmap(t => + (parameters("RoleArn", "RoleSessionName", "DurationSeconds".as[Int].?) | formFields("RoleArn", "RoleSessionName", "DurationSeconds".as[Int].?)).tmap(t => t.copy(_1 = AwsRoleArn(t._1), _3 = parseDurationSeconds(t._3)) ) } diff --git a/src/main/scala/com/ing/wbaa/rokku/sts/api/UserApi.scala b/src/main/scala/com/ing/wbaa/rokku/sts/api/UserApi.scala index 4d1561d..0ad5d7b 100644 --- a/src/main/scala/com/ing/wbaa/rokku/sts/api/UserApi.scala +++ b/src/main/scala/com/ing/wbaa/rokku/sts/api/UserApi.scala @@ -6,7 +6,7 @@ import akka.http.scaladsl.server.Route import com.ing.wbaa.rokku.sts.data.aws.{ AwsAccessKey, AwsSessionToken } import com.ing.wbaa.rokku.sts.data.{ RequestId, STSUserInfo, UserAssumeRole, UserGroup } import com.ing.wbaa.rokku.sts.handler.LoggerHandlerWithId -import com.ing.wbaa.rokku.sts.util.JwtToken +import com.ing.wbaa.rokku.sts.util.{ JwtToken, JwtTokenException } import spray.json.RootJsonFormat import scala.concurrent.Future @@ -36,27 +36,34 @@ trait UserApi extends JwtToken { case None => RequestId("") } - if (verifyInternalToken(bearerToken)) { - parameters((Symbol("accessKey"), Symbol("sessionToken").?)) { (accessKey, sessionToken) => - onSuccess(isCredentialActive(AwsAccessKey(accessKey), sessionToken.map(AwsSessionToken))) { + try { + val isBearerTokenValid = verifyInternalToken(bearerToken) + if (isBearerTokenValid) { + parameters("accessKey", "sessionToken".?) { (accessKey, sessionToken) => + onSuccess(isCredentialActive(AwsAccessKey(accessKey), sessionToken.map(AwsSessionToken))) { - case Some(userInfo) => - logger.info("isCredentialActive ok for accessKey={}, sessionToken={}", accessKey, sessionToken) - complete((StatusCodes.OK, UserInfoToReturn( - userInfo.userName.value, - userInfo.userGroup.map(_.value), - userInfo.awsAccessKey.value, - userInfo.awsSecretKey.value, - userInfo.userRole.getOrElse(UserAssumeRole("")).value))) + case Some(userInfo) => + logger.info("isCredentialActive ok for accessKey={}, sessionToken={}", accessKey, sessionToken) + complete((StatusCodes.OK, UserInfoToReturn( + userInfo.userName.value, + userInfo.userGroup.map(_.value), + userInfo.awsAccessKey.value, + userInfo.awsSecretKey.value, + userInfo.userRole.getOrElse(UserAssumeRole("")).value))) - case None => - logger.warn("isCredentialActive forbidden for accessKey={}, sessionToken={}", accessKey, sessionToken) - complete(StatusCodes.Forbidden) + case None => + logger.warn("isCredentialActive forbidden for accessKey={}, sessionToken={}", accessKey, sessionToken) + complete(StatusCodes.Forbidden) + } } + } else { + logger.warn("isCredentialActive not verified for token={}", bearerToken) + complete(StatusCodes.Forbidden) } - } else { - logger.warn("isCredentialActive not verified for token={}", bearerToken) - complete(StatusCodes.Forbidden) + } catch { + case ex: JwtTokenException => + logger.warn("isCredentialActive malformed token={}, $s", bearerToken, ex.getMessage) + complete(StatusCodes.BadRequest) } } } diff --git a/src/main/scala/com/ing/wbaa/rokku/sts/api/xml/TokenXML.scala b/src/main/scala/com/ing/wbaa/rokku/sts/api/xml/TokenXML.scala index 0ee4565..96474f1 100644 --- a/src/main/scala/com/ing/wbaa/rokku/sts/api/xml/TokenXML.scala +++ b/src/main/scala/com/ing/wbaa/rokku/sts/api/xml/TokenXML.scala @@ -7,6 +7,7 @@ import com.ing.wbaa.rokku.sts.data.AuthenticationTokenId import com.ing.wbaa.rokku.sts.data.aws.{ AwsCredentialWithToken, AwsRoleArn } import scala.xml.NodeSeq +import scala.annotation.unused trait TokenXML extends ScalaXmlSupport { @@ -25,7 +26,7 @@ trait TokenXML extends ScalaXmlSupport { awsCredentialWithToken: AwsCredentialWithToken, roleArn: AwsRoleArn, roleSessionName: String, - keycloakTokenId: AuthenticationTokenId + @unused keycloakTokenId: AuthenticationTokenId ): NodeSeq = { diff --git a/src/main/scala/com/ing/wbaa/rokku/sts/config/HttpSettings.scala b/src/main/scala/com/ing/wbaa/rokku/sts/config/HttpSettings.scala index 7c343e6..6d4a3cb 100644 --- a/src/main/scala/com/ing/wbaa/rokku/sts/config/HttpSettings.scala +++ b/src/main/scala/com/ing/wbaa/rokku/sts/config/HttpSettings.scala @@ -11,6 +11,6 @@ class HttpSettings(config: Config) extends Extension { object HttpSettings extends ExtensionId[HttpSettings] with ExtensionIdProvider { override def createExtension(system: ExtendedActorSystem): HttpSettings = new HttpSettings(system.settings.config) - override def lookup(): ExtensionId[HttpSettings] = HttpSettings + override def lookup: ExtensionId[HttpSettings] = HttpSettings } diff --git a/src/main/scala/com/ing/wbaa/rokku/sts/config/KeycloakSettings.scala b/src/main/scala/com/ing/wbaa/rokku/sts/config/KeycloakSettings.scala index 74bbd94..4a7d37d 100644 --- a/src/main/scala/com/ing/wbaa/rokku/sts/config/KeycloakSettings.scala +++ b/src/main/scala/com/ing/wbaa/rokku/sts/config/KeycloakSettings.scala @@ -21,5 +21,5 @@ class KeycloakSettings(config: Config) extends Extension { object KeycloakSettings extends ExtensionId[KeycloakSettings] with ExtensionIdProvider { override def createExtension(system: ExtendedActorSystem): KeycloakSettings = new KeycloakSettings(system.settings.config) - override def lookup(): ExtensionId[KeycloakSettings] = KeycloakSettings + override def lookup: ExtensionId[KeycloakSettings] = KeycloakSettings } diff --git a/src/main/scala/com/ing/wbaa/rokku/sts/config/MariaDBSettings.scala b/src/main/scala/com/ing/wbaa/rokku/sts/config/MariaDBSettings.scala index 128fe26..4190dd1 100644 --- a/src/main/scala/com/ing/wbaa/rokku/sts/config/MariaDBSettings.scala +++ b/src/main/scala/com/ing/wbaa/rokku/sts/config/MariaDBSettings.scala @@ -12,5 +12,5 @@ class MariaDBSettings(config: Config) extends Extension { object MariaDBSettings extends ExtensionId[MariaDBSettings] with ExtensionIdProvider { override def createExtension(system: ExtendedActorSystem): MariaDBSettings = new MariaDBSettings(system.settings.config) - override def lookup(): ExtensionId[MariaDBSettings] = MariaDBSettings + override def lookup: ExtensionId[MariaDBSettings] = MariaDBSettings } diff --git a/src/main/scala/com/ing/wbaa/rokku/sts/config/StsSettings.scala b/src/main/scala/com/ing/wbaa/rokku/sts/config/StsSettings.scala index 076f5b3..7876707 100644 --- a/src/main/scala/com/ing/wbaa/rokku/sts/config/StsSettings.scala +++ b/src/main/scala/com/ing/wbaa/rokku/sts/config/StsSettings.scala @@ -20,5 +20,5 @@ class StsSettings(config: Config) extends Extension { object StsSettings extends ExtensionId[StsSettings] with ExtensionIdProvider { override def createExtension(system: ExtendedActorSystem): StsSettings = new StsSettings(system.settings.config) - override def lookup(): ExtensionId[StsSettings] = StsSettings + override def lookup: ExtensionId[StsSettings] = StsSettings } diff --git a/src/main/scala/com/ing/wbaa/rokku/sts/config/VaultSettings.scala b/src/main/scala/com/ing/wbaa/rokku/sts/config/VaultSettings.scala index 419ca94..a4a4ef3 100644 --- a/src/main/scala/com/ing/wbaa/rokku/sts/config/VaultSettings.scala +++ b/src/main/scala/com/ing/wbaa/rokku/sts/config/VaultSettings.scala @@ -21,5 +21,5 @@ class VaultSettings(config: Config) extends Extension { object VaultSettings extends ExtensionId[VaultSettings] with ExtensionIdProvider { override def createExtension(system: ExtendedActorSystem): VaultSettings = new VaultSettings(system.settings.config) - override def lookup(): ExtensionId[VaultSettings] = VaultSettings + override def lookup: ExtensionId[VaultSettings] = VaultSettings } diff --git a/src/main/scala/com/ing/wbaa/rokku/sts/service/UserTokenDbService.scala b/src/main/scala/com/ing/wbaa/rokku/sts/service/UserTokenDbService.scala index 6ab8401..db360bb 100644 --- a/src/main/scala/com/ing/wbaa/rokku/sts/service/UserTokenDbService.scala +++ b/src/main/scala/com/ing/wbaa/rokku/sts/service/UserTokenDbService.scala @@ -77,25 +77,28 @@ trait UserTokenDbService extends LazyLogging with TokenGeneration { getUserSecretWithExtInfo(awsAccessKey) flatMap { case Some((userName, awsSecretKey, NPA(isNPA), AccountStatus(isEnabled), groups)) => awsSessionToken match { - case Some(sessionToken) if isEnabled => - isTokenActive(sessionToken, userName).flatMap { - case TokenActive => Future.successful(Some(STSUserInfo(userName, groups, awsAccessKey, awsSecretKey, None))) - case TokenActiveForRole(role) => Future.successful(Some(STSUserInfo(userName, Set.empty, awsAccessKey, awsSecretKey, Some(role)))) - case TokenNotActive => Future.successful(None) + case Some(sessionToken) => + if (isEnabled) { + isTokenActive(sessionToken, userName).flatMap { + case TokenActive => Future.successful(Some(STSUserInfo(userName, groups, awsAccessKey, awsSecretKey, None))) + case TokenActiveForRole(role) => Future.successful(Some(STSUserInfo(userName, Set.empty, awsAccessKey, awsSecretKey, Some(role)))) + case TokenNotActive => Future.successful(None) + } + } else { + logger.warn(s"User validation failed. User account is not enabled in STS " + + s"(username: $userName, accessKey: $awsAccessKey)") + Future.successful(None) } - case Some(_) if !isEnabled => - logger.warn(s"User validation failed. User account is not enabled in STS " + - s"(username: $userName, accessKey: $awsAccessKey)") - Future.successful(None) - - case None if isNPA && isEnabled => - Future.successful(Some(STSUserInfo(userName, Set.empty, awsAccessKey, awsSecretKey, None))) + case None => + if (isNPA && isEnabled) { + Future.successful(Some(STSUserInfo(userName, Set.empty, awsAccessKey, awsSecretKey, None))) + } else { + logger.warn(s"User validation failed. No sessionToken provided while user is not an NPA " + + s"(username: $userName, accessKey: $awsAccessKey) or account is not enabled") + Future.successful(None) + } - case None if !isNPA => - logger.warn(s"User validation failed. No sessionToken provided while user is not an NPA " + - s"(username: $userName, accessKey: $awsAccessKey)") - Future.successful(None) } case None => @@ -155,10 +158,13 @@ trait UserTokenDbService extends LazyLogging with TokenGeneration { private[this] def getOrGenerateAwsCredentialWithStatus(userName: UserName): Future[(AwsCredential, AccountStatus)] = getAwsCredentialAndStatus(userName) .flatMap { - case (Some(awsCredential), AccountStatus(isEnabled)) if isEnabled => Future.successful((awsCredential, AccountStatus(isEnabled))) - case (Some(awsCredential), AccountStatus(isEnabled)) if !isEnabled => - logger.info(s"User account disabled for ${awsCredential.accessKey}") - Future.successful((awsCredential, AccountStatus(isEnabled))) + case (Some(awsCredential), AccountStatus(isEnabled)) => + if (isEnabled) { + Future.successful((awsCredential, AccountStatus(isEnabled))) + } else { + logger.info(s"User account disabled for ${awsCredential.accessKey}") + Future.successful((awsCredential, AccountStatus(isEnabled))) + } case (None, _) => getNewAwsCredential(userName).map(c => (c, AccountStatus(true))) } diff --git a/src/test/scala/com/ing/wbaa/rokku/sts/api/STSApiTest.scala b/src/test/scala/com/ing/wbaa/rokku/sts/api/STSApiTest.scala index aba3be4..fe40a81 100644 --- a/src/test/scala/com/ing/wbaa/rokku/sts/api/STSApiTest.scala +++ b/src/test/scala/com/ing/wbaa/rokku/sts/api/STSApiTest.scala @@ -135,6 +135,7 @@ class STSApiTest extends AnyWordSpec with Diagrams with ScalatestRouteTest { queries.map(_.substring(1).split("=")) .map { case Array(k, v) => (k, v) + case _ => ("", "") }.toMap } diff --git a/src/test/scala/com/ing/wbaa/rokku/sts/api/UserApiTest.scala b/src/test/scala/com/ing/wbaa/rokku/sts/api/UserApiTest.scala index 06deedd..8bccce1 100644 --- a/src/test/scala/com/ing/wbaa/rokku/sts/api/UserApiTest.scala +++ b/src/test/scala/com/ing/wbaa/rokku/sts/api/UserApiTest.scala @@ -32,23 +32,21 @@ class UserApiTest extends AnyWordSpec override protected[this] def stsSettings: StsSettings = new StsSettings(testSystem.settings.config) }.userRoutes - val bearerToken: String = { + def generateBearerToken(serviceClaimValue: String = "rokku") = { val stsSettings: StsSettings = new StsSettings(testSystem.settings.config) val algorithm = Algorithm.HMAC256(stsSettings.decodeSecret) JWT.create() .withIssuer("rokku") - .withClaim("service", "rokku") + .withClaim("service", serviceClaimValue) .sign(algorithm) } - import com.ing.wbaa.rokku.sts.handler.StsExceptionHandlers.exceptionHandler - "User api" should { "check isCredentialActive" that { "returns user info" in { Get(s"/isCredentialActive?accessKey=accesskey&sessionToken=sessionToken") - .addHeader(RawHeader("Authorization", bearerToken)) ~> testRoute ~> check { + .addHeader(RawHeader("Authorization", generateBearerToken())) ~> testRoute ~> check { assert(status == StatusCodes.OK) val response = responseAs[String] assert(response == """{"accessKey":"a","secretKey":"s","userGroups":["group1","group2"],"userName":"username","userRole":""}""") @@ -63,14 +61,14 @@ class UserApiTest extends AnyWordSpec "check credential and return rejection because the accessKey param is missing" in { Get("/isCredentialActive") - .addHeader(RawHeader("Authorization", bearerToken)) ~> testRoute ~> check { + .addHeader(RawHeader("Authorization", generateBearerToken())) ~> testRoute ~> check { assert(rejection == MissingQueryParamRejection("accessKey")) } } "check credential and return status forbidden because the accessKey is wrong" in { Get(s"/isCredentialActive?accessKey=access&sessionToken=session") - .addHeader(RawHeader("Authorization", bearerToken)) ~> new testUserApi { + .addHeader(RawHeader("Authorization", generateBearerToken())) ~> new testUserApi { override protected[this] def stsSettings: StsSettings = new StsSettings(testSystem.settings.config) override def isCredentialActive(awsAccessKey: AwsAccessKey, awsSessionToken: Option[AwsSessionToken]): Future[Option[STSUserInfo]] = Future.successful(None) @@ -79,9 +77,16 @@ class UserApiTest extends AnyWordSpec } } - "check credential and return status forbidden because the bearerToken is wrong" in { + "check credential and return status bad request because the bearerToken is not a valid JWT" in { Get(s"/isCredentialActive?accessKey=access&sessionToken=session") .addHeader(RawHeader("Authorization", "fakeToken")) ~> testRoute ~> check { + assert(status == StatusCodes.BadRequest) + } + } + + "check credential and return status forbidden because the bearerToken is invalid" in { + Get(s"/isCredentialActive?accessKey=access&sessionToken=session") + .addHeader(RawHeader("Authorization", generateBearerToken("invalid"))) ~> testRoute ~> check { assert(status == StatusCodes.Forbidden) } }