From 7968cbe83505d3e6598fec9dfa093cdb9e735a20 Mon Sep 17 00:00:00 2001 From: Kelvin Chappell Date: Thu, 13 Feb 2025 14:02:59 +0000 Subject: [PATCH 1/4] Upgrade AWS IAM v1 client to v2 --- app/aws/Federation.scala | 31 ++- app/data/Policies.scala | 2 +- build.sbt | 11 +- .../scala/com/gu/janus/model/models.scala | 5 +- .../main/scala/com/gu/janus/policy/Iam.scala | 148 +++++++++++++ .../com/gu/janus/policy/Statements.scala | 3 +- .../scala/com/gu/janus/policy/IamSpec.scala | 209 ++++++++++++++++++ .../com/gu/janus/policy/StatementsTest.scala | 2 +- test/aws/FederationTest.scala | 14 +- test/fixtures/Fixtures.scala | 2 +- test/logic/UserAccessTest.scala | 3 +- 11 files changed, 388 insertions(+), 42 deletions(-) create mode 100644 configTools/src/main/scala/com/gu/janus/policy/Iam.scala create mode 100644 configTools/src/test/scala/com/gu/janus/policy/IamSpec.scala diff --git a/app/aws/Federation.scala b/app/aws/Federation.scala index e0d6cd73..a1e6f8d0 100644 --- a/app/aws/Federation.scala +++ b/app/aws/Federation.scala @@ -1,18 +1,15 @@ package aws -import awscala.iam._ -import com.amazonaws.auth.AWSStaticCredentialsProvider -import com.amazonaws.services.identitymanagement.model.GetRoleRequest import com.gu.janus.model.{AwsAccount, Permission} import data.Policies import logic.Date import org.joda.time.{DateTime, DateTimeZone, Duration, Period} import play.api.libs.json.Json +import software.amazon.awssdk.auth.credentials._ +import software.amazon.awssdk.services.iam.IamClient +import software.amazon.awssdk.services.iam.model.PutRolePolicyRequest import software.amazon.awssdk.services.sts.StsClient -import software.amazon.awssdk.services.sts.model.{ - AssumeRoleRequest, - Credentials -} +import software.amazon.awssdk.services.sts.model._ import java.net.{URI, URLEncoder} import java.nio.charset.StandardCharsets.UTF_8 @@ -141,24 +138,24 @@ object Federation { stsClient, Federation.awsMinimumSessionLength ) - val sessionCredentials = awscala.Credentials( + val sessionCredentials = AwsSessionCredentials.create( creds.accessKeyId, creds.secretAccessKey, creds.sessionToken ) - val provider = new AWSStaticCredentialsProvider(sessionCredentials) - val iamClient = IAM(provider) + val provider = StaticCredentialsProvider.create(sessionCredentials) + val iamClient = IamClient.builder().credentialsProvider(provider).build() // remove access from assumed role val roleName = getRoleName(roleArn) - val getRoleRequest = new GetRoleRequest().withRoleName(roleName) - val role = Role(iamClient.getRole(getRoleRequest).getRole) - val roleRevocationPolicy = - RolePolicy(role, "janus-role-revocation-policy", revocationPolicyDocument) - // ^ - // this name should match policy in cloudformation/federation.template.yaml + val roleRevocationPolicy = PutRolePolicyRequest + .builder() + .roleName(roleName) + .policyName("janus-role-revocation-policy") + .policyDocument(revocationPolicyDocument) + .build() iamClient.putRolePolicy(roleRevocationPolicy) - iamClient.shutdown() + iamClient.close() } private[aws] def getRoleName(roleArn: String): String = { diff --git a/app/data/Policies.scala b/app/data/Policies.scala index 452064de..a8cdd63f 100644 --- a/app/data/Policies.scala +++ b/app/data/Policies.scala @@ -1,7 +1,7 @@ package data -import awscala._ import com.gu.janus.model.{AwsAccount, Permission} +import com.gu.janus.policy.Iam._ object Policies { val revokeAccess = Policy( diff --git a/build.sbt b/build.sbt index 091a7eb5..238e46ad 100644 --- a/build.sbt +++ b/build.sbt @@ -8,15 +8,12 @@ import sbtversionpolicy.withsbtrelease.ReleaseVersion ThisBuild / organization := "com.gu" ThisBuild / licenses := Seq(License.Apache2) -val awsSdkVersion = "1.12.781" -val awsSdkV2Version = "2.30.20" -val awscalaVersion = "0.9.2" +val awsSdkVersion = "2.30.20" val circeVersion = "0.14.10" val commonDependencies = Seq( "org.typelevel" %% "cats-core" % "2.12.0", "joda-time" % "joda-time" % "2.13.0", "org.joda" % "joda-convert" % "3.0.1", - "com.github.seratch" %% "awscala-iam" % awscalaVersion, "org.scalatest" %% "scalatest" % "3.2.19" % Test, "org.scalacheck" %% "scalacheck" % "1.18.1" % Test, "org.scalatestplus" %% "scalacheck-1-16" % "3.2.14.0" % Test, @@ -83,9 +80,9 @@ lazy val root = (project in file(".")) ws, filters, "com.gu.play-googleauth" %% "play-v30" % "19.0.0", - "com.amazonaws" % "aws-java-sdk-iam" % awsSdkVersion, - "software.amazon.awssdk" % "sts" % awsSdkV2Version, - "software.amazon.awssdk" % "dynamodb" % awsSdkV2Version, + "software.amazon.awssdk" % "iam" % awsSdkVersion, + "software.amazon.awssdk" % "sts" % awsSdkVersion, + "software.amazon.awssdk" % "dynamodb" % awsSdkVersion, "net.logstash.logback" % "logstash-logback-encoder" % "7.3" // scala-steward:off ) ++ jacksonDatabindOverrides ++ jacksonOverrides diff --git a/configTools/src/main/scala/com/gu/janus/model/models.scala b/configTools/src/main/scala/com/gu/janus/model/models.scala index 62cb132f..1cdfbef6 100644 --- a/configTools/src/main/scala/com/gu/janus/model/models.scala +++ b/configTools/src/main/scala/com/gu/janus/model/models.scala @@ -1,6 +1,7 @@ package com.gu.janus.model -import awscala.Policy +import com.gu.janus.policy.Iam.Policy +import io.circe.syntax.EncoderOps import org.joda.time._ case class JanusData( @@ -87,7 +88,7 @@ object Permission { policy: Policy, shortTerm: Boolean = false ): Permission = { - Permission(account, label, description, policy.asJson, shortTerm) + Permission(account, label, description, policy.asJson.noSpaces, shortTerm) } } diff --git a/configTools/src/main/scala/com/gu/janus/policy/Iam.scala b/configTools/src/main/scala/com/gu/janus/policy/Iam.scala new file mode 100644 index 00000000..7803ffef --- /dev/null +++ b/configTools/src/main/scala/com/gu/janus/policy/Iam.scala @@ -0,0 +1,148 @@ +package com.gu.janus.policy + +import io.circe.syntax._ +import io.circe.{Encoder, Json, JsonObject} + +import scala.collection.immutable.ListMap +import scala.collection.mutable + +/** This models the AWS IAM policy types and subtypes. + * + * See https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html + * for the logic of the Json encoding. + */ +object Iam { + + sealed trait Effect + object Effect { + case object Allow extends Effect + case object Deny extends Effect + + implicit val encoder: Encoder[Effect] = Encoder.instance { + case Allow => Json.fromString("Allow") + case Deny => Json.fromString("Deny") + } + } + + case class Action(name: String) + object Action { + implicit val encoder: Encoder[Action] = Encoder[String].contramap(_.name) + } + + case class Resource(id: String) + object Resource { + implicit val encoder: Encoder[Resource] = Encoder[String].contramap(_.id) + } + + case class Condition( + key: String, + typeName: String, + conditionValues: Seq[String] + ) + object Condition { + implicit val encoder: Encoder[Condition] = Encoder.instance { condition => + Json.obj( + condition.typeName -> Json.obj( + condition.key -> Json.fromValues( + condition.conditionValues.map(Json.fromString) + ) + ) + ) + } + } + + case class Principal(id: String, provider: String) + object Principal { + implicit val encoder: Encoder[Principal] = Encoder.instance { principal => + Json.obj( + principal.provider -> Json.fromString(principal.id) + ) + } + } + + case class Statement( + effect: Effect, + actions: Seq[Action], + resources: Seq[Resource], + id: Option[String] = None, + conditions: Seq[Condition] = Nil, + principals: Seq[Principal] = Nil + ) + object Statement { + private def conditionsAsJson(conditions: Seq[Condition]): Json = { + val mergedConditions = conditions + .groupBy(_.typeName) + .view + .mapValues(conditions => + Json.obj( + conditions.map(condition => + condition.key -> Json.fromValues( + condition.conditionValues.map(Json.fromString) + ) + ): _* + ) + ) + .toMap + Json.fromJsonObject(JsonObject.fromMap(mergedConditions)) + } + + private def principalsAsJson(principals: Seq[Principal]): Json = { + val principalMap = principals + .groupBy(_.provider) + .view + .mapValues(principals => + Json.fromValues( + principals.map(p => Json.fromString(p.id)) + ) + ) + .toMap + Json.fromJsonObject(JsonObject.fromMap(principalMap)) + } + + implicit val encoder: Encoder[Statement] = Encoder.instance { statement => + // Using ListMap to preserve order of fields, which is easier to debug + val baseFields = ListMap( + "Effect" -> statement.effect.asJson, + "Action" -> statement.actions.asJson, + "Resource" -> statement.resources.asJson + ) + + val withSid = statement.id.fold(baseFields)(id => + baseFields + ("Sid" -> Json.fromString(id)) + ) + + val withConditions = + if (statement.conditions.nonEmpty) + withSid + ("Condition" -> conditionsAsJson(statement.conditions)) + else withSid + + val withPrincipals = + if (statement.principals.nonEmpty) + withConditions + ("Principal" -> principalsAsJson( + statement.principals + )) + else withConditions + + Json.fromJsonObject(JsonObject.fromMap(withPrincipals)) + } + } + + case class Policy( + statements: Seq[Statement], + id: Option[String] = None + ) + + object Policy { + private val PolicyVersion = "2012-10-17" + + implicit val encoder: Encoder[Policy] = Encoder.instance { policy => + val baseObj = JsonObject( + "Version" -> Json.fromString(PolicyVersion), + "Statement" -> policy.statements.asJson + ) + Json.fromJsonObject( + policy.id.fold(baseObj)(id => baseObj.add("Id", Json.fromString(id))) + ) + } + } +} diff --git a/configTools/src/main/scala/com/gu/janus/policy/Statements.scala b/configTools/src/main/scala/com/gu/janus/policy/Statements.scala index 70a212c9..0325eef4 100644 --- a/configTools/src/main/scala/com/gu/janus/policy/Statements.scala +++ b/configTools/src/main/scala/com/gu/janus/policy/Statements.scala @@ -1,7 +1,6 @@ package com.gu.janus.policy -import awscala._ -import com.amazonaws.auth.policy.Statement.Effect +import com.gu.janus.policy.Iam._ object Statements { diff --git a/configTools/src/test/scala/com/gu/janus/policy/IamSpec.scala b/configTools/src/test/scala/com/gu/janus/policy/IamSpec.scala new file mode 100644 index 00000000..d130ef5e --- /dev/null +++ b/configTools/src/test/scala/com/gu/janus/policy/IamSpec.scala @@ -0,0 +1,209 @@ +package com.gu.janus.policy + +import com.gu.janus.policy.Iam._ +import io.circe.JsonObject +import io.circe.parser.decode +import io.circe.syntax._ +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers + +class IamSpec extends AnyFreeSpec with Matchers { + + "Action" - { + "should encode action name correctly" in { + Action("s3:GetObject").asJson.noSpaces shouldBe """"s3:GetObject"""" + } + } + + "Resource" - { + "should encode resource id correctly" in { + Resource( + "arn:aws:s3:::my-bucket/*" + ).asJson.noSpaces shouldBe """"arn:aws:s3:::my-bucket/*"""" + } + } + + "Condition" - { + "should encode single condition correctly" in { + val condition = Condition( + key = "aws:SourceIp", + typeName = "StringEquals", + conditionValues = List("203.0.113.0/24") + ) + + val expected = """{"StringEquals":{"aws:SourceIp":["203.0.113.0/24"]}}""" + condition.asJson.noSpaces shouldBe expected + } + + "should encode multiple condition values correctly" in { + val condition = Condition( + key = "aws:SourceIp", + typeName = "StringEquals", + conditionValues = List("203.0.113.0/24", "203.0.113.1/24") + ) + + val expected = + """{"StringEquals":{"aws:SourceIp":["203.0.113.0/24","203.0.113.1/24"]}}""" + condition.asJson.noSpaces shouldBe expected + } + } + + "Principal" - { + "should encode single principal correctly" in { + val principal = Principal("AROAXXXXXXXXXXXXXXX", "AWS") + val expected = """{"AWS":"AROAXXXXXXXXXXXXXXX"}""" + principal.asJson.noSpaces shouldBe expected + } + } + + "Statement" - { + val basicStatement = Statement( + effect = Effect.Allow, + actions = List(Action("iam:PutRolePolicy"), Action("iam:getRole")), + resources = List(Resource("*")), + id = Some("1") + ) + + "should encode basic statement correctly" in { + val expected = """{ + |"Effect":"Allow", + |"Action":["iam:PutRolePolicy","iam:getRole"], + |"Resource":["*"], + |"Sid":"1" + |}""".stripMargin.replaceAll("\\s", "") + + basicStatement.asJson.noSpaces shouldBe expected + } + + "should encode statement with ID correctly" in { + val statementWithId = basicStatement.copy(id = Some("statement1")) + val expected = """{ + |"Effect":"Allow", + |"Action":["iam:PutRolePolicy","iam:getRole"], + |"Resource":["*"], + |"Sid":"statement1" + |}""".stripMargin.replaceAll("\\s", "") + + statementWithId.asJson.noSpaces shouldBe expected + } + + "should encode statement with conditions correctly" in { + val statementWithCondition = basicStatement.copy( + conditions = List( + Condition("aws:SourceIp", "StringEquals", List("203.0.113.0/24")) + ) + ) + + val expected = """{ + |"Effect":"Allow", + |"Action":["iam:PutRolePolicy","iam:getRole"], + |"Resource":["*"], + |"Sid":"1", + |"Condition":{"StringEquals":{"aws:SourceIp":["203.0.113.0/24"]}} + |}""".stripMargin.replaceAll("\\s", "") + + statementWithCondition.asJson.noSpaces shouldBe expected + } + + "should encode statement with principals correctly" in { + val statementWithPrincipals = basicStatement.copy( + principals = List( + Principal("AROAXXXXXXXXXXXXXXX1", "AWS"), + Principal("AROAXXXXXXXXXXXXXXX2", "AWS"), + Principal("ServiceAccount1", "Kubernetes") + ) + ) + + val expected = """{ + |"Effect":"Allow", + |"Action":["iam:PutRolePolicy","iam:getRole"], + |"Resource":["*"], + |"Sid":"1", + |"Principal":{ + |"AWS":["AROAXXXXXXXXXXXXXXX1","AROAXXXXXXXXXXXXXXX2"], + |"Kubernetes":["ServiceAccount1"] + |} + |}""".stripMargin.replaceAll("\\s", "") + + statementWithPrincipals.asJson.noSpaces shouldBe expected + } + } + + "Policy" - { + val basicStatement = Statement( + effect = Effect.Allow, + actions = List(Action("s3:GetObject")), + resources = List(Resource("arn:aws:s3:::my-bucket/*")) + ) + + "should encode basic policy correctly" in { + val policy = Policy(statements = List(basicStatement)) + val expected = """{ + |"Version":"2012-10-17", + |"Statement":[{ + |"Effect":"Allow", + |"Action":["s3:GetObject"], + |"Resource":["arn:aws:s3:::my-bucket/*"] + |}] + |}""".stripMargin.replaceAll("\\s", "") + + policy.asJson.noSpaces shouldBe expected + } + + "should encode policy with ID correctly" in { + val policy = Policy( + statements = List(basicStatement), + id = Some("MyPolicyId") + ) + + val expected = """{ + |"Version":"2012-10-17", + |"Statement":[{ + |"Effect":"Allow", + |"Action":["s3:GetObject"], + |"Resource":["arn:aws:s3:::my-bucket/*"] + |}], + |"Id":"MyPolicyId" + |}""".stripMargin.replaceAll("\\s", "") + + policy.asJson.noSpaces shouldBe expected + } + + "should encode complex policy correctly" in { + val complexPolicy = Policy( + statements = List( + Statement( + effect = Effect.Allow, + actions = List(Action("s3:GetObject"), Action("s3:PutObject")), + resources = List( + Resource("arn:aws:s3:::my-bucket/*"), + Resource("arn:aws:s3:::my-bucket-2/*") + ), + id = Some("statement1"), + conditions = List( + Condition("aws:SourceIp", "StringEquals", List("203.0.113.0/24")) + ), + principals = List( + Principal("AROAXXXXXXXXXXXXXXX", "AWS"), + Principal("ServiceAccount1", "Kubernetes") + ) + ) + ), + id = Some("ComplexPolicy") + ) + + val json = complexPolicy.asJson.noSpaces + // Verify that the generated JSON is valid + decode[JsonObject](json).isRight shouldBe true + + // Verify specific elements + json should include(""""Version":"2012-10-17"""") + json should include(""""Id":"ComplexPolicy"""") + json should include(""""Effect":"Allow"""") + json should include(""""Sid":"statement1"""") + json should include(""""aws:SourceIp":["203.0.113.0/24"]""") + json should include(""""AWS":["AROAXXXXXXXXXXXXXXX"]""") + json should include(""""Kubernetes":["ServiceAccount1"]""") + } + } +} diff --git a/configTools/src/test/scala/com/gu/janus/policy/StatementsTest.scala b/configTools/src/test/scala/com/gu/janus/policy/StatementsTest.scala index 6fbb7068..695ae964 100644 --- a/configTools/src/test/scala/com/gu/janus/policy/StatementsTest.scala +++ b/configTools/src/test/scala/com/gu/janus/policy/StatementsTest.scala @@ -1,6 +1,6 @@ package com.gu.janus.policy -import Statements._ +import com.gu.janus.policy.Statements._ import org.scalatest.freespec.AnyFreeSpec import org.scalatest.matchers.should.Matchers diff --git a/test/aws/FederationTest.scala b/test/aws/FederationTest.scala index 11436827..f16168b1 100644 --- a/test/aws/FederationTest.scala +++ b/test/aws/FederationTest.scala @@ -1,19 +1,15 @@ package aws -import awscala.Policy import com.gu.janus.model.{AwsAccount, Permission} -import org.joda.time.{DateTime, DateTimeZone} +import com.gu.janus.policy.Iam.Policy +import org.joda.time.DateTimeZone import org.scalactic.source import org.scalatest.freespec.AnyFreeSpec import org.scalatest.matchers.should.Matchers -import play.api.routing.sird.{ - QueryStringParameterExtractor, - RequiredQueryStringParameter -} +import play.api.routing.sird.QueryStringParameterExtractor import testutils.JodaTimeUtils -import java.net.URLDecoder -import java.nio.charset.Charset +import java.net.{URI, URLDecoder} class FederationTest extends AnyFreeSpec with Matchers with JodaTimeUtils { import Federation._ @@ -169,7 +165,7 @@ class FederationTest extends AnyFreeSpec with Matchers with JodaTimeUtils { private def extractRedirectUri( url: String )(implicit pos: source.Position): String = { - new java.net.URL(url) match { + new URI(url) match { case RedirectUri(redirectUri) => URLDecoder.decode(redirectUri, "UTF-8") case result => fail(s"redirect_uri parameter not present on resulting URL $result") diff --git a/test/fixtures/Fixtures.scala b/test/fixtures/Fixtures.scala index f1f2e175..589a848a 100644 --- a/test/fixtures/Fixtures.scala +++ b/test/fixtures/Fixtures.scala @@ -1,7 +1,7 @@ package fixtures -import awscala.Policy import com.gu.janus.model.{AwsAccount, Permission} +import com.gu.janus.policy.Iam.Policy object Fixtures { val fooAct = AwsAccount("Foo", "foo") diff --git a/test/logic/UserAccessTest.scala b/test/logic/UserAccessTest.scala index 7c2bcc8b..a447e66b 100644 --- a/test/logic/UserAccessTest.scala +++ b/test/logic/UserAccessTest.scala @@ -1,10 +1,9 @@ package logic -import awscala.DateTime import com.gu.googleauth.UserIdentity import fixtures.Fixtures._ import com.gu.janus.model.{ACL, SupportACL} -import org.joda.time.{DateTimeZone, Period} +import org.joda.time.{DateTime, DateTimeZone, Period} import org.scalatest.freespec.AnyFreeSpec import org.scalatest.matchers.should.Matchers import org.scalatest.{Inspectors, OptionValues} From 24dfd12643f2c8e877a4d285ef7c0a72234b597b Mon Sep 17 00:00:00 2001 From: Kelvin Chappell Date: Thu, 20 Feb 2025 11:34:56 +0000 Subject: [PATCH 2/4] Remove redundant import --- configTools/src/main/scala/com/gu/janus/policy/Iam.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/configTools/src/main/scala/com/gu/janus/policy/Iam.scala b/configTools/src/main/scala/com/gu/janus/policy/Iam.scala index 7803ffef..6e31e68e 100644 --- a/configTools/src/main/scala/com/gu/janus/policy/Iam.scala +++ b/configTools/src/main/scala/com/gu/janus/policy/Iam.scala @@ -4,7 +4,6 @@ import io.circe.syntax._ import io.circe.{Encoder, Json, JsonObject} import scala.collection.immutable.ListMap -import scala.collection.mutable /** This models the AWS IAM policy types and subtypes. * From abfb417395bfdb40e41913d13b4161d3d90e406e Mon Sep 17 00:00:00 2001 From: Kelvin Chappell Date: Mon, 24 Feb 2025 18:14:50 +0000 Subject: [PATCH 3/4] Separate out tests --- .../scala/com/gu/janus/policy/IamSpec.scala | 83 ++++++++++++++++--- 1 file changed, 73 insertions(+), 10 deletions(-) diff --git a/configTools/src/test/scala/com/gu/janus/policy/IamSpec.scala b/configTools/src/test/scala/com/gu/janus/policy/IamSpec.scala index d130ef5e..38539e97 100644 --- a/configTools/src/test/scala/com/gu/janus/policy/IamSpec.scala +++ b/configTools/src/test/scala/com/gu/janus/policy/IamSpec.scala @@ -169,7 +169,7 @@ class IamSpec extends AnyFreeSpec with Matchers { policy.asJson.noSpaces shouldBe expected } - "should encode complex policy correctly" in { + "should encode complex policy correctly" - { val complexPolicy = Policy( statements = List( Statement( @@ -193,17 +193,80 @@ class IamSpec extends AnyFreeSpec with Matchers { ) val json = complexPolicy.asJson.noSpaces - // Verify that the generated JSON is valid decode[JsonObject](json).isRight shouldBe true - // Verify specific elements - json should include(""""Version":"2012-10-17"""") - json should include(""""Id":"ComplexPolicy"""") - json should include(""""Effect":"Allow"""") - json should include(""""Sid":"statement1"""") - json should include(""""aws:SourceIp":["203.0.113.0/24"]""") - json should include(""""AWS":["AROAXXXXXXXXXXXXXXX"]""") - json should include(""""Kubernetes":["ServiceAccount1"]""") + "with correct version" in { + val json = complexPolicy.asJson.noSpaces + json should include(""""Version":"2012-10-17"""") + } + + "with correct ID" in { + val json = complexPolicy.asJson.noSpaces + json should include(""""Id":"ComplexPolicy"""") + } + + "with correct effect" in { + val json = complexPolicy.asJson.noSpaces + json should include(""""Effect":"Allow"""") + } + + "with explicitly set statement ID" in { + val json = complexPolicy.asJson.noSpaces + json should include(""""Sid":"statement1"""") + } + + "with correct condition" in { + val json = complexPolicy.asJson.noSpaces + json should include(""""aws:SourceIp":["203.0.113.0/24"]""") + } + + "with correct principal" in { + val json = complexPolicy.asJson.noSpaces + json should include(""""AWS":["AROAXXXXXXXXXXXXXXX"]""") + } + + "with correct second principal" in { + val json = complexPolicy.asJson.noSpaces + json should include(""""Kubernetes":["ServiceAccount1"]""") + } + + "with correct complete json string" in { + val json = complexPolicy.asJson.spaces2 + val expected = """{ + | "Version" : "2012-10-17", + | "Statement" : [ + | { + | "Effect" : "Allow", + | "Action" : [ + | "s3:GetObject", + | "s3:PutObject" + | ], + | "Resource" : [ + | "arn:aws:s3:::my-bucket/*", + | "arn:aws:s3:::my-bucket-2/*" + | ], + | "Sid" : "statement1", + | "Condition" : { + | "StringEquals" : { + | "aws:SourceIp" : [ + | "203.0.113.0/24" + | ] + | } + | }, + | "Principal" : { + | "AWS" : [ + | "AROAXXXXXXXXXXXXXXX" + | ], + | "Kubernetes" : [ + | "ServiceAccount1" + | ] + | } + | } + | ], + | "Id" : "ComplexPolicy" + |}""".stripMargin + json shouldBe expected + } } } } From a389ae07210eba592e1c10789adf1bb9f48c9b17 Mon Sep 17 00:00:00 2001 From: Kelvin Chappell Date: Tue, 25 Feb 2025 10:15:26 +0000 Subject: [PATCH 4/4] Amend tests showing no SID unless explicit --- .../src/test/scala/com/gu/janus/policy/IamSpec.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/configTools/src/test/scala/com/gu/janus/policy/IamSpec.scala b/configTools/src/test/scala/com/gu/janus/policy/IamSpec.scala index 38539e97..291d80ba 100644 --- a/configTools/src/test/scala/com/gu/janus/policy/IamSpec.scala +++ b/configTools/src/test/scala/com/gu/janus/policy/IamSpec.scala @@ -61,15 +61,14 @@ class IamSpec extends AnyFreeSpec with Matchers { effect = Effect.Allow, actions = List(Action("iam:PutRolePolicy"), Action("iam:getRole")), resources = List(Resource("*")), - id = Some("1") + id = None ) "should encode basic statement correctly" in { val expected = """{ |"Effect":"Allow", |"Action":["iam:PutRolePolicy","iam:getRole"], - |"Resource":["*"], - |"Sid":"1" + |"Resource":["*"] |}""".stripMargin.replaceAll("\\s", "") basicStatement.asJson.noSpaces shouldBe expected @@ -98,7 +97,6 @@ class IamSpec extends AnyFreeSpec with Matchers { |"Effect":"Allow", |"Action":["iam:PutRolePolicy","iam:getRole"], |"Resource":["*"], - |"Sid":"1", |"Condition":{"StringEquals":{"aws:SourceIp":["203.0.113.0/24"]}} |}""".stripMargin.replaceAll("\\s", "") @@ -118,7 +116,6 @@ class IamSpec extends AnyFreeSpec with Matchers { |"Effect":"Allow", |"Action":["iam:PutRolePolicy","iam:getRole"], |"Resource":["*"], - |"Sid":"1", |"Principal":{ |"AWS":["AROAXXXXXXXXXXXXXXX1","AROAXXXXXXXXXXXXXXX2"], |"Kubernetes":["ServiceAccount1"]