cloudmason2 1.5.0 → 1.6.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.
@@ -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){
@@ -124,20 +143,7 @@ async function ensureGithubRole(repo, bucket, appPrefix, appName, productId){
124
143
  }
125
144
 
126
145
  // Trust: only GitHub Actions workflows from this repo
127
- const trustPolicy = {
128
- Version: "2012-10-17",
129
- Statement: [
130
- {
131
- Effect: "Allow",
132
- Principal: { Federated: oidcArn },
133
- Action: "sts:AssumeRoleWithWebIdentity",
134
- Condition: {
135
- StringEquals: { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" },
136
- StringLike: { "token.actions.githubusercontent.com:sub": `repo:${repo}:*` }
137
- }
138
- }
139
- ]
140
- };
146
+ const trustPolicy = exports.githubTrustPolicy(oidcArn, repo);
141
147
  const created = await iam.send(new CreateRoleCommand({
142
148
  RoleName: roleName,
143
149
  AssumeRolePolicyDocument: JSON.stringify(trustPolicy),
@@ -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 -ec2');
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
- await applyAssets(s3, bucket, appPrefix, cfg.region, args.assets, manifest);
38
- }
39
- if (args.pid){
40
- manifest.productId = args.pid;
41
- console.log('Set productId');
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
- if (args.ec2){
44
- manifest.RecommendedInstanceType = args.ec2;
45
- console.log('Set RecommendedInstanceType');
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': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudmason2",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "",
5
5
  "main": "main.js",
6
6
  "files": [