1
- import { debug , notice , getIDToken } from '@actions/core' ;
1
+ import { debug , notice , getIDToken , exportVariable , info } from '@actions/core' ;
2
2
import { getInput } from '@actions/core' ;
3
+ import { context } from '@actions/github' ;
3
4
import { warn } from 'console' ;
4
5
import fs from 'fs' ;
5
6
import path from 'path' ;
6
- import { CloudFormationClient , DescribeStacksCommand } from '@aws-sdk/client-cloudformation' ;
7
- // import axios from 'axios';
8
- import * as jose from 'jose' ;
7
+ import { CloudFormationClient , DescribeStacksCommand , Stack } from '@aws-sdk/client-cloudformation' ;
8
+ import {
9
+ STSClient ,
10
+ AssumeRoleWithWebIdentityCommand ,
11
+ GetCallerIdentityCommand ,
12
+ } from '@aws-sdk/client-sts' ;
13
+ import packageJson from '../package.json' ;
14
+ import { roleSetupInstructions } from './messages' ;
9
15
10
- const { GITHUB_TOKEN } = process . env ;
16
+ const { GITHUB_TOKEN , GITHUB_REPOSITORY } = process . env ;
11
17
12
18
type ServerlessState = {
13
19
service : {
@@ -21,20 +27,18 @@ type ServerlessState = {
21
27
22
28
export class Action {
23
29
async run ( ) : Promise < void > {
24
- debug ( 'Running!' ) ;
25
-
26
- const token = getInput ( 'token' ) || GITHUB_TOKEN ;
27
-
28
- if ( ! token ) {
29
- throw new Error ( 'Missing GitHub Token' ) ;
30
- }
30
+ const region = getInput ( 'region' ) || 'us-east-1' ;
31
+ const role = getInput ( 'role' ) ;
32
+ const [ owner , repo ] = GITHUB_REPOSITORY ?. split ( '/' ) || [ ] ;
31
33
32
34
let idToken : string | undefined = undefined ;
33
35
34
36
try {
35
37
idToken = await getIDToken ( ) ;
36
38
} catch ( e ) {
37
- warn ( 'Unable to get ID Token.' ) ;
39
+ warn (
40
+ 'Unable to get ID Token. Please ensure the `id-token: write` is enabled in the GitHub Action permissions.' ,
41
+ ) ;
38
42
debug ( `Error: ${ e } ` ) ;
39
43
}
40
44
@@ -43,77 +47,113 @@ export class Action {
43
47
return ;
44
48
}
45
49
46
- // console.log(
47
- // '!!! idToken',
48
- // Buffer.from(Buffer.from(idToken, 'utf8').toString('base64'), 'utf8').toString('base64'),
49
- // );
50
+ try {
51
+ let client = new STSClient ( { region } ) ;
52
+ const assumeResponse = await client . send (
53
+ new AssumeRoleWithWebIdentityCommand ( {
54
+ WebIdentityToken : idToken ,
55
+ RoleArn : role ,
56
+ RoleSessionName : `${ packageJson . name } @${ packageJson . version } :${ context . runId } ` ,
57
+ } ) ,
58
+ ) ;
59
+
60
+ exportVariable ( 'AWS_DEFAULT_REGION' , region ) ;
61
+ exportVariable ( 'AWS_ACCESS_KEY_ID' , assumeResponse . Credentials ?. AccessKeyId ) ;
62
+ exportVariable ( 'AWS_SECRET_ACCESS_KEY' , assumeResponse . Credentials ?. SecretAccessKey ) ;
63
+ exportVariable ( 'AWS_SESSION_TOKEN' , assumeResponse . Credentials ?. SessionToken ) ;
50
64
51
- const JWKS = jose . createRemoteJWKSet (
52
- new URL ( 'https://token.actions.githubusercontent.com/.well-known/jwks' ) ,
53
- ) ;
65
+ client = new STSClient ( {
66
+ region,
67
+ credentials : {
68
+ accessKeyId : assumeResponse . Credentials ! . AccessKeyId ! ,
69
+ secretAccessKey : assumeResponse . Credentials ! . SecretAccessKey ! ,
70
+ sessionToken : assumeResponse . Credentials ! . SessionToken ! ,
71
+ } ,
72
+ } ) ;
54
73
55
- const { payload, protectedHeader } = await jose . jwtVerify ( idToken , JWKS , {
56
- issuer : 'https://token.actions.githubusercontent.com' ,
57
- // audience: 'urn:example:audience',
58
- } ) ;
74
+ const callerIdentity = await client . send ( new GetCallerIdentityCommand ( { } ) ) ;
59
75
60
- console . log ( '!!! payload' , payload ) ;
61
- console . log ( '!!! protectedHeader' , protectedHeader ) ;
76
+ info (
77
+ `Assumed ${ role } : ${ callerIdentity . Arn } (Credential expiration at ${ assumeResponse . Credentials ?. Expiration } )` ,
78
+ ) ;
79
+ } catch ( e ) {
80
+ if ( ! ( e instanceof Error ) ) {
81
+ throw e ;
82
+ }
83
+ debug ( `Error: ${ e } ` ) ;
84
+ throw new Error ( `Unable to assume role: ${ e . message } \n${ roleSetupInstructions ( owner , repo ) } ` ) ;
85
+ }
62
86
}
63
87
64
88
async post ( ) : Promise < void > {
65
- debug ( 'Post Running!' ) ;
66
-
67
89
const token = getInput ( 'token' ) || GITHUB_TOKEN ;
68
90
69
91
if ( ! token ) {
70
92
throw new Error ( 'Missing GitHub Token' ) ;
71
93
}
72
94
73
- let serverlessState : ServerlessState | undefined = undefined ;
95
+ const httpApiUrl = await this . httpApiUrl ;
74
96
75
- try {
76
- serverlessState = JSON . parse (
77
- fs . readFileSync ( path . join ( '.serverless' , 'serverless-state.json' ) , 'utf8' ) ,
78
- ) ;
79
- } catch ( e ) {
80
- warn ( 'No serverless state found.' ) ;
81
- debug ( `Error: ${ e } ` ) ;
82
- return ;
83
- }
97
+ notice ( `HTTP API URL: ${ httpApiUrl } ` ) ;
98
+ }
99
+
100
+ get serverlessState ( ) : Promise < ServerlessState | undefined > {
101
+ return new Promise ( async ( resolve ) => {
102
+ try {
103
+ const serverlessState = JSON . parse (
104
+ fs . readFileSync ( path . join ( '.serverless' , 'serverless-state.json' ) , 'utf8' ) ,
105
+ ) ;
106
+
107
+ return resolve ( serverlessState ) ;
108
+ } catch ( e ) {
109
+ warn ( 'No serverless state found.' ) ;
110
+ debug ( `Error: ${ e } ` ) ;
111
+ return ;
112
+ }
113
+ } ) ;
114
+ }
84
115
85
- let httpApiUrl : string | undefined = undefined ;
116
+ get stack ( ) : Promise < Stack | undefined > {
117
+ return new Promise ( async ( resolve ) => {
118
+ const serverlessState = await this . serverlessState ;
86
119
87
- try {
88
- const stackName = `${ serverlessState ! . service . service } -${
89
- serverlessState ! . service . provider . stage
90
- } `;
120
+ if ( ! serverlessState ) {
121
+ return resolve ( undefined ) ;
122
+ }
91
123
92
- const client = new CloudFormationClient ( { region : serverlessState ! . service . provider . region } ) ;
124
+ const stackName = `${ serverlessState . service . service } -${ serverlessState . service . provider . stage } ` ;
125
+
126
+ const client = new CloudFormationClient ( { region : serverlessState . service . provider . region } ) ;
93
127
94
128
const describeStacks = await client . send ( new DescribeStacksCommand ( { StackName : stackName } ) ) ;
95
129
96
130
const stack = describeStacks . Stacks ?. find (
97
131
( s ) =>
98
132
s . StackName === stackName &&
99
133
s . Tags ?. find (
100
- ( t ) => t . Key === 'STAGE' && t . Value === serverlessState ! . service . provider . stage ,
134
+ ( t ) => t . Key === 'STAGE' && t . Value === serverlessState . service . provider . stage ,
101
135
) ,
102
136
) ;
103
137
104
138
if ( ! stack ) {
105
139
warn ( 'Unable to find stack.' ) ;
106
140
debug ( JSON . stringify ( describeStacks ) ) ;
107
- return ;
141
+ return resolve ( undefined ) ;
108
142
}
109
143
110
- httpApiUrl = stack . Outputs ?. find ( ( o ) => o . OutputKey === 'HttpApiUrl' ) ?. OutputValue ;
111
- } catch ( e ) {
112
- warn ( 'Unable to determine HTTP API URL.' ) ;
113
- debug ( `Error: ${ e } ` ) ;
114
- return ;
115
- }
144
+ return resolve ( stack ) ;
145
+ } ) ;
146
+ }
116
147
117
- notice ( `HTTP API URL: ${ httpApiUrl } ` ) ;
148
+ get httpApiUrl ( ) : Promise < string | undefined > {
149
+ return new Promise ( async ( resolve ) => {
150
+ const stack = await this . stack ;
151
+
152
+ if ( ! stack ) {
153
+ return resolve ( undefined ) ;
154
+ }
155
+
156
+ return resolve ( stack . Outputs ?. find ( ( o ) => o . OutputKey === 'HttpApiUrl' ) ?. OutputValue ) ;
157
+ } ) ;
118
158
}
119
159
}
0 commit comments