-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Upgrade AWS IAM v1 client to v2 #509
base: main
Are you sure you want to change the base?
Changes from 3 commits
7968cbe
b0c0fd5
24dfd12
32c68c1
abfb417
68fc71c
a389ae0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package com.gu.janus.policy | ||
|
||
import io.circe.syntax._ | ||
import io.circe.{Encoder, Json, JsonObject} | ||
|
||
import scala.collection.immutable.ListMap | ||
|
||
/** 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this Iam object wrapper? No harm, I can see (minor) arguments both ways 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seemed like a convenient reference to hold everything together. But can remove if you prefer - not strictly necessary. |
||
|
||
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 => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's talk about Statement IDs! The current implementation adds unique SIDs using incrementing integers, so if we're going for compatibility we should keep that behaviour. However, for inline policies (which is what Janus produces) SIDs are not required and I was already considering removing them. Let's talk about the behaviour we want for now in this PR and in the future. However, this does tie into my other point about having a test that ensures we are maintining Janus Data config-file-compatibility during this change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the face of it, removing the statement IDs looks sensible. I'm not sure where or how they're referenced. Let's discuss next week. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now statement IDs will be included in the output json representation if they're explicitly added to a Statement case class, otherwise they'll be omitted. |
||
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))) | ||
) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🥳