Skip to content

Commit

Permalink
Merge pull request #1 from alliander-opensource/switch-to-managed-policy
Browse files Browse the repository at this point in the history
Use managed policies instead of inline policies for KMS actions
  • Loading branch information
koen92 authored Apr 11, 2023
2 parents 413ec0d + 881295f commit 912d164
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 27 deletions.
11 changes: 1 addition & 10 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
SPDX-FileCopyrightText: 'Copyright Alliander NV'
SPDX-FileCopyrightText: 'Copyright Alliander NV'
SPDX-License-Identifier: Apache-2.0
-->
Expand All @@ -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.
Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -38,15 +38,15 @@ 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:
*
* Set the default aud claim in the jwt, when not specified in the GET param
*/
defaultAudience: 'api://resourceAudience',

/*
* Optional: when using a custom domain
*
Expand All @@ -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:
*
Expand All @@ -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:
*
Expand All @@ -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'

})
}
}
Expand All @@ -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`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@alliander-opensource/aws-jwt-sts",
"version": "0.2.3",
"version": "0.2.4",
"author": {
"name": "Alliander NV"
},
Expand Down
20 changes: 18 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,19 +167,29 @@ 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,
ISSUER: issuer
}
})

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,
Expand Down Expand Up @@ -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 ------------------ */

Expand Down

0 comments on commit 912d164

Please sign in to comment.