@factiii/stack 0.1.177 → 0.1.182
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 +21 -21
- package/README.md +441 -441
- package/bin/stack +300 -300
- package/dist/cli/dev-sync.js +16 -16
- package/dist/plugins/addons/auth/index.js +7 -7
- package/dist/plugins/addons/vercel/index.js +9 -9
- package/dist/plugins/addons/vercel/scanfix/config.js +10 -10
- package/dist/plugins/addons/vercel/scanfix/token.js +15 -15
- package/dist/plugins/approved.json +13 -13
- package/dist/plugins/pipelines/aws/index.js +12 -12
- package/dist/plugins/pipelines/aws/policies/bootstrap-policy.json +135 -135
- package/dist/plugins/pipelines/aws/prod.js +1 -1
- package/dist/plugins/pipelines/aws/scanfix/iam.d.ts.map +1 -1
- package/dist/plugins/pipelines/aws/scanfix/iam.js +40 -29
- package/dist/plugins/pipelines/aws/scanfix/iam.js.map +1 -1
- package/dist/plugins/pipelines/aws/utils/aws-helpers.d.ts +3 -1
- package/dist/plugins/pipelines/aws/utils/aws-helpers.d.ts.map +1 -1
- package/dist/plugins/pipelines/aws/utils/aws-helpers.js +31 -2
- package/dist/plugins/pipelines/aws/utils/aws-helpers.js.map +1 -1
- package/dist/plugins/pipelines/factiii/prod.js +21 -21
- package/dist/plugins/pipelines/factiii/scanfix/env-files.d.ts.map +1 -1
- package/dist/plugins/pipelines/factiii/scanfix/env-files.js +59 -0
- package/dist/plugins/pipelines/factiii/scanfix/env-files.js.map +1 -1
- package/dist/plugins/pipelines/factiii/staging.js +23 -23
- package/dist/plugins/pipelines/factiii/workflows/stack-ci.yml +75 -75
- package/dist/plugins/pipelines/factiii/workflows/stack-cicd-prod.yml +73 -73
- package/dist/plugins/servers/amazon-linux/index.js +16 -16
- package/dist/plugins/servers/mac/index.js +12 -12
- package/dist/plugins/servers/mac/staging.js +2 -2
- package/dist/plugins/servers/ubuntu/index.js +23 -23
- package/dist/plugins/servers/windows/index.js +15 -15
- package/dist/scripts/generate-all.js +73 -73
- package/dist/utils/deployment-report.js +2 -2
- package/dist/utils/secret-prompts.js +34 -34
- package/dist/utils/ssh-helper.d.ts.map +1 -1
- package/dist/utils/ssh-helper.js +31 -1
- package/dist/utils/ssh-helper.js.map +1 -1
- package/dist/utils/template-generator.js +74 -74
- package/package.json +93 -100
|
@@ -142,13 +142,13 @@ class AuthAddon {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
static helpText = {
|
|
145
|
-
JWT_SECRET: `
|
|
146
|
-
JWT signing secret for @factiii/auth.
|
|
147
|
-
|
|
148
|
-
This is auto-generated (256-bit random) when you run:
|
|
149
|
-
npx stack fix --secrets
|
|
150
|
-
|
|
151
|
-
The secret is stored in Ansible Vault and used to sign
|
|
145
|
+
JWT_SECRET: `
|
|
146
|
+
JWT signing secret for @factiii/auth.
|
|
147
|
+
|
|
148
|
+
This is auto-generated (256-bit random) when you run:
|
|
149
|
+
npx stack fix --secrets
|
|
150
|
+
|
|
151
|
+
The secret is stored in Ansible Vault and used to sign
|
|
152
152
|
authentication tokens (JWT) for your application.`,
|
|
153
153
|
};
|
|
154
154
|
// ============================================================
|
|
@@ -128,15 +128,15 @@ class VercelAddon {
|
|
|
128
128
|
return false;
|
|
129
129
|
}
|
|
130
130
|
static helpText = {
|
|
131
|
-
VERCEL_TOKEN: `
|
|
132
|
-
Vercel API Token for deployments.
|
|
133
|
-
|
|
134
|
-
Get from: https://vercel.com/account/tokens
|
|
135
|
-
|
|
136
|
-
Create a new token with:
|
|
137
|
-
- Scope: Full Account (or specific team)
|
|
138
|
-
- Expiration: No Expiration (or custom)
|
|
139
|
-
|
|
131
|
+
VERCEL_TOKEN: `
|
|
132
|
+
Vercel API Token for deployments.
|
|
133
|
+
|
|
134
|
+
Get from: https://vercel.com/account/tokens
|
|
135
|
+
|
|
136
|
+
Create a new token with:
|
|
137
|
+
- Scope: Full Account (or specific team)
|
|
138
|
+
- Expiration: No Expiration (or custom)
|
|
139
|
+
|
|
140
140
|
The token will be stored securely in Ansible Vault.`,
|
|
141
141
|
};
|
|
142
142
|
// ============================================================
|
|
@@ -259,13 +259,13 @@ exports.fixes = [
|
|
|
259
259
|
return false;
|
|
260
260
|
}
|
|
261
261
|
},
|
|
262
|
-
manualFix: `
|
|
263
|
-
Add Vercel to stack.yml:
|
|
264
|
-
|
|
265
|
-
vercel: {}
|
|
266
|
-
|
|
267
|
-
Then run: npx stack fix
|
|
268
|
-
(Auto-creates project, detects framework, and saves IDs via Vercel API)
|
|
262
|
+
manualFix: `
|
|
263
|
+
Add Vercel to stack.yml:
|
|
264
|
+
|
|
265
|
+
vercel: {}
|
|
266
|
+
|
|
267
|
+
Then run: npx stack fix
|
|
268
|
+
(Auto-creates project, detects framework, and saves IDs via Vercel API)
|
|
269
269
|
`,
|
|
270
270
|
},
|
|
271
271
|
{
|
|
@@ -335,9 +335,9 @@ Then run: npx stack fix
|
|
|
335
335
|
return false;
|
|
336
336
|
}
|
|
337
337
|
},
|
|
338
|
-
manualFix: `
|
|
339
|
-
Run: npx stack fix
|
|
340
|
-
(Auto-creates .vercel/project.json from Vercel API — no CLI needed)
|
|
338
|
+
manualFix: `
|
|
339
|
+
Run: npx stack fix
|
|
340
|
+
(Auto-creates .vercel/project.json from Vercel API — no CLI needed)
|
|
341
341
|
`,
|
|
342
342
|
},
|
|
343
343
|
{
|
|
@@ -107,12 +107,12 @@ exports.fixes = [
|
|
|
107
107
|
return false;
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
|
-
manualFix: `
|
|
111
|
-
Store VERCEL_TOKEN in Ansible Vault manually:
|
|
112
|
-
|
|
113
|
-
npx stack deploy --secrets set VERCEL_TOKEN
|
|
114
|
-
|
|
115
|
-
Or get token from: https://vercel.com/account/tokens
|
|
110
|
+
manualFix: `
|
|
111
|
+
Store VERCEL_TOKEN in Ansible Vault manually:
|
|
112
|
+
|
|
113
|
+
npx stack deploy --secrets set VERCEL_TOKEN
|
|
114
|
+
|
|
115
|
+
Or get token from: https://vercel.com/account/tokens
|
|
116
116
|
`,
|
|
117
117
|
},
|
|
118
118
|
{
|
|
@@ -128,15 +128,15 @@ Or get token from: https://vercel.com/account/tokens
|
|
|
128
128
|
return !process.env.VERCEL_TOKEN;
|
|
129
129
|
},
|
|
130
130
|
fix: null,
|
|
131
|
-
manualFix: `
|
|
132
|
-
VERCEL_TOKEN is not required in your environment during development.
|
|
133
|
-
It will be automatically read from Ansible Vault during deployment.
|
|
134
|
-
|
|
135
|
-
If you want to set it in your shell for testing:
|
|
136
|
-
export VERCEL_TOKEN="your-token-here"
|
|
137
|
-
|
|
138
|
-
Or add to your shell profile (~/.bashrc, ~/.zshrc):
|
|
139
|
-
export VERCEL_TOKEN="$(npx stack deploy --secrets get VERCEL_TOKEN)"
|
|
131
|
+
manualFix: `
|
|
132
|
+
VERCEL_TOKEN is not required in your environment during development.
|
|
133
|
+
It will be automatically read from Ansible Vault during deployment.
|
|
134
|
+
|
|
135
|
+
If you want to set it in your shell for testing:
|
|
136
|
+
export VERCEL_TOKEN="your-token-here"
|
|
137
|
+
|
|
138
|
+
Or add to your shell profile (~/.bashrc, ~/.zshrc):
|
|
139
|
+
export VERCEL_TOKEN="$(npx stack deploy --secrets get VERCEL_TOKEN)"
|
|
140
140
|
`,
|
|
141
141
|
},
|
|
142
142
|
];
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 1,
|
|
3
|
-
"description": "List of approved/validated external plugins for Factiii Stack",
|
|
4
|
-
"approved": [
|
|
5
|
-
"@factiii/stack-plugin-expo",
|
|
6
|
-
"@factiii/stack-plugin-prisma-trpc",
|
|
7
|
-
"@factiii/stack-plugin-nextjs"
|
|
8
|
-
],
|
|
9
|
-
"lastUpdated": "2024-12-16"
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"description": "List of approved/validated external plugins for Factiii Stack",
|
|
4
|
+
"approved": [
|
|
5
|
+
"@factiii/stack-plugin-expo",
|
|
6
|
+
"@factiii/stack-plugin-prisma-trpc",
|
|
7
|
+
"@factiii/stack-plugin-nextjs"
|
|
8
|
+
],
|
|
9
|
+
"lastUpdated": "2024-12-16"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
@@ -166,17 +166,17 @@ class AWSPipeline {
|
|
|
166
166
|
'free-tier': free_tier_js_1.default,
|
|
167
167
|
};
|
|
168
168
|
static helpText = {
|
|
169
|
-
SSH: `
|
|
170
|
-
SSH private key for accessing the EC2 instance.
|
|
171
|
-
|
|
172
|
-
Option A: Auto-generate via AWS (recommended)
|
|
173
|
-
- Factiii will create an EC2 Key Pair via AWS API
|
|
174
|
-
|
|
175
|
-
Option B: Use existing key
|
|
169
|
+
SSH: `
|
|
170
|
+
SSH private key for accessing the EC2 instance.
|
|
171
|
+
|
|
172
|
+
Option A: Auto-generate via AWS (recommended)
|
|
173
|
+
- Factiii will create an EC2 Key Pair via AWS API
|
|
174
|
+
|
|
175
|
+
Option B: Use existing key
|
|
176
176
|
ssh-keygen -t ed25519 -C "deploy-key" -f ~/.ssh/deploy_key`,
|
|
177
|
-
AWS_SECRET_ACCESS_KEY: `
|
|
178
|
-
AWS Secret Access Key
|
|
179
|
-
|
|
177
|
+
AWS_SECRET_ACCESS_KEY: `
|
|
178
|
+
AWS Secret Access Key
|
|
179
|
+
|
|
180
180
|
Get from AWS Console: IAM -> Users -> Security credentials`,
|
|
181
181
|
};
|
|
182
182
|
// ============================================================
|
|
@@ -498,8 +498,8 @@ class AWSPipeline {
|
|
|
498
498
|
}
|
|
499
499
|
try {
|
|
500
500
|
const repoName = config.name ?? 'app';
|
|
501
|
-
await AWSPipeline.sshExec(envConfig, `
|
|
502
|
-
cd ~/.factiii && docker compose stop ${repoName}-prod
|
|
501
|
+
await AWSPipeline.sshExec(envConfig, `
|
|
502
|
+
cd ~/.factiii && docker compose stop ${repoName}-prod
|
|
503
503
|
`);
|
|
504
504
|
return { success: true, message: 'Production containers stopped' };
|
|
505
505
|
}
|
|
@@ -1,135 +1,135 @@
|
|
|
1
|
-
{
|
|
2
|
-
"Version": "2012-10-17",
|
|
3
|
-
"Statement": [
|
|
4
|
-
{
|
|
5
|
-
"Sid": "FactiiiEC2Full",
|
|
6
|
-
"Effect": "Allow",
|
|
7
|
-
"Action": [
|
|
8
|
-
"ec2:CreateVpc",
|
|
9
|
-
"ec2:DeleteVpc",
|
|
10
|
-
"ec2:DescribeVpcs",
|
|
11
|
-
"ec2:ModifyVpcAttribute",
|
|
12
|
-
"ec2:CreateSubnet",
|
|
13
|
-
"ec2:DeleteSubnet",
|
|
14
|
-
"ec2:DescribeSubnets",
|
|
15
|
-
"ec2:ModifySubnetAttribute",
|
|
16
|
-
"ec2:CreateInternetGateway",
|
|
17
|
-
"ec2:DeleteInternetGateway",
|
|
18
|
-
"ec2:AttachInternetGateway",
|
|
19
|
-
"ec2:DetachInternetGateway",
|
|
20
|
-
"ec2:DescribeInternetGateways",
|
|
21
|
-
"ec2:CreateRouteTable",
|
|
22
|
-
"ec2:DeleteRouteTable",
|
|
23
|
-
"ec2:CreateRoute",
|
|
24
|
-
"ec2:AssociateRouteTable",
|
|
25
|
-
"ec2:DescribeRouteTables",
|
|
26
|
-
"ec2:CreateSecurityGroup",
|
|
27
|
-
"ec2:DeleteSecurityGroup",
|
|
28
|
-
"ec2:AuthorizeSecurityGroupIngress",
|
|
29
|
-
"ec2:RevokeSecurityGroupIngress",
|
|
30
|
-
"ec2:DescribeSecurityGroups",
|
|
31
|
-
"ec2:CreateKeyPair",
|
|
32
|
-
"ec2:DeleteKeyPair",
|
|
33
|
-
"ec2:DescribeKeyPairs",
|
|
34
|
-
"ec2:RunInstances",
|
|
35
|
-
"ec2:TerminateInstances",
|
|
36
|
-
"ec2:DescribeInstances",
|
|
37
|
-
"ec2:AllocateAddress",
|
|
38
|
-
"ec2:ReleaseAddress",
|
|
39
|
-
"ec2:AssociateAddress",
|
|
40
|
-
"ec2:DescribeAddresses",
|
|
41
|
-
"ec2:DescribeAvailabilityZones",
|
|
42
|
-
"ec2:DescribeImages",
|
|
43
|
-
"ec2:CreateTags"
|
|
44
|
-
],
|
|
45
|
-
"Resource": "*"
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"Sid": "FactiiiRDSFull",
|
|
49
|
-
"Effect": "Allow",
|
|
50
|
-
"Action": [
|
|
51
|
-
"rds:CreateDBInstance",
|
|
52
|
-
"rds:DeleteDBInstance",
|
|
53
|
-
"rds:DescribeDBInstances",
|
|
54
|
-
"rds:CreateDBSubnetGroup",
|
|
55
|
-
"rds:DeleteDBSubnetGroup",
|
|
56
|
-
"rds:DescribeDBSubnetGroups",
|
|
57
|
-
"rds:AddTagsToResource",
|
|
58
|
-
"rds:ListTagsForResource"
|
|
59
|
-
],
|
|
60
|
-
"Resource": "*"
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"Sid": "FactiiiS3Full",
|
|
64
|
-
"Effect": "Allow",
|
|
65
|
-
"Action": [
|
|
66
|
-
"s3:CreateBucket",
|
|
67
|
-
"s3:DeleteBucket",
|
|
68
|
-
"s3:ListBucket",
|
|
69
|
-
"s3:PutBucketEncryption",
|
|
70
|
-
"s3:PutBucketPublicAccessBlock",
|
|
71
|
-
"s3:PutBucketCORS",
|
|
72
|
-
"s3:GetBucketEncryption",
|
|
73
|
-
"s3:GetBucketPublicAccessBlock",
|
|
74
|
-
"s3:GetBucketCORS",
|
|
75
|
-
"s3:PutObject",
|
|
76
|
-
"s3:GetObject",
|
|
77
|
-
"s3:ListAllMyBuckets"
|
|
78
|
-
],
|
|
79
|
-
"Resource": "*"
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
"Sid": "FactiiiECRFull",
|
|
83
|
-
"Effect": "Allow",
|
|
84
|
-
"Action": [
|
|
85
|
-
"ecr:CreateRepository",
|
|
86
|
-
"ecr:DeleteRepository",
|
|
87
|
-
"ecr:DescribeRepositories",
|
|
88
|
-
"ecr:GetAuthorizationToken",
|
|
89
|
-
"ecr:PutLifecyclePolicy",
|
|
90
|
-
"ecr:BatchGetImage",
|
|
91
|
-
"ecr:BatchCheckLayerAvailability",
|
|
92
|
-
"ecr:PutImage",
|
|
93
|
-
"ecr:InitiateLayerUpload",
|
|
94
|
-
"ecr:UploadLayerPart",
|
|
95
|
-
"ecr:CompleteLayerUpload"
|
|
96
|
-
],
|
|
97
|
-
"Resource": "*"
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
"Sid": "FactiiiSES",
|
|
101
|
-
"Effect": "Allow",
|
|
102
|
-
"Action": [
|
|
103
|
-
"ses:VerifyDomainIdentity",
|
|
104
|
-
"ses:VerifyDomainDkim",
|
|
105
|
-
"ses:GetAccountSendingEnabled",
|
|
106
|
-
"ses:GetIdentityVerificationAttributes",
|
|
107
|
-
"ses:GetIdentityDkimAttributes"
|
|
108
|
-
],
|
|
109
|
-
"Resource": "*"
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
"Sid": "FactiiiIAMLimited",
|
|
113
|
-
"Effect": "Allow",
|
|
114
|
-
"Action": [
|
|
115
|
-
"iam:CreateUser",
|
|
116
|
-
"iam:DeleteUser",
|
|
117
|
-
"iam:GetUser",
|
|
118
|
-
"iam:PutUserPolicy",
|
|
119
|
-
"iam:DeleteUserPolicy",
|
|
120
|
-
"iam:CreateAccessKey",
|
|
121
|
-
"iam:ListAccessKeys",
|
|
122
|
-
"iam:ListUsers"
|
|
123
|
-
],
|
|
124
|
-
"Resource": "*"
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
"Sid": "FactiiiSTS",
|
|
128
|
-
"Effect": "Allow",
|
|
129
|
-
"Action": [
|
|
130
|
-
"sts:GetCallerIdentity"
|
|
131
|
-
],
|
|
132
|
-
"Resource": "*"
|
|
133
|
-
}
|
|
134
|
-
]
|
|
135
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"Version": "2012-10-17",
|
|
3
|
+
"Statement": [
|
|
4
|
+
{
|
|
5
|
+
"Sid": "FactiiiEC2Full",
|
|
6
|
+
"Effect": "Allow",
|
|
7
|
+
"Action": [
|
|
8
|
+
"ec2:CreateVpc",
|
|
9
|
+
"ec2:DeleteVpc",
|
|
10
|
+
"ec2:DescribeVpcs",
|
|
11
|
+
"ec2:ModifyVpcAttribute",
|
|
12
|
+
"ec2:CreateSubnet",
|
|
13
|
+
"ec2:DeleteSubnet",
|
|
14
|
+
"ec2:DescribeSubnets",
|
|
15
|
+
"ec2:ModifySubnetAttribute",
|
|
16
|
+
"ec2:CreateInternetGateway",
|
|
17
|
+
"ec2:DeleteInternetGateway",
|
|
18
|
+
"ec2:AttachInternetGateway",
|
|
19
|
+
"ec2:DetachInternetGateway",
|
|
20
|
+
"ec2:DescribeInternetGateways",
|
|
21
|
+
"ec2:CreateRouteTable",
|
|
22
|
+
"ec2:DeleteRouteTable",
|
|
23
|
+
"ec2:CreateRoute",
|
|
24
|
+
"ec2:AssociateRouteTable",
|
|
25
|
+
"ec2:DescribeRouteTables",
|
|
26
|
+
"ec2:CreateSecurityGroup",
|
|
27
|
+
"ec2:DeleteSecurityGroup",
|
|
28
|
+
"ec2:AuthorizeSecurityGroupIngress",
|
|
29
|
+
"ec2:RevokeSecurityGroupIngress",
|
|
30
|
+
"ec2:DescribeSecurityGroups",
|
|
31
|
+
"ec2:CreateKeyPair",
|
|
32
|
+
"ec2:DeleteKeyPair",
|
|
33
|
+
"ec2:DescribeKeyPairs",
|
|
34
|
+
"ec2:RunInstances",
|
|
35
|
+
"ec2:TerminateInstances",
|
|
36
|
+
"ec2:DescribeInstances",
|
|
37
|
+
"ec2:AllocateAddress",
|
|
38
|
+
"ec2:ReleaseAddress",
|
|
39
|
+
"ec2:AssociateAddress",
|
|
40
|
+
"ec2:DescribeAddresses",
|
|
41
|
+
"ec2:DescribeAvailabilityZones",
|
|
42
|
+
"ec2:DescribeImages",
|
|
43
|
+
"ec2:CreateTags"
|
|
44
|
+
],
|
|
45
|
+
"Resource": "*"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"Sid": "FactiiiRDSFull",
|
|
49
|
+
"Effect": "Allow",
|
|
50
|
+
"Action": [
|
|
51
|
+
"rds:CreateDBInstance",
|
|
52
|
+
"rds:DeleteDBInstance",
|
|
53
|
+
"rds:DescribeDBInstances",
|
|
54
|
+
"rds:CreateDBSubnetGroup",
|
|
55
|
+
"rds:DeleteDBSubnetGroup",
|
|
56
|
+
"rds:DescribeDBSubnetGroups",
|
|
57
|
+
"rds:AddTagsToResource",
|
|
58
|
+
"rds:ListTagsForResource"
|
|
59
|
+
],
|
|
60
|
+
"Resource": "*"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"Sid": "FactiiiS3Full",
|
|
64
|
+
"Effect": "Allow",
|
|
65
|
+
"Action": [
|
|
66
|
+
"s3:CreateBucket",
|
|
67
|
+
"s3:DeleteBucket",
|
|
68
|
+
"s3:ListBucket",
|
|
69
|
+
"s3:PutBucketEncryption",
|
|
70
|
+
"s3:PutBucketPublicAccessBlock",
|
|
71
|
+
"s3:PutBucketCORS",
|
|
72
|
+
"s3:GetBucketEncryption",
|
|
73
|
+
"s3:GetBucketPublicAccessBlock",
|
|
74
|
+
"s3:GetBucketCORS",
|
|
75
|
+
"s3:PutObject",
|
|
76
|
+
"s3:GetObject",
|
|
77
|
+
"s3:ListAllMyBuckets"
|
|
78
|
+
],
|
|
79
|
+
"Resource": "*"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"Sid": "FactiiiECRFull",
|
|
83
|
+
"Effect": "Allow",
|
|
84
|
+
"Action": [
|
|
85
|
+
"ecr:CreateRepository",
|
|
86
|
+
"ecr:DeleteRepository",
|
|
87
|
+
"ecr:DescribeRepositories",
|
|
88
|
+
"ecr:GetAuthorizationToken",
|
|
89
|
+
"ecr:PutLifecyclePolicy",
|
|
90
|
+
"ecr:BatchGetImage",
|
|
91
|
+
"ecr:BatchCheckLayerAvailability",
|
|
92
|
+
"ecr:PutImage",
|
|
93
|
+
"ecr:InitiateLayerUpload",
|
|
94
|
+
"ecr:UploadLayerPart",
|
|
95
|
+
"ecr:CompleteLayerUpload"
|
|
96
|
+
],
|
|
97
|
+
"Resource": "*"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"Sid": "FactiiiSES",
|
|
101
|
+
"Effect": "Allow",
|
|
102
|
+
"Action": [
|
|
103
|
+
"ses:VerifyDomainIdentity",
|
|
104
|
+
"ses:VerifyDomainDkim",
|
|
105
|
+
"ses:GetAccountSendingEnabled",
|
|
106
|
+
"ses:GetIdentityVerificationAttributes",
|
|
107
|
+
"ses:GetIdentityDkimAttributes"
|
|
108
|
+
],
|
|
109
|
+
"Resource": "*"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"Sid": "FactiiiIAMLimited",
|
|
113
|
+
"Effect": "Allow",
|
|
114
|
+
"Action": [
|
|
115
|
+
"iam:CreateUser",
|
|
116
|
+
"iam:DeleteUser",
|
|
117
|
+
"iam:GetUser",
|
|
118
|
+
"iam:PutUserPolicy",
|
|
119
|
+
"iam:DeleteUserPolicy",
|
|
120
|
+
"iam:CreateAccessKey",
|
|
121
|
+
"iam:ListAccessKeys",
|
|
122
|
+
"iam:ListUsers"
|
|
123
|
+
],
|
|
124
|
+
"Resource": "*"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"Sid": "FactiiiSTS",
|
|
128
|
+
"Effect": "Allow",
|
|
129
|
+
"Action": [
|
|
130
|
+
"sts:GetCallerIdentity"
|
|
131
|
+
],
|
|
132
|
+
"Resource": "*"
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
@@ -154,7 +154,7 @@ async function writeEnvFile(envConfig, repoDir, environment, envVarsString) {
|
|
|
154
154
|
else {
|
|
155
155
|
// We're remote - SSH to write
|
|
156
156
|
console.log(` 📝 Writing ${envFileName} on remote server (${envVars.length} variables)...`);
|
|
157
|
-
await sshExecCommand(envConfig, `cat > ${repoDir}/${envFileName} << 'ENVEOF'
|
|
157
|
+
await sshExecCommand(envConfig, `cat > ${repoDir}/${envFileName} << 'ENVEOF'
|
|
158
158
|
${envFileContent}ENVEOF`);
|
|
159
159
|
}
|
|
160
160
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iam.d.ts","sourceRoot":"","sources":["../../../../../src/plugins/pipelines/aws/scanfix/iam.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAiB,GAAG,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"iam.d.ts","sourceRoot":"","sources":["../../../../../src/plugins/pipelines/aws/scanfix/iam.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAiB,GAAG,EAAE,MAAM,4BAA4B,CAAC;AAuQrE,eAAO,MAAM,QAAQ,EAAE,GAAG,EA2MzB,CAAC"}
|
|
@@ -181,15 +181,26 @@ function getProdPolicy(projectName, region, accountId) {
|
|
|
181
181
|
async function ensureIamAccess(config, region) {
|
|
182
182
|
if (await (0, aws_helpers_js_1.canManageIam)(region))
|
|
183
183
|
return true;
|
|
184
|
-
const callerArn = await (0, aws_helpers_js_1.getCallerArn)(region);
|
|
185
184
|
const { confirm } = await Promise.resolve().then(() => __importStar(require('../../../../utils/secret-prompts.js')));
|
|
185
|
+
const configKeyId = (0, aws_helpers_js_1.getAwsConfig)(config).accessKeyId;
|
|
186
|
+
const identity = await (0, aws_helpers_js_1.getCallerArn)(region);
|
|
187
|
+
// Determine if we can't connect at all vs connected but lacking permissions
|
|
188
|
+
const canConnect = !!identity;
|
|
186
189
|
console.log('');
|
|
187
190
|
console.log(' ============================================================');
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
if (canConnect) {
|
|
192
|
+
console.log(' ACCESS KEY ' + (configKeyId ?? 'unknown') + ' DOES NOT HAVE IAM PERMISSIONS');
|
|
193
|
+
console.log(' ============================================================');
|
|
194
|
+
console.log(' Logged in as: ' + identity);
|
|
195
|
+
console.log(' This IAM user does not have permission to create IAM users.');
|
|
196
|
+
console.log(' The access_key_id in stack.yml needs to belong to an admin user.');
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.log(' CANNOT CONNECT TO AWS WITH ACCESS KEY ' + (configKeyId ?? 'unknown'));
|
|
200
|
+
console.log(' ============================================================');
|
|
201
|
+
console.log(' The access_key_id in stack.yml could not authenticate.');
|
|
202
|
+
console.log(' Check that this key exists and has not been deactivated in AWS.');
|
|
203
|
+
}
|
|
193
204
|
console.log(' ============================================================');
|
|
194
205
|
console.log('');
|
|
195
206
|
// Check if vault has credentials we can swap to
|
|
@@ -210,7 +221,7 @@ async function ensureIamAccess(config, region) {
|
|
|
210
221
|
}
|
|
211
222
|
}
|
|
212
223
|
if (vaultHasCreds) {
|
|
213
|
-
const swap = await confirm(' Load
|
|
224
|
+
const swap = await confirm(' Load credentials from Ansible Vault instead?', true);
|
|
214
225
|
if (swap) {
|
|
215
226
|
try {
|
|
216
227
|
const { AnsibleVaultSecrets } = await Promise.resolve().then(() => __importStar(require('../../../../utils/ansible-vault-secrets.js')));
|
|
@@ -218,13 +229,13 @@ async function ensureIamAccess(config, region) {
|
|
|
218
229
|
vault_path: config.ansible.vault_path,
|
|
219
230
|
vault_password_file: config.ansible.vault_password_file,
|
|
220
231
|
});
|
|
221
|
-
const
|
|
232
|
+
const vaultKeyId = await vault.getSecret('AWS_ACCESS_KEY_ID');
|
|
222
233
|
const secretKey = await vault.getSecret('AWS_SECRET_ACCESS_KEY');
|
|
223
|
-
if (!
|
|
234
|
+
if (!vaultKeyId || !secretKey) {
|
|
224
235
|
console.log(' Failed to read credentials from vault.');
|
|
225
236
|
return false;
|
|
226
237
|
}
|
|
227
|
-
(0, aws_helpers_js_1.writeAwsCredentials)(
|
|
238
|
+
(0, aws_helpers_js_1.writeAwsCredentials)(vaultKeyId, secretKey, region);
|
|
228
239
|
(0, aws_helpers_js_1.clearClientCache)(); // Pick up new credentials
|
|
229
240
|
const newArn = await (0, aws_helpers_js_1.getCallerArn)(region);
|
|
230
241
|
console.log(' [OK] Switched to: ' + (newArn ?? 'unknown'));
|
|
@@ -233,9 +244,9 @@ async function ensureIamAccess(config, region) {
|
|
|
233
244
|
return true;
|
|
234
245
|
}
|
|
235
246
|
console.log('');
|
|
236
|
-
console.log('
|
|
247
|
+
console.log(' Vault credentials (' + vaultKeyId + ') also lack IAM permissions.');
|
|
237
248
|
console.log('');
|
|
238
|
-
console.log(' To fix,
|
|
249
|
+
console.log(' To fix, store admin credentials in the vault:');
|
|
239
250
|
console.log(' npx stack deploy --secrets set AWS_ACCESS_KEY_ID');
|
|
240
251
|
console.log(' npx stack deploy --secrets set AWS_SECRET_ACCESS_KEY');
|
|
241
252
|
console.log(' Then run: npx stack fix');
|
|
@@ -270,37 +281,37 @@ async function ensureIamAccess(config, region) {
|
|
|
270
281
|
}
|
|
271
282
|
exports.iamFixes = [
|
|
272
283
|
{
|
|
273
|
-
id: 'aws-iam-
|
|
284
|
+
id: 'aws-iam-admin-user-missing',
|
|
274
285
|
stage: 'secrets',
|
|
275
286
|
severity: 'warning',
|
|
276
|
-
description: '👤 IAM
|
|
287
|
+
description: '👤 IAM admin user not created (recess for dev workflows)',
|
|
277
288
|
scan: async (config) => {
|
|
278
289
|
if (!(0, aws_helpers_js_1.isAwsConfigured)(config))
|
|
279
290
|
return false;
|
|
280
291
|
const { region } = (0, aws_helpers_js_1.getAwsConfig)(config);
|
|
281
292
|
const projectName = (0, aws_helpers_js_1.getProjectName)(config);
|
|
282
|
-
return !(await (0, aws_helpers_js_1.findIamUser)('factiii-' + projectName + '-
|
|
293
|
+
return !(await (0, aws_helpers_js_1.findIamUser)('factiii-' + projectName + '-admin', region));
|
|
283
294
|
},
|
|
284
295
|
fix: async (config) => {
|
|
285
296
|
const { region } = (0, aws_helpers_js_1.getAwsConfig)(config);
|
|
286
297
|
const projectName = (0, aws_helpers_js_1.getProjectName)(config);
|
|
287
|
-
const userName = 'factiii-' + projectName + '-
|
|
298
|
+
const userName = 'factiii-' + projectName + '-admin';
|
|
288
299
|
if (!(await ensureIamAccess(config, region)))
|
|
289
300
|
return false;
|
|
290
301
|
console.log('');
|
|
291
302
|
console.log(' ============================================================');
|
|
292
|
-
console.log(' CREATE IAM
|
|
303
|
+
console.log(' CREATE IAM ADMIN USER');
|
|
293
304
|
console.log(' ============================================================');
|
|
294
|
-
console.log(' Will create IAM user "' + userName + '" with
|
|
305
|
+
console.log(' Will create IAM user "' + userName + '" with admin policy:');
|
|
295
306
|
console.log(' - ECR: pull images, list repositories');
|
|
296
307
|
console.log(' - S3: read objects from project bucket');
|
|
297
308
|
console.log(' - EC2/RDS: describe (view) resources');
|
|
298
309
|
console.log('');
|
|
299
|
-
console.log(' This user is for local development and CI
|
|
310
|
+
console.log(' This user is for local development and CI access.');
|
|
300
311
|
console.log(' ============================================================');
|
|
301
312
|
console.log('');
|
|
302
313
|
const { confirm } = await Promise.resolve().then(() => __importStar(require('../../../../utils/secret-prompts.js')));
|
|
303
|
-
const proceed = await confirm(' Create IAM
|
|
314
|
+
const proceed = await confirm(' Create IAM admin user "' + userName + '"?', true);
|
|
304
315
|
if (!proceed) {
|
|
305
316
|
console.log(' [--] Skipped — you can create it later with: npx stack fix --secrets');
|
|
306
317
|
return true;
|
|
@@ -320,16 +331,16 @@ exports.iamFixes = [
|
|
|
320
331
|
const policy = getDevPolicy(projectName, region, accountId);
|
|
321
332
|
await iam.send(new aws_helpers_js_1.PutUserPolicyCommand({
|
|
322
333
|
UserName: userName,
|
|
323
|
-
PolicyName: 'factiii-' + projectName + '-
|
|
334
|
+
PolicyName: 'factiii-' + projectName + '-admin-policy',
|
|
324
335
|
PolicyDocument: policy,
|
|
325
336
|
}));
|
|
326
|
-
console.log(' Attached
|
|
337
|
+
console.log(' Attached admin policy (ECR, S3, EC2, RDS)');
|
|
327
338
|
// Create access key
|
|
328
339
|
const keyResult = await iam.send(new aws_helpers_js_1.CreateAccessKeyCommand({ UserName: userName }));
|
|
329
340
|
const accessKeyId = keyResult.AccessKey?.AccessKeyId;
|
|
330
341
|
const secretKey = keyResult.AccessKey?.SecretAccessKey;
|
|
331
342
|
console.log('');
|
|
332
|
-
console.log('
|
|
343
|
+
console.log(' Admin credentials (save these!):');
|
|
333
344
|
console.log(' Access Key ID: ' + accessKeyId);
|
|
334
345
|
console.log(' Secret Access Key: ' + secretKey);
|
|
335
346
|
console.log('');
|
|
@@ -337,26 +348,26 @@ exports.iamFixes = [
|
|
|
337
348
|
return true;
|
|
338
349
|
}
|
|
339
350
|
catch (e) {
|
|
340
|
-
console.log(' Failed to create
|
|
351
|
+
console.log(' Failed to create admin IAM user: ' + (e instanceof Error ? e.message : String(e)));
|
|
341
352
|
return false;
|
|
342
353
|
}
|
|
343
354
|
},
|
|
344
355
|
manualFix: [
|
|
345
356
|
'============================================================',
|
|
346
|
-
'IAM
|
|
357
|
+
'IAM ADMIN USER SETUP',
|
|
347
358
|
'============================================================',
|
|
348
359
|
'',
|
|
349
|
-
' This creates
|
|
360
|
+
' This creates an admin IAM user for local dev and CI.',
|
|
350
361
|
' Permissions: ECR pull, S3 read, EC2/RDS describe.',
|
|
351
362
|
'',
|
|
352
363
|
' Auto-fix: npx stack fix --secrets (creates user + policy + access key)',
|
|
353
364
|
'',
|
|
354
365
|
' Or manually in AWS Console:',
|
|
355
366
|
' 1. Go to IAM > Users > Create user',
|
|
356
|
-
' 2. Name: factiii-{project}-
|
|
357
|
-
' 3. Attach inline policy with
|
|
367
|
+
' 2. Name: factiii-{project}-admin',
|
|
368
|
+
' 3. Attach inline policy with ECR, S3, EC2, RDS access',
|
|
358
369
|
' 4. Create access key: User > Security credentials > Create access key > CLI',
|
|
359
|
-
' 5. Store secret in vault: npx stack deploy --secrets set
|
|
370
|
+
' 5. Store secret in vault: npx stack deploy --secrets set AWS_SECRET_ACCESS_KEY',
|
|
360
371
|
'',
|
|
361
372
|
'============================================================',
|
|
362
373
|
].join('\n'),
|