cloudmason2 1.5.0 → 1.7.0
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/commands/new_app.js +44 -24
- package/commands/update_listing.js +45 -13
- package/main.js +2 -1
- package/package.json +1 -1
package/commands/new_app.js
CHANGED
|
@@ -105,6 +105,25 @@ function createLocalMarketplace(){
|
|
|
105
105
|
console.log(`Created ${destDir}`);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// Trust policy allowing GitHub Actions OIDC assumption from one repo.
|
|
109
|
+
// Shared with update-listing's -repo trust update
|
|
110
|
+
exports.githubTrustPolicy = function(oidcArn, repo){
|
|
111
|
+
return {
|
|
112
|
+
Version: "2012-10-17",
|
|
113
|
+
Statement: [
|
|
114
|
+
{
|
|
115
|
+
Effect: "Allow",
|
|
116
|
+
Principal: { Federated: oidcArn },
|
|
117
|
+
Action: "sts:AssumeRoleWithWebIdentity",
|
|
118
|
+
Condition: {
|
|
119
|
+
StringEquals: { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" },
|
|
120
|
+
StringLike: { "token.actions.githubusercontent.com:sub": `repo:${repo}:*` }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
108
127
|
// Create a role GitHub Actions can assume via OIDC from the given repo.
|
|
109
128
|
// Permissions cover update-listing, update-app, launch, and publish - not new-app
|
|
110
129
|
async function ensureGithubRole(repo, bucket, appPrefix, appName, productId){
|
|
@@ -115,34 +134,27 @@ async function ensureGithubRole(repo, bucket, appPrefix, appName, productId){
|
|
|
115
134
|
|
|
116
135
|
const oidcArn = await ensureOidcProvider(iam, accountId);
|
|
117
136
|
|
|
137
|
+
// Reuse the role if it already exists - the permissions policy below is
|
|
138
|
+
// still re-applied, so re-running new-app refreshes an existing role
|
|
139
|
+
let roleArn = null;
|
|
118
140
|
try {
|
|
119
141
|
const existing = await iam.send(new GetRoleCommand({ RoleName: roleName }));
|
|
120
142
|
console.log(`Role ${roleName} already exists (${existing.Role.Arn})`);
|
|
121
|
-
|
|
143
|
+
roleArn = existing.Role.Arn;
|
|
122
144
|
} catch (e){
|
|
123
145
|
if (!/NoSuchEntity/.test(e)){ throw e }
|
|
124
146
|
}
|
|
125
147
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
StringLike: { "token.actions.githubusercontent.com:sub": `repo:${repo}:*` }
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
]
|
|
140
|
-
};
|
|
141
|
-
const created = await iam.send(new CreateRoleCommand({
|
|
142
|
-
RoleName: roleName,
|
|
143
|
-
AssumeRolePolicyDocument: JSON.stringify(trustPolicy),
|
|
144
|
-
Description: `Mason CI role for ${appName} (github.com/${repo})`
|
|
145
|
-
}));
|
|
148
|
+
if (!roleArn){
|
|
149
|
+
// Trust: only GitHub Actions workflows from this repo
|
|
150
|
+
const trustPolicy = exports.githubTrustPolicy(oidcArn, repo);
|
|
151
|
+
const created = await iam.send(new CreateRoleCommand({
|
|
152
|
+
RoleName: roleName,
|
|
153
|
+
AssumeRolePolicyDocument: JSON.stringify(trustPolicy),
|
|
154
|
+
Description: `Mason CI role for ${appName} (github.com/${repo})`
|
|
155
|
+
}));
|
|
156
|
+
roleArn = created.Role.Arn;
|
|
157
|
+
}
|
|
146
158
|
|
|
147
159
|
// Scoped to the app prefix, product id, and app stack names where the
|
|
148
160
|
// services support it. EC2 build permissions cannot be scoped
|
|
@@ -181,6 +193,14 @@ async function ensureGithubRole(repo, bucket, appPrefix, appName, productId){
|
|
|
181
193
|
Action: ["cloudformation:CreateStack", "cloudformation:UpdateStack", "cloudformation:DescribeStacks"],
|
|
182
194
|
Resource: `arn:aws:cloudformation:*:${accountId}:stack/${appName}-*/*`
|
|
183
195
|
},
|
|
196
|
+
{
|
|
197
|
+
Sid: "ReadTemplateSummary",
|
|
198
|
+
Effect: "Allow",
|
|
199
|
+
// launch calls GetTemplateSummary with a TemplateURL, which IAM
|
|
200
|
+
// evaluates against * rather than a stack ARN
|
|
201
|
+
Action: "cloudformation:GetTemplateSummary",
|
|
202
|
+
Resource: "*"
|
|
203
|
+
},
|
|
184
204
|
{
|
|
185
205
|
Sid: "ReadRoleParam",
|
|
186
206
|
Effect: "Allow",
|
|
@@ -201,9 +221,9 @@ async function ensureGithubRole(repo, bucket, appPrefix, appName, productId){
|
|
|
201
221
|
PolicyDocument: JSON.stringify(policy)
|
|
202
222
|
}));
|
|
203
223
|
|
|
204
|
-
console.log(`
|
|
205
|
-
console.log(`Use in GitHub Actions with: role-to-assume: ${
|
|
206
|
-
return
|
|
224
|
+
console.log(`Configured role ${roleName} (${roleArn})`);
|
|
225
|
+
console.log(`Use in GitHub Actions with: role-to-assume: ${roleArn}`);
|
|
226
|
+
return roleArn;
|
|
207
227
|
}
|
|
208
228
|
|
|
209
229
|
async function ensureOidcProvider(iam, accountId){
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
|
|
4
|
+
const { IAMClient, GetRoleCommand, UpdateAssumeRolePolicyCommand } = require("@aws-sdk/client-iam");
|
|
5
|
+
const { STSClient, GetCallerIdentityCommand } = require("@aws-sdk/client-sts");
|
|
4
6
|
const OrgConfig = require('./helpers/org_config');
|
|
5
7
|
const Apps = require('./helpers/apps');
|
|
6
8
|
const Mime = require('./helpers/mime');
|
|
9
|
+
const { githubTrustPolicy } = require('./new_app');
|
|
7
10
|
|
|
8
11
|
// Marketplace listing text files stored as top-level manifest attributes.
|
|
9
12
|
// Names match the keys the AWS Marketplace Catalog API expects
|
|
@@ -27,25 +30,32 @@ exports.main = async function(args){
|
|
|
27
30
|
if (!app){ throw new Error('No app named ' + args.app + '. Run new-app') }
|
|
28
31
|
const { appPrefix, manifest } = app;
|
|
29
32
|
|
|
30
|
-
if (!args.assets && !args.pid && !args.ec2){
|
|
31
|
-
console.log('Nothing to update. Pass -assets, -pid, or -
|
|
33
|
+
if (!args.assets && !args.pid && !args.ec2 && !args.repo){
|
|
34
|
+
console.log('Nothing to update. Pass -assets, -pid, -ec2, or -repo');
|
|
32
35
|
return true;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
// Apply all manifest updates in one download/update/upload pass
|
|
36
|
-
if (args.assets){
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
if (args.assets || args.pid || args.ec2){
|
|
40
|
+
if (args.assets){
|
|
41
|
+
await applyAssets(s3, bucket, appPrefix, cfg.region, args.assets, manifest);
|
|
42
|
+
}
|
|
43
|
+
if (args.pid){
|
|
44
|
+
manifest.productId = args.pid;
|
|
45
|
+
console.log('Set productId');
|
|
46
|
+
}
|
|
47
|
+
if (args.ec2){
|
|
48
|
+
manifest.RecommendedInstanceType = args.ec2;
|
|
49
|
+
console.log('Set RecommendedInstanceType');
|
|
50
|
+
}
|
|
51
|
+
await putManifest(s3, bucket, appPrefix, manifest);
|
|
52
|
+
console.log('Updated manifest');
|
|
42
53
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
|
|
55
|
+
// Replace the CI role trust policy with one for the updated repo
|
|
56
|
+
if (args.repo){
|
|
57
|
+
await updateRepoTrust(appName, args.repo);
|
|
46
58
|
}
|
|
47
|
-
await putManifest(s3, bucket, appPrefix, manifest);
|
|
48
|
-
console.log('Updated manifest');
|
|
49
59
|
return true;
|
|
50
60
|
}
|
|
51
61
|
|
|
@@ -115,6 +125,28 @@ async function applyAssets(s3, bucket, appPrefix, region, assetsPath, manifest){
|
|
|
115
125
|
}
|
|
116
126
|
}
|
|
117
127
|
|
|
128
|
+
async function updateRepoTrust(appName, repo){
|
|
129
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(repo)){ throw new Error('Invalid repo. Use org/repo format') }
|
|
130
|
+
const roleName = `mason-gha-${appName}`;
|
|
131
|
+
const iam = new IAMClient({ region: 'us-east-1' });
|
|
132
|
+
const sts = new STSClient({ region: 'us-east-1' });
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
await iam.send(new GetRoleCommand({ RoleName: roleName }));
|
|
136
|
+
} catch (e){
|
|
137
|
+
if (/NoSuchEntity/.test(e)){ throw new Error(`No CI role ${roleName} found. Run new-app with -repo first`) }
|
|
138
|
+
throw e;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const accountId = (await sts.send(new GetCallerIdentityCommand({}))).Account;
|
|
142
|
+
const oidcArn = `arn:aws:iam::${accountId}:oidc-provider/token.actions.githubusercontent.com`;
|
|
143
|
+
await iam.send(new UpdateAssumeRolePolicyCommand({
|
|
144
|
+
RoleName: roleName,
|
|
145
|
+
PolicyDocument: JSON.stringify(githubTrustPolicy(oidcArn, repo))
|
|
146
|
+
}));
|
|
147
|
+
console.log(`Updated ${roleName} trust policy to repo ${repo}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
118
150
|
async function putManifest(s3, bucket, appPrefix, manifest){
|
|
119
151
|
await s3.send(new PutObjectCommand({
|
|
120
152
|
Bucket: bucket,
|
package/main.js
CHANGED
|
@@ -39,7 +39,8 @@ const Commands = {
|
|
|
39
39
|
{n: 'app', desc: 'Name of existing app', pattern: `^[A-Za-z]{2,20}$`, r: true},
|
|
40
40
|
{n: 'assets', desc: 'Path to folder with application assets', r: false},
|
|
41
41
|
{n: 'pid', desc: 'AWS Product ID', r: false},
|
|
42
|
-
{n: 'ec2', desc: 'Instance type (default r8g.medium)', r: false}
|
|
42
|
+
{n: 'ec2', desc: 'Instance type (default r8g.medium)', r: false},
|
|
43
|
+
{n: 'repo', desc: 'GitHub repo (org/repo). Replaces the CI role trust policy with this repo', r: false}
|
|
43
44
|
]
|
|
44
45
|
},
|
|
45
46
|
'update-assets': {
|