@alliander-opensource/aws-jwt-sts 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +201 -0
- package/README.md +130 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +451 -0
- package/dist/index.keyrotate.d.ts +1 -0
- package/dist/index.keyrotate.js +193 -0
- package/dist/index.sign.d.ts +2 -0
- package/dist/index.sign.js +120 -0
- package/dist/test/index.keyrotate.test.d.ts +1 -0
- package/dist/test/index.keyrotate.test.js +152 -0
- package/dist/test/index.sign.test.d.ts +1 -0
- package/dist/test/index.sign.test.js +146 -0
- package/dist/test/index.test.d.ts +1 -0
- package/dist/test/index.test.js +62 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +56 -0
- package/src/index.keyrotate.ts +228 -0
- package/src/index.sign.ts +145 -0
- package/src/index.ts +597 -0
- package/src/test/index.keyrotate.test.ts +168 -0
- package/src/test/index.sign.test.ts +187 -0
- package/src/test/index.test.ts +72 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2023 Alliander NV
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import { mockClient } from 'aws-sdk-client-mock'
|
|
6
|
+
import { KMSClient, GetPublicKeyCommand, DescribeKeyCommand } from '@aws-sdk/client-kms'
|
|
7
|
+
import { S3Client } from '@aws-sdk/client-s3'
|
|
8
|
+
|
|
9
|
+
import { handler } from '../index.keyrotate'
|
|
10
|
+
|
|
11
|
+
const kmsMock = mockClient(KMSClient)
|
|
12
|
+
const s3Mock = mockClient(S3Client)
|
|
13
|
+
|
|
14
|
+
const pubKeys = {
|
|
15
|
+
PREVIOUS: {
|
|
16
|
+
pem: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt0O+biOuAYD5FrM2R6dAliN1v9HA5XpsuoAtXTn8OVKsLvvBFEhBFlghvSXPpu71vE/JYpUj0lL7J54o/RmCz9ZRDzojLU7aWEYM2sEC9nO2ITdu8it+rr3faa70+7PGW09o4iFD+mXYUgadYT8VWxrKQ3eV/LQrSM+6/KYl3BhlNZNxwjtbHGWAldOlzvy14I59GU5W/zDPgOIWSQBbpRvoJKT2rzOZYDtn7C62197hJYAU7QIZ4mOz/ia10ayFFI7p2Uogku3tY5cyYEtSWGzlTL3EiEzSvvsfQ0717bA5ybbDqCWtShg8+IoOxmby4K9X7XuGAQZYE/fgNAXg3wIDAQAB',
|
|
17
|
+
jwk_kid: 'reND9IAI5hj2pe8UfKm2X6r-SjW1v7s23oC3_N5WPiQ'
|
|
18
|
+
},
|
|
19
|
+
CURRENT: {
|
|
20
|
+
pem: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/PC3f+8XOs6yway2FhPLdZrWU67RIqFACSPJ0A4q/eJ8GlGXDj8cxHcJBJyvTxEU/rttSe3f44ZfrvwlDUbgAmTi2zYEDrBRHr+LmR6qoyvczLNZkiMmJZygdeOMT87gPx1fb8hhFAXQkOL8dHKiBZ+s4Hls8yu5eMuBhjh+hUYxEQWw0ilDgaXCaGRjooHPSU6+I+Qbm73MuCbBAyzSIAGDKyyD50Kx9Z9Cc0i+6ZfXwWU/2Sda7u4U4R2B/PkhAy0fIjn7kMaw9sgpdQHHxygxQ8y7PduNgDBF/C1zOeKJuRa3QGoMXY9kn/OVBwnZG7bQ9Enz3RnTkM3q0nf9JQIDAQAB',
|
|
21
|
+
jwk_kid: '-NIJE4RQ8NYWrOOh5_JyGKFAobfY5_oCKo1MrNXoQOg'
|
|
22
|
+
},
|
|
23
|
+
PENDING: {
|
|
24
|
+
pem: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzeWAt2aRiX57vDd78OwF+83IdEI0mWh05hXvAzQXMqt+QR49hiIWjJtYh1B3sYvbp9BWC8yo+BlWWtsI5fu5mCXsBBp/Q/sgfArEsji+dWEXc+xGRN3hptb9tT+sabIWmd6Qyw4dYCksrBzJvSLO+Hi10Otd2NtzYbAqjZ6soaaClSnrOiw9+J4/GFHuY5gOw8P0uaMclI5sDLGN+G/ayGpUK7xegfEAd9VB6mhdgWoYEAT6yEDnFt0BwvTOYT6TI/5v6scRE7Bywsq5V2Mz5VZe43POcSt1n7vIZ9cXXHSGW8JPv1KKcniHsxIc3Fc74OjcEbqWKw49kVGCE3ayfQIDAQAB',
|
|
25
|
+
jwk_kid: 'bHyjPYB3AfII8o_X3tGkOCVzThZzQN2UKwKVCO9E9gY'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('handlers/keyrotate/keyrotate.test.ts', () => {
|
|
30
|
+
const OLD_ENV = process.env
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
jest.resetModules()
|
|
34
|
+
kmsMock.reset()
|
|
35
|
+
s3Mock.reset()
|
|
36
|
+
process.env = { ...OLD_ENV }
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
kmsMock.reset()
|
|
41
|
+
s3Mock.reset()
|
|
42
|
+
process.env = OLD_ENV
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('should generate & upload correct JWKS file to S3', async () => {
|
|
46
|
+
kmsMock
|
|
47
|
+
.on(GetPublicKeyCommand, { KeyId: 'alias/sts/PREVIOUS' }).resolves({
|
|
48
|
+
PublicKey: base64ToArrayBuffer(pubKeys.PREVIOUS.pem)
|
|
49
|
+
})
|
|
50
|
+
.on(GetPublicKeyCommand, { KeyId: 'alias/sts/CURRENT' }).resolves({
|
|
51
|
+
PublicKey: base64ToArrayBuffer(pubKeys.CURRENT.pem)
|
|
52
|
+
})
|
|
53
|
+
.on(GetPublicKeyCommand, { KeyId: 'alias/sts/PENDING' }).resolves({
|
|
54
|
+
PublicKey: base64ToArrayBuffer(pubKeys.PENDING.pem)
|
|
55
|
+
})
|
|
56
|
+
.on(DescribeKeyCommand, { KeyId: 'alias/sts/PREVIOUS' }).resolves({
|
|
57
|
+
KeyMetadata: {
|
|
58
|
+
KeyId: 'key-1'
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.on(DescribeKeyCommand, { KeyId: 'alias/sts/CURRENT' }).resolves({
|
|
62
|
+
KeyMetadata: {
|
|
63
|
+
KeyId: 'key-2'
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
.on(DescribeKeyCommand, { KeyId: 'alias/sts/PENDING' }).resolves({
|
|
67
|
+
KeyMetadata: {
|
|
68
|
+
KeyId: 'key-3'
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
process.env.S3_BUCKET = 'test-bucket-name'
|
|
73
|
+
process.env.ISSUER = 'test-issuer.com'
|
|
74
|
+
|
|
75
|
+
await handler({ step: 'generateArtifacts' })
|
|
76
|
+
|
|
77
|
+
// @ts-ignore
|
|
78
|
+
const tagsPrevious = kmsMock.call(2).args[0].input.Tags
|
|
79
|
+
expect(tagsPrevious[0].TagKey).toBe('jwk_kid')
|
|
80
|
+
expect(tagsPrevious[0].TagValue).toBe(pubKeys.PREVIOUS.jwk_kid)
|
|
81
|
+
|
|
82
|
+
// @ts-ignore
|
|
83
|
+
const tagsCurrent = kmsMock.call(5).args[0].input.Tags
|
|
84
|
+
expect(tagsCurrent[0].TagKey).toBe('jwk_kid')
|
|
85
|
+
expect(tagsCurrent[0].TagValue).toBe(pubKeys.CURRENT.jwk_kid)
|
|
86
|
+
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
const tagsPending = kmsMock.call(8).args[0].input.Tags
|
|
89
|
+
expect(tagsPending[0].TagKey).toBe('jwk_kid')
|
|
90
|
+
expect(tagsPending[0].TagValue).toBe(pubKeys.PENDING.jwk_kid)
|
|
91
|
+
|
|
92
|
+
// @ts-ignore
|
|
93
|
+
const s3Key = s3Mock.call(0).args[0].input.Key
|
|
94
|
+
expect(s3Key).toBe('discovery/keys')
|
|
95
|
+
|
|
96
|
+
// @ts-ignore
|
|
97
|
+
const s3Bucket = s3Mock.call(0).args[0].input.Bucket
|
|
98
|
+
expect(s3Bucket).toBe('test-bucket-name')
|
|
99
|
+
|
|
100
|
+
// @ts-ignore
|
|
101
|
+
const s3Body = JSON.parse(s3Mock.call(0).args[0].input.Body.toString())
|
|
102
|
+
expect(s3Body).toEqual({
|
|
103
|
+
keys: [
|
|
104
|
+
{
|
|
105
|
+
e: 'AQAB',
|
|
106
|
+
kid: 'reND9IAI5hj2pe8UfKm2X6r-SjW1v7s23oC3_N5WPiQ',
|
|
107
|
+
kty: 'RSA',
|
|
108
|
+
n: 't0O-biOuAYD5FrM2R6dAliN1v9HA5XpsuoAtXTn8OVKsLvvBFEhBFlghvSXPpu71vE_JYpUj0lL7J54o_RmCz9ZRDzojLU7aWEYM2sEC9nO2ITdu8it-rr3faa70-7PGW09o4iFD-mXYUgadYT8VWxrKQ3eV_LQrSM-6_KYl3BhlNZNxwjtbHGWAldOlzvy14I59GU5W_zDPgOIWSQBbpRvoJKT2rzOZYDtn7C62197hJYAU7QIZ4mOz_ia10ayFFI7p2Uogku3tY5cyYEtSWGzlTL3EiEzSvvsfQ0717bA5ybbDqCWtShg8-IoOxmby4K9X7XuGAQZYE_fgNAXg3w',
|
|
109
|
+
alg: 'RS256',
|
|
110
|
+
use: 'sig'
|
|
111
|
+
}, {
|
|
112
|
+
e: 'AQAB',
|
|
113
|
+
kid: '-NIJE4RQ8NYWrOOh5_JyGKFAobfY5_oCKo1MrNXoQOg',
|
|
114
|
+
kty: 'RSA',
|
|
115
|
+
n: '_PC3f-8XOs6yway2FhPLdZrWU67RIqFACSPJ0A4q_eJ8GlGXDj8cxHcJBJyvTxEU_rttSe3f44ZfrvwlDUbgAmTi2zYEDrBRHr-LmR6qoyvczLNZkiMmJZygdeOMT87gPx1fb8hhFAXQkOL8dHKiBZ-s4Hls8yu5eMuBhjh-hUYxEQWw0ilDgaXCaGRjooHPSU6-I-Qbm73MuCbBAyzSIAGDKyyD50Kx9Z9Cc0i-6ZfXwWU_2Sda7u4U4R2B_PkhAy0fIjn7kMaw9sgpdQHHxygxQ8y7PduNgDBF_C1zOeKJuRa3QGoMXY9kn_OVBwnZG7bQ9Enz3RnTkM3q0nf9JQ',
|
|
116
|
+
alg: 'RS256',
|
|
117
|
+
use: 'sig'
|
|
118
|
+
}, {
|
|
119
|
+
e: 'AQAB',
|
|
120
|
+
kid: 'bHyjPYB3AfII8o_X3tGkOCVzThZzQN2UKwKVCO9E9gY',
|
|
121
|
+
kty: 'RSA',
|
|
122
|
+
n: 'zeWAt2aRiX57vDd78OwF-83IdEI0mWh05hXvAzQXMqt-QR49hiIWjJtYh1B3sYvbp9BWC8yo-BlWWtsI5fu5mCXsBBp_Q_sgfArEsji-dWEXc-xGRN3hptb9tT-sabIWmd6Qyw4dYCksrBzJvSLO-Hi10Otd2NtzYbAqjZ6soaaClSnrOiw9-J4_GFHuY5gOw8P0uaMclI5sDLGN-G_ayGpUK7xegfEAd9VB6mhdgWoYEAT6yEDnFt0BwvTOYT6TI_5v6scRE7Bywsq5V2Mz5VZe43POcSt1n7vIZ9cXXHSGW8JPv1KKcniHsxIc3Fc74OjcEbqWKw49kVGCE3ayfQ',
|
|
123
|
+
alg: 'RS256',
|
|
124
|
+
use: 'sig'
|
|
125
|
+
}]
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// @ts-ignore
|
|
129
|
+
const s3KeyOpenidConfiguration = s3Mock.call(1).args[0].input.Key
|
|
130
|
+
expect(s3KeyOpenidConfiguration).toBe('.well-known/openid-configuration')
|
|
131
|
+
|
|
132
|
+
// @ts-ignore
|
|
133
|
+
const s3BodyOpenidConfiguration = JSON.parse(s3Mock.call(1).args[0].input.Body.toString())
|
|
134
|
+
expect(s3BodyOpenidConfiguration).toEqual({
|
|
135
|
+
issuer: 'test-issuer.com',
|
|
136
|
+
jwks_uri: 'test-issuer.com/discovery/keys',
|
|
137
|
+
response_types_supported: [
|
|
138
|
+
'token'
|
|
139
|
+
],
|
|
140
|
+
id_token_signing_alg_values_supported: [
|
|
141
|
+
'RS256'
|
|
142
|
+
],
|
|
143
|
+
scopes_supported: [
|
|
144
|
+
'openid'
|
|
145
|
+
],
|
|
146
|
+
token_endpoint_auth_methods_supported: [
|
|
147
|
+
'client_secret_basic'
|
|
148
|
+
],
|
|
149
|
+
claims_supported: [
|
|
150
|
+
'aud',
|
|
151
|
+
'exp',
|
|
152
|
+
'iat',
|
|
153
|
+
'iss',
|
|
154
|
+
'sub'
|
|
155
|
+
]
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
function base64ToArrayBuffer (b64: string) {
|
|
161
|
+
const byteString = atob(b64)
|
|
162
|
+
const byteArray = new Uint8Array(byteString.length)
|
|
163
|
+
for (let i = 0; i < byteString.length; i++) {
|
|
164
|
+
byteArray[i] = byteString.charCodeAt(i)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return byteArray
|
|
168
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2023 Alliander NV
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import { APIGatewayProxyEvent, Context } from 'aws-lambda'
|
|
6
|
+
import { mockClient } from 'aws-sdk-client-mock'
|
|
7
|
+
/* eslint-disable camelcase */
|
|
8
|
+
import jwt_decode from 'jwt-decode'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
KMSClient,
|
|
12
|
+
DescribeKeyCommand,
|
|
13
|
+
ListResourceTagsCommand,
|
|
14
|
+
SignCommand
|
|
15
|
+
} from '@aws-sdk/client-kms'
|
|
16
|
+
|
|
17
|
+
import { handler } from '../index.sign'
|
|
18
|
+
|
|
19
|
+
const kmsMock = mockClient(KMSClient)
|
|
20
|
+
|
|
21
|
+
const VALID_IDENTITY_USER_ARN = 'arn:aws:sts:eu-central-1:123456789012:assumed-role/this-is-my-role-name/this-is-my-username'
|
|
22
|
+
|
|
23
|
+
const VALID_EVENT: APIGatewayProxyEvent = {
|
|
24
|
+
requestContext: {
|
|
25
|
+
identity: {
|
|
26
|
+
userArn: VALID_IDENTITY_USER_ARN
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} as any
|
|
30
|
+
|
|
31
|
+
const CONTEXT: Context = {} as any
|
|
32
|
+
|
|
33
|
+
describe('handlers/sign/sign.ts', () => {
|
|
34
|
+
const OLD_ENV = process.env
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
jest.resetModules()
|
|
38
|
+
kmsMock.reset()
|
|
39
|
+
process.env = { ...OLD_ENV }
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
kmsMock.reset()
|
|
44
|
+
process.env = OLD_ENV
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('it should respond bad request if no userIdentity is passed', async () => {
|
|
48
|
+
const event: APIGatewayProxyEvent = {
|
|
49
|
+
requestContext: {
|
|
50
|
+
}
|
|
51
|
+
} as any
|
|
52
|
+
|
|
53
|
+
const response = await handler(event, CONTEXT)
|
|
54
|
+
|
|
55
|
+
expect(response.statusCode).toEqual(400)
|
|
56
|
+
expect(response.body).toEqual('Unable to resolve identity')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('it should respond bad request if an invalid userIdentity is passed', async () => {
|
|
60
|
+
const invalidServiceResponse = await handler({
|
|
61
|
+
requestContext: {
|
|
62
|
+
identity: {
|
|
63
|
+
userArn: 'arn:aws:invalid-service:eu-central-1:123456789012:assumed-role/this-is-my-role-name/this-is-my-username'
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} as any, CONTEXT)
|
|
67
|
+
|
|
68
|
+
expect(invalidServiceResponse.statusCode).toEqual(400)
|
|
69
|
+
expect(invalidServiceResponse.body).toEqual('Unable to resolve identity')
|
|
70
|
+
|
|
71
|
+
const invalidAccountIdResponse = await handler({
|
|
72
|
+
requestContext: {
|
|
73
|
+
identity: {
|
|
74
|
+
userArn: 'arn:aws:sts:eu-central-1:account-id:assumed-role/this-is-my-role-name/this-is-my-username'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} as any, CONTEXT)
|
|
78
|
+
|
|
79
|
+
expect(invalidAccountIdResponse.statusCode).toEqual(400)
|
|
80
|
+
expect(invalidAccountIdResponse.body).toEqual('Unable to resolve identity')
|
|
81
|
+
|
|
82
|
+
const completelyInvalidArn = await handler({
|
|
83
|
+
requestContext: {
|
|
84
|
+
identity: {
|
|
85
|
+
userArn: 'i-am-not-even-trying'
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} as any, CONTEXT)
|
|
89
|
+
|
|
90
|
+
expect(completelyInvalidArn.statusCode).toEqual(400)
|
|
91
|
+
expect(completelyInvalidArn.body).toEqual('Unable to resolve identity')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('it should respond internal server error if no tag is present on the KMS key', async () => {
|
|
95
|
+
kmsMock
|
|
96
|
+
.on(DescribeKeyCommand).resolves({
|
|
97
|
+
KeyMetadata: {
|
|
98
|
+
KeyId: 'key-1'
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
.on(ListResourceTagsCommand).resolves({
|
|
102
|
+
Tags: [
|
|
103
|
+
{
|
|
104
|
+
TagKey: 'NotTheKid',
|
|
105
|
+
TagValue: 'I won\'t be resolved'
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const response = await handler(VALID_EVENT, CONTEXT)
|
|
111
|
+
|
|
112
|
+
expect(response.statusCode).toEqual(500)
|
|
113
|
+
expect(response.body).toEqual('KMS key is not correctly tagged')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('it should respond internal server error if the KeyId is not in the metadata', async () => {
|
|
117
|
+
kmsMock
|
|
118
|
+
.on(DescribeKeyCommand).resolves({})
|
|
119
|
+
|
|
120
|
+
const response = await handler(VALID_EVENT, CONTEXT)
|
|
121
|
+
|
|
122
|
+
expect(response.statusCode).toEqual(500)
|
|
123
|
+
expect(response.body).toEqual('KMS key could not be retrieved')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('should sign correctly', async () => {
|
|
127
|
+
jest
|
|
128
|
+
.useFakeTimers()
|
|
129
|
+
.setSystemTime(new Date('2020-01-01'))
|
|
130
|
+
|
|
131
|
+
const b64Signature = Buffer.from('i-am-a-signature').toString('base64')
|
|
132
|
+
const signature = base64ToArrayBuffer(b64Signature)
|
|
133
|
+
|
|
134
|
+
kmsMock
|
|
135
|
+
.on(DescribeKeyCommand).resolves({
|
|
136
|
+
KeyMetadata: {
|
|
137
|
+
KeyId: 'key-1'
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
.on(ListResourceTagsCommand).resolves({
|
|
141
|
+
Tags: [
|
|
142
|
+
{
|
|
143
|
+
TagKey: 'jwk_kid',
|
|
144
|
+
TagValue: 'I am the KID from the JWK'
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
})
|
|
148
|
+
.on(SignCommand).resolves({
|
|
149
|
+
Signature: signature
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
process.env.ISSUER = 'https://test-issuer.com'
|
|
153
|
+
process.env.DEFAULT_AUDIENCE = 'api://default-aud'
|
|
154
|
+
|
|
155
|
+
const response = await handler(VALID_EVENT, CONTEXT)
|
|
156
|
+
|
|
157
|
+
expect(response.statusCode).toEqual(200)
|
|
158
|
+
const responseBody = JSON.parse(response.body)
|
|
159
|
+
const token = responseBody.token
|
|
160
|
+
|
|
161
|
+
const decodedHeader: any = jwt_decode(token, { header: true })
|
|
162
|
+
|
|
163
|
+
expect(decodedHeader.alg).toEqual('RS256')
|
|
164
|
+
expect(decodedHeader.typ).toEqual('JWT')
|
|
165
|
+
expect(decodedHeader.kid).toEqual('I am the KID from the JWK')
|
|
166
|
+
|
|
167
|
+
const decodedToken: any = jwt_decode(token)
|
|
168
|
+
expect(decodedToken.sub).toEqual('arn:aws:iam:eu-central-1:123456789012:role/this-is-my-role-name')
|
|
169
|
+
expect(decodedToken.aud).toEqual('api://default-aud')
|
|
170
|
+
expect(decodedToken.iss).toEqual('https://test-issuer.com')
|
|
171
|
+
expect(decodedToken.exp - decodedToken.iat).toEqual(3600)
|
|
172
|
+
expect(decodedToken.iat - decodedToken.nbf).toEqual(300)
|
|
173
|
+
|
|
174
|
+
const tokenParts = responseBody.token.split('.')
|
|
175
|
+
expect(tokenParts[2]).toEqual(`${b64Signature.replace('==', '')}`)
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
function base64ToArrayBuffer (b64: string) {
|
|
180
|
+
const byteString = atob(b64)
|
|
181
|
+
const byteArray = new Uint8Array(byteString.length)
|
|
182
|
+
for (let i = 0; i < byteString.length; i++) {
|
|
183
|
+
byteArray[i] = byteString.charCodeAt(i)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return byteArray
|
|
187
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2023 Alliander NV
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
/* eslint-disable no-new */
|
|
6
|
+
import * as cdk from 'aws-cdk-lib'
|
|
7
|
+
import { Match, Template } from 'aws-cdk-lib/assertions'
|
|
8
|
+
import { AwsJwtSts } from '../index'
|
|
9
|
+
|
|
10
|
+
test('creates sts construct correctly', () => {
|
|
11
|
+
const stack = new cdk.Stack()
|
|
12
|
+
new AwsJwtSts(stack, 'AllianderIngress', {
|
|
13
|
+
defaultAudience: 'api://default-aud'
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const template = Template.fromStack(stack)
|
|
17
|
+
template.hasResourceProperties('AWS::Lambda::Function', Match.objectLike({
|
|
18
|
+
Runtime: 'nodejs18.x'
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
template.hasResourceProperties('AWS::Events::Rule', Match.objectLike(
|
|
22
|
+
{
|
|
23
|
+
EventPattern: {
|
|
24
|
+
'detail-type': ['CloudFormation Stack Status Change']
|
|
25
|
+
},
|
|
26
|
+
State: 'ENABLED'
|
|
27
|
+
}
|
|
28
|
+
))
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('creates sts construct with key rotation on create/update disabled', () => {
|
|
32
|
+
const stack = new cdk.Stack()
|
|
33
|
+
new AwsJwtSts(stack, 'AllianderIngress', {
|
|
34
|
+
defaultAudience: 'api://default-aud',
|
|
35
|
+
disableKeyRotateOnCreate: true
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const template = Template.fromStack(stack)
|
|
39
|
+
|
|
40
|
+
template.resourcePropertiesCountIs('AWS::Events::Rule', Match.objectLike(
|
|
41
|
+
{
|
|
42
|
+
EventPattern: {
|
|
43
|
+
'detail-type': ['CloudFormation Stack Status Change']
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
), 0)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('creates sts construct with custom alarm names', () => {
|
|
50
|
+
const stack = new cdk.Stack()
|
|
51
|
+
new AwsJwtSts(stack, 'AllianderIngress', {
|
|
52
|
+
defaultAudience: 'api://default-aud',
|
|
53
|
+
alarmNameApiGateway5xx: 'alarm-api-gw-5xx',
|
|
54
|
+
alarmNameKeyRotationLambdaFailed: 'alarm-key-rotation-lambda-failed',
|
|
55
|
+
alarmNameKeyRotationStepFunctionFailed: 'alarm-step-functions-failed',
|
|
56
|
+
alarmNameSignLambdaFailed: 'alarm-sign-lambda-failed'
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const template = Template.fromStack(stack)
|
|
60
|
+
template.hasResourceProperties('AWS::CloudWatch::Alarm', Match.objectLike({
|
|
61
|
+
AlarmName: 'alarm-api-gw-5xx'
|
|
62
|
+
}))
|
|
63
|
+
template.hasResourceProperties('AWS::CloudWatch::Alarm', Match.objectLike({
|
|
64
|
+
AlarmName: 'alarm-key-rotation-lambda-failed'
|
|
65
|
+
}))
|
|
66
|
+
template.hasResourceProperties('AWS::CloudWatch::Alarm', Match.objectLike({
|
|
67
|
+
AlarmName: 'alarm-step-functions-failed'
|
|
68
|
+
}))
|
|
69
|
+
template.hasResourceProperties('AWS::CloudWatch::Alarm', Match.objectLike({
|
|
70
|
+
AlarmName: 'alarm-sign-lambda-failed'
|
|
71
|
+
}))
|
|
72
|
+
})
|