-
Notifications
You must be signed in to change notification settings - Fork 17
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
feat: support passing multiple trustedRepositories #41
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,52 @@ export interface GithubConfiguration { | |
*/ | ||
readonly provider: IGithubActionsIdentityProvider; | ||
|
||
/** | ||
* Repository owner (organization or username). | ||
* | ||
* Use `trustedRepositories` if you want to provide multiple repo configurations | ||
* | ||
* @example | ||
* 'octo-org' | ||
*/ | ||
readonly owner?: string; | ||
|
||
/** | ||
* Repository name (slug) without the owner. | ||
* | ||
* Use `trustedRepositories` if you want to provide multiple repo configurations | ||
* | ||
* @example | ||
* 'octo-repo' | ||
*/ | ||
readonly repo?: string; | ||
|
||
/** | ||
* Subject condition filter, appended after `repo:${owner}/${repo}:` string in IAM Role trust relationship. | ||
* | ||
* @default | ||
* '*' | ||
* | ||
* You may use this value to only allow Github to assume the role on specific branches, tags, environments, pull requests etc. | ||
* @example | ||
* 'ref:refs/tags/v*' | ||
* 'ref:refs/heads/demo-branch' | ||
* 'pull_request' | ||
* 'environment:Production' | ||
* | ||
* @see https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#examples | ||
*/ | ||
readonly filter?: string; | ||
|
||
/** | ||
* Provide multiple trusted repositories allowed to assume this role. | ||
* | ||
* @default - required if top-level owner/repo/filter not set | ||
*/ | ||
readonly trustedRepositories?: TrustedRepository[]; | ||
} | ||
|
||
export interface TrustedRepository { | ||
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.
|
||
/** | ||
* Repository owner (organization or username). | ||
* | ||
|
@@ -102,6 +148,7 @@ export class GithubActionsRole extends iam.Role { | |
delete extractProps.owner; | ||
delete extractProps.repo; | ||
delete extractProps.filter; | ||
delete extractProps.trustedRepositories; | ||
return extractProps; | ||
} | ||
|
||
|
@@ -112,6 +159,20 @@ export class GithubActionsRole extends iam.Role { | |
} | ||
} | ||
|
||
/** Validates conflicting props aren't set simultaneously */ | ||
private static validateProps(scope: Construct, props: GithubActionsRoleProps) { | ||
const topLevelPropsSet = props.owner || props.repo || props.filter; | ||
const trustedRepositoriesSet = props.trustedRepositories && props.trustedRepositories.length > 0 || false; | ||
|
||
if (topLevelPropsSet && trustedRepositoriesSet) { | ||
cdk.Annotations.of(scope).addError('Cannot set both top-level owner/repo/filter and trustedRepositories. Use one or the other.'); | ||
} | ||
|
||
if (!trustedRepositoriesSet && (props.owner === undefined || props.repo === undefined)) { | ||
cdk.Annotations.of(scope).addError("If you don't provide `trustedRepositories`, you must provide `owner` and `repo`."); | ||
} | ||
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. If we decided to deprecate the top-level properties, we could annotate the deprecated properties here. |
||
} | ||
|
||
/** Validates the Github repository name (without owner). */ | ||
private static validateRepo(scope: Construct, repo: string): void { | ||
if (repo === '') { | ||
|
@@ -120,8 +181,8 @@ export class GithubActionsRole extends iam.Role { | |
} | ||
|
||
/** Formats the `sub` value used in trust policy. */ | ||
private static formatSubject(props: GithubConfiguration): string { | ||
const { owner, repo, filter = '*' } = props; | ||
private static formatSubject(trustedRepository: TrustedRepository): string { | ||
const { owner, repo, filter = '*' } = trustedRepository; | ||
return `repo:${owner}/${repo}:${filter}`; | ||
} | ||
|
||
|
@@ -146,25 +207,32 @@ export class GithubActionsRole extends iam.Role { | |
*/ | ||
constructor(scope: Construct, id: string, props: GithubActionsRoleProps) { | ||
|
||
const { provider, owner, repo } = props; | ||
const { provider, owner, repo, trustedRepositories } = props; | ||
|
||
// Validate props | ||
GithubActionsRole.validateProps(scope, props); | ||
|
||
// Perform validations | ||
GithubActionsRole.validateOwner(scope, owner); | ||
GithubActionsRole.validateRepo(scope, repo); | ||
// Unify the two ways of defining trusted repositories | ||
const subjects = trustedRepositories || [{ owner: owner!, repo: repo!, filter: props.filter }]; | ||
|
||
// Perform validations on each trusted repository | ||
subjects.forEach((subject) => { | ||
GithubActionsRole.validateOwner(scope, subject.owner); | ||
GithubActionsRole.validateRepo(scope, subject.repo); | ||
}); | ||
|
||
// Prepare values | ||
const subject = GithubActionsRole.formatSubject(props); | ||
// Extract IAM Role props | ||
const roleProps = GithubActionsRole.extractRoleProps(props); | ||
|
||
// The actual IAM Role creation | ||
super(scope, id, { | ||
...roleProps, | ||
assumedBy: new iam.WebIdentityPrincipal(provider.openIdConnectProviderArn, { | ||
StringLike: { | ||
'ForAnyValue:StringLike': { | ||
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. This should be safe even if just one property is passed. From the docs:
If we think people might be worried about seeing a 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. When setting this up manually, StringLike also worked. As there is only one key in this map there should be no difference. The array of values is automatically evaluated as an OR |
||
// Only allow specified subjects to assume this role | ||
[`${GithubActionsIdentityProvider.issuer}:sub`]: subject, | ||
[`${GithubActionsIdentityProvider.issuer}:sub`]: subjects.map((subject) => GithubActionsRole.formatSubject(subject)), | ||
}, | ||
StringEquals: { | ||
'StringEquals': { | ||
// Audience is always sts.amazonaws.com with AWS official Github Action | ||
// https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws | ||
[`${GithubActionsIdentityProvider.issuer}:aud`]: 'sts.amazonaws.com', | ||
|
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.
Up for discussion - would it be clearer to deprecate / remove the top-level properties and force people to use
trustedRepostories
instead?