Skip to content

Commit

Permalink
wip! GitHub OIDC → Cognito Identity → AssumeRoleWithWebSession
Browse files Browse the repository at this point in the history
Let's us map GitHub token claims to principal tags, which is great.
No need to customize the "sub" claim any more, and granular matching.

But ultimately not workable because GitHub OIDC doesn't include the
repository name _sans_ owner in its token claims (e.g. "zika" for
"nextstrain/zika"), so we don't have any policy variable to interpolate
into policy conditions.

I thought maybe we could (ab)use role session names for this and pass
the unqualified repo name via it ourselves, but that hit a wall because
while sts:RoleSessionName is available in the AssumeRoleWithWebIdentity
request, it's not available on subsequent requests which we need to be
able to use in policy conditions.

Then I thought maybe we could use sts:SourceIdentity instead in the role
assumption request which is available as aws:SourceIdentity in
subsequent requests, except that GitHub's token would have to include an
"https://aws.amazonaws.com/source_identity" claim, and it ofc does not
provide any way to do that.  If it did, it would likely also let us
provide a custom "https://aws.amazonaws.com/tags" claim at which point
all of this nonsense would be moot anyway.

Finally, I thought maybe we could use a custom audience ("aud" claim)
which GitHub _does_ let you specify.  This is available in AWS requests
as the "token.actions.githubusercontent.com:aud" key and we can use it
as a policy variable.  \o/  But to use it we'd need to cut out Cognito
Identity (e.g. go back to where we started without it) and lose the
token claims → principal tags mapping.  So we'd need to customize the
GitHub token's "sub" claim and match against it.  We'd also need to
explicitly list out all possible valid "aud" on the AWS OIDC IdP
configuration (as "client_id_list"), and that would be frustrating.

This all feels more complex than it's worth at this point.

Resolves: <#4>
Related-to: <nextstrain/.github#81>
  • Loading branch information
tsibley committed May 17, 2024
1 parent 4e0b461 commit 890a59b
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 6 deletions.
22 changes: 22 additions & 0 deletions env/production/aws-cognito-identity.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# XXX FIXME: describe what we're doing here
# <https://catnekaise.github.io/github-actions-abac-aws/cognito-identity/>
# <https://catnekaise.github.io/github-actions-abac-aws/detailed-explanation/>
# <https://awsteele.com/blog/2023/10/25/aws-role-session-tags-for-github-actions.html>
resource "aws_cognito_identity_pool" "github-actions" {
identity_pool_name = "github-actions"
allow_unauthenticated_identities = false
allow_classic_flow = true
openid_connect_provider_arns = [aws_iam_openid_connect_provider.github-actions.arn]
}

