diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 10748ee..36b4063 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -15,16 +15,7 @@ jobs: # path: ~/.cache/pre-commit # key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} - run: pip3 install pre-commit - - name: "install root node dependencies" - run: npm install - - name: "install handlers/keyrotate node dependencies" - working-directory: ./handlers/keyrotate - run: npm install - - name: "install handlers/sign node dependencies" - working-directory: ./handlers/sign - run: npm install - - name: "install handlers/notification node dependencies" - working-directory: ./handlers/notification + - name: "install dependencies" run: npm install - name: "run pre-commit" run: pre-commit run --all-files -c .pre-commit-config-ci.yaml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5a4d205..68ac578 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,5 @@ @@ -11,7 +11,7 @@ just a few small guidelines you need to follow. ## Ways of contributing -Contribution does not necessarily mean committing code to the repository. +Contribution does not necessarily mean committing code to the repository. We recognize different levels of contributions as shown below in increasing order of dedication: 1. Test and use the project. Give feedback on the user experience or suggest new features. diff --git a/README.md b/README.md index f85658a..97d0ac2 100644 --- a/README.md +++ b/README.md @@ -7,21 +7,21 @@ An alternative to authenticating with external workloads is to use short-lived c ## Secure Token Service (STS) Exchanging credentials from on form to the other is done with a Secure Token Service (STS) function. AWS also provides STS functions not the one we need. Only the other way around: to exchange a JWT to IAM Session which is called [AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html). -This repo contains a CDK Construct which will deploy a new function which adds the function to exchange an AWS IAM (Session) credential with a signed JWT. +This repo contains a CDK Construct which will deploy a new function which adds the function to exchange an AWS IAM (Session) credential with a signed JWT. ## Solution overview The solution provides 2 endpoints; an OIDC discovery endpoint and a token endpoint. The OIDC discovery endpoint can be used to establish a trust between te STS function and a resource. The token endpoint can be used by client in AWS to exchange their IAM credentials to a JWT. -When a client wants to retrieve a JWT key, it will invoke the token API via the API GW. The backing lambda creates a JWT based on the invoking IAM identity and invokes KMS to sign the token. +When a client wants to retrieve a JWT key, it will invoke the token API via the API GW. The backing lambda creates a JWT based on the invoking IAM identity and invokes KMS to sign the token. -On time based events, EventBridge will trigger a Step function rotation flow. This flow triggers a lambda which in subsequent steps will create a new KMS signing key. Based on that signing key a JWKS is generated and stored in S3 together with discovery files. +On time based events, EventBridge will trigger a Step function rotation flow. This flow triggers a lambda which in subsequent steps will create a new KMS signing key. Based on that signing key a JWKS is generated and stored in S3 together with discovery files. ![Solution architecture](/doc/sts-jwt-function.drawio.png "Solution architecture") ## Using the construct -1. Init a new typescript CDK project +1. Init a new typescript CDK project `cdk init app --language typescript` 2. Config npm to retrieve packages from github package repository `echo @alliander-opensource:registry=https://npm.pkg.github.com > .npmrc` @@ -38,7 +38,7 @@ import { AwsJwtSts, wafUsage } from '@alliander-opensource/aws-jwt-sts' export class MyStsStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); - + new AwsJwtSts(this, 'sts', { /* * Mandatory: @@ -46,7 +46,7 @@ export class MyStsStack extends cdk.Stack { * Set the default aud claim in the jwt, when not specified in the GET param */ defaultAudience: 'api://resourceAudience', - + /* * Optional: when using a custom domain * @@ -56,13 +56,13 @@ export class MyStsStack extends cdk.Stack { */ hostedZoneName: 'test.example.com', // domain name of the zone hostedZoneId: 'ZXXXXXXXXXXXXXXXXXXXX', // zoneId of the zone - + /* * Optional; when using a custom domain the subdomains of the endpoints can optionally be set */ oidcSubdomain: 'oidc', // default 'oidc' for the discovery endpoint (cloudfront) tokenSubdomain: 'token', // default 'token' for the the token api gw endpoint - + /* * Optional: * @@ -74,14 +74,14 @@ export class MyStsStack extends cdk.Stack { * By not setting apiGwWaf, no WAF will be deployed */ apiGwWaf: wafUsage.ProvideWebAclArn, - + /* * Optional; only applicable if apiGwWaf is set to: wafUsage.ProvideWebAclArn * * Specify the WebAcl to use for the API GW */ apiGwWafWebAclArn: 'arn:aws:wafv2:{REGION}:{ACCOUNTID}:regional/webacl/{WebAclName}/{WebAclId}', - + /* * Optional: * @@ -90,7 +90,7 @@ export class MyStsStack extends cdk.Stack { * When not specified no policy is places on the API GW Stage and it will only allow access from within that account */ orgId: 'o-xxxxxxxxxx' - + }) } } @@ -102,7 +102,7 @@ export class MyStsStack extends cdk.Stack { The stack outputs the urls of the endpoints. So if no custom domain is provided observe the CDK Stack output. ## Using the STS function -A token from the STS function can be obained by invoking the token endpoint. +A token from the STS function can be obained by invoking the token endpoint. `GET https://$host/token` optionally an audience can be provided if this needs to be different than the installed default `GET https://$host/token?aud=api://myAudience` diff --git a/package.json b/package.json index d3f13d5..f843039 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@alliander-opensource/aws-jwt-sts", - "version": "0.2.3", + "version": "0.2.4", "author": { "name": "Alliander NV" }, diff --git a/src/index.ts b/src/index.ts index 3cb382d..429140f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -167,9 +167,14 @@ export class AwsJwtSts extends Construct { const issuer = useCustomDomain ? 'https://' + oidcDomainName : 'https://' + distribution.distributionDomainName + const rotateKeysRole = new iam.Role(this, 'rotateKeysRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')] + }) const rotateKeys = new lambdaNodejs.NodejsFunction(this, 'keyrotate', { timeout: cdk.Duration.seconds(5), runtime: lambda.Runtime.NODEJS_18_X, + role: rotateKeysRole, architecture, environment: { S3_BUCKET: oidcbucket.bucketName, @@ -177,9 +182,14 @@ export class AwsJwtSts extends Construct { } }) + const signRole = new iam.Role(this, 'signRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')] + }) const sign = new lambdaNodejs.NodejsFunction(this, 'sign', { timeout: cdk.Duration.seconds(5), runtime: lambda.Runtime.NODEJS_18_X, + role: signRole, architecture, environment: { ISSUER: issuer, @@ -289,12 +299,18 @@ export class AwsJwtSts extends Construct { const statementSign = new iam.PolicyStatement() statementSign.addActions('kms:*') statementSign.addResources('*') - sign.addToRolePolicy(statementSign) + const signPolicy = new iam.ManagedPolicy(this, 'SignPolicy', { + statements: [statementSign] + }) + signRole.addManagedPolicy(signPolicy) const statementRotateKeys = new iam.PolicyStatement() statementRotateKeys.addActions('kms:*') statementRotateKeys.addResources('*') - rotateKeys.addToRolePolicy(statementRotateKeys) + const rotateKeysPolicy = new iam.ManagedPolicy(this, 'RotateKeysPolicy', { + statements: [statementRotateKeys] + }) + rotateKeysRole.addManagedPolicy(rotateKeysPolicy) /** ------------------ Events Rule Definition ------------------ */