@codyswann/lisa 2.226.0 → 2.226.2
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/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/scripts/install-remote-agent-aws.mjs +109 -10
- package/plugins/lisa/scripts/remote-agent-aws-setup.sh +79 -9
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/install-remote-agent-aws.mjs +109 -10
- package/plugins/lisa-agy/scripts/remote-agent-aws-setup.sh +79 -9
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/scripts/install-remote-agent-aws.mjs +109 -10
- package/plugins/lisa-copilot/scripts/remote-agent-aws-setup.sh +79 -9
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/scripts/install-remote-agent-aws.mjs +109 -10
- package/plugins/lisa-cursor/scripts/remote-agent-aws-setup.sh +79 -9
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/scripts/install-remote-agent-aws.mjs +109 -10
- package/plugins/src/base/scripts/remote-agent-aws-setup.sh +79 -9
package/package.json
CHANGED
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"form-data": ">=4.0.6"
|
|
103
103
|
},
|
|
104
104
|
"name": "@codyswann/lisa",
|
|
105
|
-
"version": "2.226.
|
|
105
|
+
"version": "2.226.2",
|
|
106
106
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
107
107
|
"main": "dist/index.js",
|
|
108
108
|
"exports": {
|
|
@@ -98,9 +98,17 @@ function installCopilotAdapter(project) {
|
|
|
98
98
|
);
|
|
99
99
|
mkdirSync(path.dirname(workflowPath), { recursive: true });
|
|
100
100
|
const marker = "Lisa remote AWS bootstrap";
|
|
101
|
+
const secretEnvironment =
|
|
102
|
+
" LISA_AWS_BOOTSTRAP_JSON: ${{ secrets.LISA_AWS_BOOTSTRAP_JSON }}";
|
|
103
|
+
const checkoutStep = [
|
|
104
|
+
" - uses: actions/checkout@v4",
|
|
105
|
+
" with:",
|
|
106
|
+
" persist-credentials: false",
|
|
107
|
+
].join("\n");
|
|
101
108
|
const step = [
|
|
102
109
|
` - name: ${marker}`,
|
|
103
110
|
" env:",
|
|
111
|
+
secretEnvironment,
|
|
104
112
|
" LISA_REMOTE_AGENT: copilot",
|
|
105
113
|
` run: ${INSTALL_COMMAND}`,
|
|
106
114
|
].join("\n");
|
|
@@ -118,7 +126,7 @@ function installCopilotAdapter(project) {
|
|
|
118
126
|
" permissions:",
|
|
119
127
|
" contents: read",
|
|
120
128
|
" steps:",
|
|
121
|
-
|
|
129
|
+
checkoutStep,
|
|
122
130
|
step,
|
|
123
131
|
"",
|
|
124
132
|
].join("\n")
|
|
@@ -127,7 +135,6 @@ function installCopilotAdapter(project) {
|
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
const current = readFileSync(workflowPath, "utf8");
|
|
130
|
-
if (current.includes(marker)) return path.relative(project, workflowPath);
|
|
131
138
|
const lines = current.split("\n");
|
|
132
139
|
const jobIndex = lines.findIndex(line =>
|
|
133
140
|
/^\s{2}copilot-setup-steps:\s*$/.test(line)
|
|
@@ -146,10 +153,95 @@ function installCopilotAdapter(project) {
|
|
|
146
153
|
"Existing copilot-setup-steps.yml must contain jobs.copilot-setup-steps.steps before Lisa can merge the AWS bootstrap step"
|
|
147
154
|
);
|
|
148
155
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
|
|
157
|
+
let changed = false;
|
|
158
|
+
const checkoutIndex = lines.findIndex(
|
|
159
|
+
(line, index) =>
|
|
160
|
+
index > stepsIndex &&
|
|
161
|
+
(jobEndIndex < 0 || index < jobEndIndex) &&
|
|
162
|
+
/^\s{6}-\s+uses:\s+actions\/checkout@/.test(line)
|
|
163
|
+
);
|
|
164
|
+
if (checkoutIndex >= 0) {
|
|
165
|
+
const nextStepIndex = lines.findIndex(
|
|
166
|
+
(line, index) => index > checkoutIndex && /^\s{6}-\s/.test(line)
|
|
167
|
+
);
|
|
168
|
+
const checkoutEnd = nextStepIndex < 0 ? lines.length : nextStepIndex;
|
|
169
|
+
const withIndex = lines.findIndex(
|
|
170
|
+
(line, index) =>
|
|
171
|
+
index > checkoutIndex &&
|
|
172
|
+
index < checkoutEnd &&
|
|
173
|
+
/^\s{8}with:\s*$/.test(line)
|
|
174
|
+
);
|
|
175
|
+
const persistenceIndex = lines.findIndex(
|
|
176
|
+
(line, index) =>
|
|
177
|
+
index > checkoutIndex &&
|
|
178
|
+
index < checkoutEnd &&
|
|
179
|
+
/^\s{10}persist-credentials:\s*/.test(line)
|
|
180
|
+
);
|
|
181
|
+
if (persistenceIndex >= 0) {
|
|
182
|
+
if (lines[persistenceIndex] !== " persist-credentials: false") {
|
|
183
|
+
lines[persistenceIndex] = " persist-credentials: false";
|
|
184
|
+
changed = true;
|
|
185
|
+
}
|
|
186
|
+
} else if (withIndex >= 0) {
|
|
187
|
+
lines.splice(withIndex + 1, 0, " persist-credentials: false");
|
|
188
|
+
changed = true;
|
|
189
|
+
} else {
|
|
190
|
+
lines.splice(
|
|
191
|
+
checkoutIndex + 1,
|
|
192
|
+
0,
|
|
193
|
+
" with:",
|
|
194
|
+
" persist-credentials: false"
|
|
195
|
+
);
|
|
196
|
+
changed = true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const markerIndex = lines.findIndex(line => line.includes(marker));
|
|
201
|
+
if (markerIndex >= 0) {
|
|
202
|
+
const nextStepIndex = lines.findIndex(
|
|
203
|
+
(line, index) => index > markerIndex && /^\s{6}-\s/.test(line)
|
|
204
|
+
);
|
|
205
|
+
const managedStepEnd = nextStepIndex < 0 ? lines.length : nextStepIndex;
|
|
206
|
+
const managedStep = lines.slice(markerIndex, managedStepEnd);
|
|
207
|
+
if (!managedStep.includes(secretEnvironment)) {
|
|
208
|
+
const environmentIndex = lines.findIndex(
|
|
209
|
+
(line, index) =>
|
|
210
|
+
index > markerIndex &&
|
|
211
|
+
index < managedStepEnd &&
|
|
212
|
+
/^\s{8}env:\s*$/.test(line)
|
|
213
|
+
);
|
|
214
|
+
if (environmentIndex < 0) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
"Existing Lisa remote AWS bootstrap step must contain an env block"
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
lines.splice(environmentIndex + 1, 0, secretEnvironment);
|
|
220
|
+
changed = true;
|
|
221
|
+
}
|
|
222
|
+
if (checkoutIndex < 0) {
|
|
223
|
+
lines.splice(stepsIndex + 1, 0, ...checkoutStep.split("\n"));
|
|
224
|
+
changed = true;
|
|
225
|
+
}
|
|
226
|
+
if (changed) writeFileSync(workflowPath, lines.join("\n"));
|
|
227
|
+
return path.relative(project, workflowPath);
|
|
228
|
+
}
|
|
229
|
+
const additions = checkoutIndex >= 0 ? [step] : [checkoutStep, step];
|
|
230
|
+
const nextCheckoutStepIndex = lines.findIndex(
|
|
231
|
+
(line, index) => index > checkoutIndex && /^\s{6}-\s/.test(line)
|
|
232
|
+
);
|
|
233
|
+
const updatedJobEndIndex = lines.findIndex(
|
|
234
|
+
(line, index) => index > jobIndex && /^\s{2}\S[^:]*:\s*$/.test(line)
|
|
235
|
+
);
|
|
236
|
+
const additionIndex =
|
|
237
|
+
checkoutIndex < 0
|
|
238
|
+
? stepsIndex + 1
|
|
239
|
+
: nextCheckoutStepIndex >= 0
|
|
240
|
+
? nextCheckoutStepIndex
|
|
241
|
+
: updatedJobEndIndex >= 0
|
|
242
|
+
? updatedJobEndIndex
|
|
243
|
+
: lines.length;
|
|
244
|
+
lines.splice(additionIndex, 0, ...additions);
|
|
153
245
|
writeFileSync(workflowPath, lines.join("\n"));
|
|
154
246
|
return path.relative(project, workflowPath);
|
|
155
247
|
}
|
|
@@ -161,13 +253,20 @@ function writeGuide(project, platform, secretName) {
|
|
|
161
253
|
guidePath,
|
|
162
254
|
`# Remote coding-agent AWS access
|
|
163
255
|
|
|
256
|
+
## Context
|
|
257
|
+
|
|
164
258
|
This repository uses Lisa's vendor-neutral AWS bootstrap. Configure the complete
|
|
165
259
|
Secrets Manager SecretString as the secret \`LISA_AWS_BOOTSTRAP_JSON\`; do not
|
|
166
260
|
split it into \`AWS_ACCESS_KEY_ID\` and \`AWS_SECRET_ACCESS_KEY\` environment
|
|
167
261
|
variables. The setup script writes a named source profile whose only permission
|
|
168
262
|
is \`sts:AssumeRole\`, then creates automatically refreshed role profiles.
|
|
169
263
|
|
|
170
|
-
##
|
|
264
|
+
## Goal
|
|
265
|
+
|
|
266
|
+
Give supported remote coding agents renewable, least-privilege AWS CLI access
|
|
267
|
+
without borrowing a developer identity or granting direct production repair.
|
|
268
|
+
|
|
269
|
+
## Changes
|
|
171
270
|
|
|
172
271
|
| Runtime | Setup |
|
|
173
272
|
|---|---|
|
|
@@ -180,7 +279,7 @@ is \`sts:AssumeRole\`, then creates automatically refreshed role profiles.
|
|
|
180
279
|
|
|
181
280
|
Generated for: \`${platform}\`.
|
|
182
281
|
|
|
183
|
-
##
|
|
282
|
+
## Implementation
|
|
184
283
|
|
|
185
284
|
The bootstrap bundle defines the available profile names. \`dev\` is the
|
|
186
285
|
default when present. Select another account explicitly, for example:
|
|
@@ -193,8 +292,6 @@ aws --profile production cloudformation describe-stacks
|
|
|
193
292
|
Production and shared profiles are observer-only. Production repair remains a
|
|
194
293
|
human-driven local-workstation operation.
|
|
195
294
|
|
|
196
|
-
## Administrator rollout
|
|
197
|
-
|
|
198
295
|
After the infrastructure pipeline deploys the remote-agent IAM stacks, retrieve
|
|
199
296
|
the complete bundle from the shared account. Do not extract or distribute its
|
|
200
297
|
individual access-key fields:
|
|
@@ -210,6 +307,8 @@ Store that exact output as the masked secret \`LISA_AWS_BOOTSTRAP_JSON\` on each
|
|
|
210
307
|
remote-agent platform. Run the setup command and require its live
|
|
211
308
|
\`sts:GetCallerIdentity\` check to pass before considering the platform ready.
|
|
212
309
|
|
|
310
|
+
## Notes
|
|
311
|
+
|
|
213
312
|
Rotate by replacing the bootstrap IAM access key through infrastructure and
|
|
214
313
|
then updating this one secret on every configured platform. Disabling or
|
|
215
314
|
deleting the bootstrap user immediately prevents new role sessions.
|
|
@@ -14,6 +14,7 @@ set -euo pipefail
|
|
|
14
14
|
umask 077
|
|
15
15
|
|
|
16
16
|
BOOTSTRAP_PROFILE="lisa-remote-agent-bootstrap"
|
|
17
|
+
AWS_CLI_VERSION="2.36.2"
|
|
17
18
|
|
|
18
19
|
fail() {
|
|
19
20
|
echo "remote-agent-aws-setup: $*" >&2
|
|
@@ -35,17 +36,17 @@ as_root() {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
install_base_tools() {
|
|
38
|
-
need curl && need unzip && need jq && return 0
|
|
39
|
+
need curl && need unzip && need jq && need gpg && return 0
|
|
39
40
|
|
|
40
41
|
if need apt-get; then
|
|
41
42
|
as_root apt-get update -y
|
|
42
|
-
as_root apt-get install -y curl unzip jq
|
|
43
|
+
as_root apt-get install -y curl unzip jq gnupg
|
|
43
44
|
elif need dnf; then
|
|
44
|
-
as_root dnf install -y curl unzip jq
|
|
45
|
+
as_root dnf install -y curl unzip jq gnupg2
|
|
45
46
|
elif need yum; then
|
|
46
|
-
as_root yum install -y curl unzip jq
|
|
47
|
+
as_root yum install -y curl unzip jq gnupg2
|
|
47
48
|
else
|
|
48
|
-
fail "install curl, unzip, and
|
|
49
|
+
fail "install curl, unzip, jq, and gpg in the remote image before running this script"
|
|
49
50
|
fi
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -62,9 +63,49 @@ install_aws_cli() {
|
|
|
62
63
|
|
|
63
64
|
temporary_directory="$(mktemp -d)"
|
|
64
65
|
trap 'rm -rf "${temporary_directory:-}"' EXIT
|
|
66
|
+
local installer_url
|
|
67
|
+
installer_url="https://awscli.amazonaws.com/awscli-exe-linux-${aws_architecture}-${AWS_CLI_VERSION}.zip"
|
|
65
68
|
curl -fsSL \
|
|
66
|
-
"
|
|
69
|
+
"$installer_url" \
|
|
67
70
|
-o "$temporary_directory/awscliv2.zip"
|
|
71
|
+
curl -fsSL "$installer_url.sig" -o "$temporary_directory/awscliv2.sig"
|
|
72
|
+
cat >"$temporary_directory/aws-cli-public-key.asc" <<'AWS_CLI_PUBLIC_KEY'
|
|
73
|
+
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
74
|
+
|
|
75
|
+
mQINBF2Cr7UBEADJZHcgusOJl7ENSyumXh85z0TRV0xJorM2B/JL0kHOyigQluUG
|
|
76
|
+
ZMLhENaG0bYatdrKP+3H91lvK050pXwnO/R7fB/FSTouki4ciIx5OuLlnJZIxSzx
|
|
77
|
+
PqGl0mkxImLNbGWoi6Lto0LYxqHN2iQtzlwTVmq9733zd3XfcXrZ3+LblHAgEt5G
|
|
78
|
+
TfNxEKJ8soPLyWmwDH6HWCnjZ/aIQRBTIQ05uVeEoYxSh6wOai7ss/KveoSNBbYz
|
|
79
|
+
gbdzoqI2Y8cgH2nbfgp3DSasaLZEdCSsIsK1u05CinE7k2qZ7KgKAUIcT/cR/grk
|
|
80
|
+
C6VwsnDU0OUCideXcQ8WeHutqvgZH1JgKDbznoIzeQHJD238GEu+eKhRHcz8/jeG
|
|
81
|
+
94zkcgJOz3KbZGYMiTh277Fvj9zzvZsbMBCedV1BTg3TqgvdX4bdkhf5cH+7NtWO
|
|
82
|
+
lrFj6UwAsGukBTAOxC0l/dnSmZhJ7Z1KmEWilro/gOrjtOxqRQutlIqG22TaqoPG
|
|
83
|
+
fYVN+en3Zwbt97kcgZDwqbuykNt64oZWc4XKCa3mprEGC3IbJTBFqglXmZ7l9ywG
|
|
84
|
+
EEUJYOlb2XrSuPWml39beWdKM8kzr1OjnlOm6+lpTRCBfo0wa9F8YZRhHPAkwKkX
|
|
85
|
+
XDeOGpWRj4ohOx0d2GWkyV5xyN14p2tQOCdOODmz80yUTgRpPVQUtOEhXQARAQAB
|
|
86
|
+
tCFBV1MgQ0xJIFRlYW0gPGF3cy1jbGlAYW1hem9uLmNvbT6JAlQEEwEIAD4CGwMF
|
|
87
|
+
CwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQT7Xbd/1cEYuAURraimMQrMRnJHXAUC
|
|
88
|
+
akV0ygUJDqP4lQAKCRCmMQrMRnJHXFHjD/9eyZLYcKuQOlLvtqSDtUBiEZf6ZZjM
|
|
89
|
+
i3ygYH8rJNtuToUH+HvSpe819urJCquXhDrlK6N+aqW0hCLtNABJG/vsafIgvIYJ
|
|
90
|
+
hSGgpgtNnQyMV1jViRWqPjbouw8OkYKBThUfT1i2Y+wn58ifs6ODBCmTexWtXspA
|
|
91
|
+
Si+Gt49xDOW0APmbOPnI+a4HJW6tVEo6MWS0WjzpiBayR3d1A4pt4YrPfSdDgpLo
|
|
92
|
+
h2SLQqlRqvvVZJaWBjhkErNFpfsBA06sDcPEOb0G8LBUbR4WOcdvhe5LubJbZuxC
|
|
93
|
+
AG9kNPCVeQP1ixwjgjXKysaxeQ6rv0VzIQgRp6tLVLWhy6AKDNvLjFSsmXZ1Wl08
|
|
94
|
+
Y/RlOHXlzLuQMRE6sR1wOdRxc9TsrNWTGiBK65cvSWOy03JeBkQQ8pesqltiyxI9
|
|
95
|
+
U21kkgiXtTSKNGfKK8pO27D81YANhRqPK7iTp6kuFiY2WtOg90KTMNlIT+Ff85Y2
|
|
96
|
+
b1rHj6Z0SrCkJujhWk3IBPic/wJgz01LEc/OAdUPlby90RJZcIBhSlWhT7mXnXIO
|
|
97
|
+
c0HWlNQrns2s3CTyYwZSiSlYe9ApeLwhjDo8NhbFuCAy61l6O5UsR4AfZxx/rGKv
|
|
98
|
+
2wFb1/RN/P4gNe6vmxZAPjR0AQcwD3tc2McimOLr/22kmPz8IH3I0X7WoSFr0Biz
|
|
99
|
+
E91G7bb0hOb/cA==
|
|
100
|
+
=knv7
|
|
101
|
+
-----END PGP PUBLIC KEY BLOCK-----
|
|
102
|
+
AWS_CLI_PUBLIC_KEY
|
|
103
|
+
mkdir -m 700 "$temporary_directory/gnupg"
|
|
104
|
+
gpg --batch --homedir "$temporary_directory/gnupg" \
|
|
105
|
+
--import "$temporary_directory/aws-cli-public-key.asc" >/dev/null 2>&1
|
|
106
|
+
gpg --batch --homedir "$temporary_directory/gnupg" \
|
|
107
|
+
--verify "$temporary_directory/awscliv2.sig" \
|
|
108
|
+
"$temporary_directory/awscliv2.zip"
|
|
68
109
|
unzip -q "$temporary_directory/awscliv2.zip" -d "$temporary_directory"
|
|
69
110
|
as_root "$temporary_directory/aws/install" \
|
|
70
111
|
--install-dir /usr/local/aws-cli \
|
|
@@ -73,6 +114,27 @@ install_aws_cli() {
|
|
|
73
114
|
trap - EXIT
|
|
74
115
|
}
|
|
75
116
|
|
|
117
|
+
remove_profile_setting() {
|
|
118
|
+
local credentials_file profile setting temporary_file
|
|
119
|
+
credentials_file="$1"
|
|
120
|
+
profile="$2"
|
|
121
|
+
setting="$3"
|
|
122
|
+
[ -f "$credentials_file" ] || return 0
|
|
123
|
+
|
|
124
|
+
temporary_file="$(mktemp "${credentials_file}.XXXXXX")"
|
|
125
|
+
awk -v profile="$profile" -v setting="$setting" '
|
|
126
|
+
/^\[[^]]+\][[:space:]]*$/ {
|
|
127
|
+
section = $0
|
|
128
|
+
sub(/^\[/, "", section)
|
|
129
|
+
sub(/\][[:space:]]*$/, "", section)
|
|
130
|
+
in_profile = section == profile
|
|
131
|
+
}
|
|
132
|
+
!(in_profile && $0 ~ "^[[:space:]]*" setting "[[:space:]]*=") { print }
|
|
133
|
+
' "$credentials_file" >"$temporary_file"
|
|
134
|
+
chmod 600 "$temporary_file"
|
|
135
|
+
mv "$temporary_file" "$credentials_file"
|
|
136
|
+
}
|
|
137
|
+
|
|
76
138
|
sanitize_session_name() {
|
|
77
139
|
local candidate
|
|
78
140
|
candidate="$(printf '%s' "${LISA_REMOTE_AGENT:-remote-agent}" \
|
|
@@ -113,22 +175,30 @@ profiles_json="$(printf '%s' "$bootstrap_json" | jq -c '
|
|
|
113
175
|
end
|
|
114
176
|
')"
|
|
115
177
|
|
|
116
|
-
printf '%s' "$profiles_json" | jq -e '
|
|
178
|
+
printf '%s' "$profiles_json" | jq -e --arg bootstrap_profile "$BOOTSTRAP_PROFILE" '
|
|
117
179
|
type == "object" and length > 0 and
|
|
118
180
|
all(to_entries[];
|
|
181
|
+
(.key != $bootstrap_profile) and
|
|
119
182
|
(.key | test("^[A-Za-z0-9_-]+$")) and
|
|
120
183
|
(.value.roleArn | type == "string" and startswith("arn:aws:iam::")) and
|
|
121
184
|
(.value.region | type == "string" and length > 0)
|
|
122
185
|
)
|
|
123
186
|
' >/dev/null || fail "bootstrap profiles must map safe names to roleArn and region"
|
|
124
187
|
|
|
125
|
-
|
|
188
|
+
credentials_file="${AWS_SHARED_CREDENTIALS_FILE:-$HOME/.aws/credentials}"
|
|
189
|
+
config_file="${AWS_CONFIG_FILE:-$HOME/.aws/config}"
|
|
190
|
+
mkdir -p "$HOME/.aws" "$(dirname "$credentials_file")" "$(dirname "$config_file")"
|
|
126
191
|
chmod 700 "$HOME/.aws"
|
|
127
192
|
|
|
128
193
|
aws configure set aws_access_key_id "$access_key_id" --profile "$BOOTSTRAP_PROFILE"
|
|
129
194
|
aws configure set aws_secret_access_key "$secret_access_key" --profile "$BOOTSTRAP_PROFILE"
|
|
130
195
|
if [ -n "$session_token" ]; then
|
|
131
196
|
aws configure set aws_session_token "$session_token" --profile "$BOOTSTRAP_PROFILE"
|
|
197
|
+
else
|
|
198
|
+
remove_profile_setting \
|
|
199
|
+
"$credentials_file" \
|
|
200
|
+
"$BOOTSTRAP_PROFILE" \
|
|
201
|
+
"aws_session_token"
|
|
132
202
|
fi
|
|
133
203
|
|
|
134
204
|
session_name="$(sanitize_session_name)"
|
|
@@ -153,7 +223,7 @@ aws configure set external_id "$external_id" --profile default
|
|
|
153
223
|
aws configure set role_session_name "$session_name" --profile default
|
|
154
224
|
aws configure set region "$default_region" --profile default
|
|
155
225
|
|
|
156
|
-
chmod 600 "$
|
|
226
|
+
chmod 600 "$credentials_file" "$config_file"
|
|
157
227
|
|
|
158
228
|
if [ "${LISA_AWS_SKIP_VERIFY:-0}" != "1" ]; then
|
|
159
229
|
AWS_PAGER="" aws sts get-caller-identity --profile "$default_profile" >/dev/null
|
|
@@ -98,9 +98,17 @@ function installCopilotAdapter(project) {
|
|
|
98
98
|
);
|
|
99
99
|
mkdirSync(path.dirname(workflowPath), { recursive: true });
|
|
100
100
|
const marker = "Lisa remote AWS bootstrap";
|
|
101
|
+
const secretEnvironment =
|
|
102
|
+
" LISA_AWS_BOOTSTRAP_JSON: ${{ secrets.LISA_AWS_BOOTSTRAP_JSON }}";
|
|
103
|
+
const checkoutStep = [
|
|
104
|
+
" - uses: actions/checkout@v4",
|
|
105
|
+
" with:",
|
|
106
|
+
" persist-credentials: false",
|
|
107
|
+
].join("\n");
|
|
101
108
|
const step = [
|
|
102
109
|
` - name: ${marker}`,
|
|
103
110
|
" env:",
|
|
111
|
+
secretEnvironment,
|
|
104
112
|
" LISA_REMOTE_AGENT: copilot",
|
|
105
113
|
` run: ${INSTALL_COMMAND}`,
|
|
106
114
|
].join("\n");
|
|
@@ -118,7 +126,7 @@ function installCopilotAdapter(project) {
|
|
|
118
126
|
" permissions:",
|
|
119
127
|
" contents: read",
|
|
120
128
|
" steps:",
|
|
121
|
-
|
|
129
|
+
checkoutStep,
|
|
122
130
|
step,
|
|
123
131
|
"",
|
|
124
132
|
].join("\n")
|
|
@@ -127,7 +135,6 @@ function installCopilotAdapter(project) {
|
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
const current = readFileSync(workflowPath, "utf8");
|
|
130
|
-
if (current.includes(marker)) return path.relative(project, workflowPath);
|
|
131
138
|
const lines = current.split("\n");
|
|
132
139
|
const jobIndex = lines.findIndex(line =>
|
|
133
140
|
/^\s{2}copilot-setup-steps:\s*$/.test(line)
|
|
@@ -146,10 +153,95 @@ function installCopilotAdapter(project) {
|
|
|
146
153
|
"Existing copilot-setup-steps.yml must contain jobs.copilot-setup-steps.steps before Lisa can merge the AWS bootstrap step"
|
|
147
154
|
);
|
|
148
155
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
|
|
157
|
+
let changed = false;
|
|
158
|
+
const checkoutIndex = lines.findIndex(
|
|
159
|
+
(line, index) =>
|
|
160
|
+
index > stepsIndex &&
|
|
161
|
+
(jobEndIndex < 0 || index < jobEndIndex) &&
|
|
162
|
+
/^\s{6}-\s+uses:\s+actions\/checkout@/.test(line)
|
|
163
|
+
);
|
|
164
|
+
if (checkoutIndex >= 0) {
|
|
165
|
+
const nextStepIndex = lines.findIndex(
|
|
166
|
+
(line, index) => index > checkoutIndex && /^\s{6}-\s/.test(line)
|
|
167
|
+
);
|
|
168
|
+
const checkoutEnd = nextStepIndex < 0 ? lines.length : nextStepIndex;
|
|
169
|
+
const withIndex = lines.findIndex(
|
|
170
|
+
(line, index) =>
|
|
171
|
+
index > checkoutIndex &&
|
|
172
|
+
index < checkoutEnd &&
|
|
173
|
+
/^\s{8}with:\s*$/.test(line)
|
|
174
|
+
);
|
|
175
|
+
const persistenceIndex = lines.findIndex(
|
|
176
|
+
(line, index) =>
|
|
177
|
+
index > checkoutIndex &&
|
|
178
|
+
index < checkoutEnd &&
|
|
179
|
+
/^\s{10}persist-credentials:\s*/.test(line)
|
|
180
|
+
);
|
|
181
|
+
if (persistenceIndex >= 0) {
|
|
182
|
+
if (lines[persistenceIndex] !== " persist-credentials: false") {
|
|
183
|
+
lines[persistenceIndex] = " persist-credentials: false";
|
|
184
|
+
changed = true;
|
|
185
|
+
}
|
|
186
|
+
} else if (withIndex >= 0) {
|
|
187
|
+
lines.splice(withIndex + 1, 0, " persist-credentials: false");
|
|
188
|
+
changed = true;
|
|
189
|
+
} else {
|
|
190
|
+
lines.splice(
|
|
191
|
+
checkoutIndex + 1,
|
|
192
|
+
0,
|
|
193
|
+
" with:",
|
|
194
|
+
" persist-credentials: false"
|
|
195
|
+
);
|
|
196
|
+
changed = true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const markerIndex = lines.findIndex(line => line.includes(marker));
|
|
201
|
+
if (markerIndex >= 0) {
|
|
202
|
+
const nextStepIndex = lines.findIndex(
|
|
203
|
+
(line, index) => index > markerIndex && /^\s{6}-\s/.test(line)
|
|
204
|
+
);
|
|
205
|
+
const managedStepEnd = nextStepIndex < 0 ? lines.length : nextStepIndex;
|
|
206
|
+
const managedStep = lines.slice(markerIndex, managedStepEnd);
|
|
207
|
+
if (!managedStep.includes(secretEnvironment)) {
|
|
208
|
+
const environmentIndex = lines.findIndex(
|
|
209
|
+
(line, index) =>
|
|
210
|
+
index > markerIndex &&
|
|
211
|
+
index < managedStepEnd &&
|
|
212
|
+
/^\s{8}env:\s*$/.test(line)
|
|
213
|
+
);
|
|
214
|
+
if (environmentIndex < 0) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
"Existing Lisa remote AWS bootstrap step must contain an env block"
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
lines.splice(environmentIndex + 1, 0, secretEnvironment);
|
|
220
|
+
changed = true;
|
|
221
|
+
}
|
|
222
|
+
if (checkoutIndex < 0) {
|
|
223
|
+
lines.splice(stepsIndex + 1, 0, ...checkoutStep.split("\n"));
|
|
224
|
+
changed = true;
|
|
225
|
+
}
|
|
226
|
+
if (changed) writeFileSync(workflowPath, lines.join("\n"));
|
|
227
|
+
return path.relative(project, workflowPath);
|
|
228
|
+
}
|
|
229
|
+
const additions = checkoutIndex >= 0 ? [step] : [checkoutStep, step];
|
|
230
|
+
const nextCheckoutStepIndex = lines.findIndex(
|
|
231
|
+
(line, index) => index > checkoutIndex && /^\s{6}-\s/.test(line)
|
|
232
|
+
);
|
|
233
|
+
const updatedJobEndIndex = lines.findIndex(
|
|
234
|
+
(line, index) => index > jobIndex && /^\s{2}\S[^:]*:\s*$/.test(line)
|
|
235
|
+
);
|
|
236
|
+
const additionIndex =
|
|
237
|
+
checkoutIndex < 0
|
|
238
|
+
? stepsIndex + 1
|
|
239
|
+
: nextCheckoutStepIndex >= 0
|
|
240
|
+
? nextCheckoutStepIndex
|
|
241
|
+
: updatedJobEndIndex >= 0
|
|
242
|
+
? updatedJobEndIndex
|
|
243
|
+
: lines.length;
|
|
244
|
+
lines.splice(additionIndex, 0, ...additions);
|
|
153
245
|
writeFileSync(workflowPath, lines.join("\n"));
|
|
154
246
|
return path.relative(project, workflowPath);
|
|
155
247
|
}
|
|
@@ -161,13 +253,20 @@ function writeGuide(project, platform, secretName) {
|
|
|
161
253
|
guidePath,
|
|
162
254
|
`# Remote coding-agent AWS access
|
|
163
255
|
|
|
256
|
+
## Context
|
|
257
|
+
|
|
164
258
|
This repository uses Lisa's vendor-neutral AWS bootstrap. Configure the complete
|
|
165
259
|
Secrets Manager SecretString as the secret \`LISA_AWS_BOOTSTRAP_JSON\`; do not
|
|
166
260
|
split it into \`AWS_ACCESS_KEY_ID\` and \`AWS_SECRET_ACCESS_KEY\` environment
|
|
167
261
|
variables. The setup script writes a named source profile whose only permission
|
|
168
262
|
is \`sts:AssumeRole\`, then creates automatically refreshed role profiles.
|
|
169
263
|
|
|
170
|
-
##
|
|
264
|
+
## Goal
|
|
265
|
+
|
|
266
|
+
Give supported remote coding agents renewable, least-privilege AWS CLI access
|
|
267
|
+
without borrowing a developer identity or granting direct production repair.
|
|
268
|
+
|
|
269
|
+
## Changes
|
|
171
270
|
|
|
172
271
|
| Runtime | Setup |
|
|
173
272
|
|---|---|
|
|
@@ -180,7 +279,7 @@ is \`sts:AssumeRole\`, then creates automatically refreshed role profiles.
|
|
|
180
279
|
|
|
181
280
|
Generated for: \`${platform}\`.
|
|
182
281
|
|
|
183
|
-
##
|
|
282
|
+
## Implementation
|
|
184
283
|
|
|
185
284
|
The bootstrap bundle defines the available profile names. \`dev\` is the
|
|
186
285
|
default when present. Select another account explicitly, for example:
|
|
@@ -193,8 +292,6 @@ aws --profile production cloudformation describe-stacks
|
|
|
193
292
|
Production and shared profiles are observer-only. Production repair remains a
|
|
194
293
|
human-driven local-workstation operation.
|
|
195
294
|
|
|
196
|
-
## Administrator rollout
|
|
197
|
-
|
|
198
295
|
After the infrastructure pipeline deploys the remote-agent IAM stacks, retrieve
|
|
199
296
|
the complete bundle from the shared account. Do not extract or distribute its
|
|
200
297
|
individual access-key fields:
|
|
@@ -210,6 +307,8 @@ Store that exact output as the masked secret \`LISA_AWS_BOOTSTRAP_JSON\` on each
|
|
|
210
307
|
remote-agent platform. Run the setup command and require its live
|
|
211
308
|
\`sts:GetCallerIdentity\` check to pass before considering the platform ready.
|
|
212
309
|
|
|
310
|
+
## Notes
|
|
311
|
+
|
|
213
312
|
Rotate by replacing the bootstrap IAM access key through infrastructure and
|
|
214
313
|
then updating this one secret on every configured platform. Disabling or
|
|
215
314
|
deleting the bootstrap user immediately prevents new role sessions.
|