resource "aws_cognito_identity_pool_provider_principal_tag" "github-actions" {
identity_pool_id = aws_cognito_identity_pool.github-actions.id
identity_provider_name = aws_iam_openid_connect_provider.github-actions.id
use_defaults = false
principal_tags = {
# tag name = OIDC token claim
repository_owner = "repository_owner"
repository = "repository"
job_workflow_ref = "job_workflow_ref"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,36 @@ resource "aws_iam_role" "GitHubActionsRoleNextstrainBatchJobs" {
{
"Effect": "Allow",
"Principal": {
"Federated": aws_iam_openid_connect_provider.github-actions.arn
}
"Action": "sts:AssumeRoleWithWebIdentity",
"Federated": "cognito-identity.amazonaws.com"
},
"Action": [
"sts:AssumeRoleWithWebIdentity",
"sts:TagSession",
],
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": aws_cognito_identity_pool.github-actions.id,
"aws:RequestTag/repository_owner": "nextstrain",
"aws:RequestTag/respository": [
"nextstrain/$${sts:RoleSessionName}",
"nextstrain/$${sts:RoleSessionName}-ingest", # for ncov and ncov-ingest
],
},
"StringLike": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:nextstrain/.github:*"
}
"aws:RequestTag/job_workflow_ref": "nextstrain/.github/.github/workflows/pathogen-repo-build.yaml@*",
},
"ForAllValues:StringLike": {
"cognito-identity.amazonaws.com:amr": [
"authenticated",
"token.actions.githubusercontent.com",
"${aws_iam_openid_connect_provider.github-actions.arn}:*",
],
},
# Ensures that a missing/empty amr claim doesn't pass the
# ForAllValues condition above.
"Null": {
"cognito-identity.amazonaws.com:amr": "false",
},
},
}
]
Expand Down
143 changes: 143 additions & 0 deletions env/production/aws-iam-role-GitHubActionsRoleNextstrainBuilds.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
resource "aws_iam_role" "GitHubActionsRoleNextstrainBuilds" {
name = "GitHubActionsRoleNextstrainBuilds"
description = "Provides permissions for a Nextstrain build (i.e. in a pathogen repo) to upload datasets, workflow files, etc. for select GitHub Actions OIDC workflows."

max_session_duration = 43200 # seconds (12 hours)

assume_role_policy = aws_iam_role.GitHubActionsRoleNextstrainBatchJobs.assume_role_policy

# This role provides a superset of the permissions expected to actually be
# required by any individual Nextstrain pathogen build. In practice, we
# further scope down permissions per-repo using an inline session policy
# declared in our centralized and trusted pathogen-repo-build workflow. The
# inline session policy is obviously less of a hard boundary, but it still
# provides guardrails against accidental operations. See also the discussion
# in <https://github.com/nextstrain/private/issues/96>.
# -trs, 15 May 2024
managed_policy_arns = [
# Builds inside the AWS Batch runtime need access to the jobs bucket.
aws_iam_policy.NextstrainJobsAccessToBucket.arn,
]

# All builds need a subset of this access for downloading starting data and
# publishing results.
inline_policy {
name = "S3Access"
policy = jsonencode({
"Version": "2012-10-17",
"Statement": [
# Technically we don't need to include the public buckets
# nextstrain-data and nextstrain-staging in this statement since they
# already allow a superset of this with their bucket policies, but it's
# good to be explicit about what permissions we require.
# -trs, 16 Feb 2024
{
"Sid": "List",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:ListBucketVersions",
"s3:GetBucketLocation",
"s3:GetBucketVersioning",
],
"Resource": [
"arn:aws:s3:::nextstrain-data",
"arn:aws:s3:::nextstrain-data-private",
"arn:aws:s3:::nextstrain-staging",
],
"Condition": {
"StringLike": {
"s3:prefix": [
"$${sts:RoleSessionName}.json",
"$${sts:RoleSessionName}_*.json",
"files/workflows/$${sts:RoleSessionName}/*",
"files/datasets/$${sts:RoleSessionName}/*",
]
}
}
},
{
"Sid": "ReadWrite",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectTagging",
"s3:GetObjectVersion",
"s3:GetObjectVersionTagging",
"s3:PutObject",
"s3:PutObjectTagging",
"s3:DeleteObject",
# but NOT s3:DeleteObjectVersion so objects can't be completely wiped
],
"Resource": [
# Auspice dataset JSONs
"arn:aws:s3:::nextstrain-data/$${sts:RoleSessionName}.json",
"arn:aws:s3:::nextstrain-data/$${sts:RoleSessionName}_*.json",
"arn:aws:s3:::nextstrain-staging/$${sts:RoleSessionName}.json",
"arn:aws:s3:::nextstrain-staging/$${sts:RoleSessionName}_*.json",
"arn:aws:s3:::nextstrain-staging/trial_*_$${sts:RoleSessionName}.json",
"arn:aws:s3:::nextstrain-staging/trial_*_$${sts:RoleSessionName}_*.json",

# Associated data files
# <https://docs.nextstrain.org/en/latest/reference/data-files.html>
"arn:aws:s3:::nextstrain-data/files/workflows/$${sts:RoleSessionName}/*",
"arn:aws:s3:::nextstrain-data/files/datasets/$${sts:RoleSessionName}/*",
"arn:aws:s3:::nextstrain-data-private/files/workflows/$${sts:RoleSessionName}/*",
"arn:aws:s3:::nextstrain-data-private/files/datasets/$${sts:RoleSessionName}/*",
"arn:aws:s3:::nextstrain-staging/files/workflows/$${sts:RoleSessionName}/*",
"arn:aws:s3:::nextstrain-staging/files/datasets/$${sts:RoleSessionName}/*",
],
},
{
"Sid": "NcovPrivateList",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:ListBucketVersions",
"s3:GetBucketLocation",
"s3:GetBucketVersioning",
],
"Resource": [
"arn:aws:s3:::nextstrain-ncov-private",
],
"Condition": {
"StringEquals": {
"aws:PrincipalTag/repository": [
"nextstrain/ncov",
"nextstrain/ncov-ingest",
# XXX TODO: forecasts-ncov?
],
},
}
},
{
"Sid": "NcovPrivateReadWrite",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectTagging",
"s3:GetObjectVersion",
"s3:GetObjectVersionTagging",
"s3:PutObject",
"s3:PutObjectTagging",
"s3:DeleteObject",
# but NOT s3:DeleteObjectVersion so objects can't be completely wiped
],
"Resource": [
# This bucket is akin to nextstrain-data-private/files/{workflows,datasets}/ncov/.
"arn:aws:s3:::nextstrain-ncov-private/*",
],
"Condition": {
"StringEquals": {
"aws:PrincipalTag/repository": [
"nextstrain/ncov",
"nextstrain/ncov-ingest",
# XXX TODO: forecasts-ncov?
]
},
},
},
]
})
}
}

0 comments on commit 890a59b

Please sign in to comment.