Skip to content

Commit 5906b1a

Browse files
authored
Pass untrusted input via env vars (#21)
1 parent dbc2fc3 commit 5906b1a

File tree

1 file changed

+76
-38
lines changed

1 file changed

+76
-38
lines changed

.github/workflows/run-terraform.yml

Lines changed: 76 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,13 @@ jobs:
115115
# Checks that all Terraform configuration files adhere to a canonical format
116116
- name: Terraform Format
117117
id: format
118+
env:
119+
WORKING_DIRECTORY: ${{ inputs.working_directory }}
118120
run: |
119121
echo 'Run format check' | tee -a $GITHUB_STEP_SUMMARY
120122
terraform fmt -check -no-color || { echo '
121123
FAILURE! The above files are not properly formatted.
122-
Run `terraform fmt` in ${{ inputs.working_directory }}, commit the changed files and push to fix the issue' | tee -a $GITHUB_STEP_SUMMARY ; exit 1; }
124+
Run `terraform fmt` in $WORKING_DIRECTORY, commit the changed files and push to fix the issue' | tee -a $GITHUB_STEP_SUMMARY ; exit 1; }
123125
124126
terraform_plan:
125127
name: Terraform Plan
@@ -170,7 +172,9 @@ jobs:
170172

171173
- name: Authenticate with Kubernetes
172174
if: inputs.kubernetes_cluster != ''
173-
run: gcloud container hub memberships get-credentials ${{ inputs.kubernetes_cluster }} --verbosity debug
175+
env:
176+
KUBERNETES_CLUSTER: ${{ inputs.kubernetes_cluster }}
177+
run: gcloud container hub memberships get-credentials $KUBERNETES_CLUSTER --verbosity debug
174178

175179
- name: Authenticate with Vault
176180
if: inputs.vault_role != ''
@@ -188,9 +192,11 @@ jobs:
188192

189193
- name: Terraform Plan
190194
id: plan
195+
env:
196+
ENV: ${{ inputs.environment }}
191197
run: |
192198
# 1. Run and send stdout to build log as usual
193-
terraform plan ${{ inputs.terraform_options }} -input=false -no-color -detailed-exitcode -out=plan-${{ inputs.environment }}.tfplan | tee output.txt
199+
terraform plan ${{ inputs.terraform_options }} -input=false -no-color -detailed-exitcode -out=plan-$ENV.tfplan | tee output.txt
194200
195201
# 2. Remove some github commands and fluff
196202
STDOUT="$(sed '/^::/d' output.txt | grep -v 'Refreshing state...')"
@@ -201,19 +207,13 @@ jobs:
201207
${STDOUT}
202208
\`\`\`
203209
EOF
204-
205-
# 4. Sanitize string for when it is injected into js template string later
206-
# Sanitize ` into \`
207-
STDOUT="${STDOUT//\`/'\`'}"
208-
# Sanitize ${} into \${}
209-
STDOUT="${STDOUT//\${/'\${'}"
210-
211-
# 5. Serialize into single-line string for transfer using outputs that only support single line
210+
211+
# 4. Serialize into single-line string for transfer using outputs that only support single line
212212
STDOUT="${STDOUT//'%'/'%25'}"
213213
STDOUT="${STDOUT//$'\n'/'%0A'}"
214214
STDOUT="${STDOUT//$'\r'/'%0D'}"
215215
216-
# 6. Write output
216+
# 5. Write output
217217
echo "stdout=$STDOUT" >> $GITHUB_OUTPUT
218218
219219
- name: Upload Plan Artifact
@@ -237,6 +237,18 @@ jobs:
237237
steps:
238238
- name: Update Pull Request
239239
uses: actions/github-script@v6
240+
env:
241+
# Passing via env vars to sanitize untrusted inputs
242+
ENV: ${{ inputs.environment }}
243+
PLAN_EXITCODE: ${{ needs.terraform_plan.outputs.plan_exitcode }}
244+
DEPLOY_ON: ${{ inputs.deploy_on }}
245+
VALIDATE_OUTPUT: ${{ needs.terraform_check.outputs.validate_stdout }}
246+
PLAN_OUTPUT: ${{ needs.terraform_plan.outputs.plan_stdout }}
247+
FORMAT_OUTCOME: ${{ needs.terraform_check.outputs.format_outcome }}
248+
INIT_OUTCOME: ${{ needs.terraform_plan.outputs.init_outcome }}
249+
VALIDATE_OUTCOME: ${{ needs.terraform_check.outputs.validate_outcome }}
250+
PLAN_OUTCOME: ${{ needs.terraform_plan.outputs.plan_outcome }}
251+
WORKING_DIRECTORY: ${{ inputs.working_directory }}
240252
with:
241253
github-token: ${{ secrets.GITHUB_TOKEN }}
242254
script: |
@@ -246,6 +258,19 @@ jobs:
246258
repo: context.repo.repo
247259
});
248260
261+
const {
262+
ENV,
263+
PLAN_EXITCODE,
264+
DEPLOY_ON,
265+
VALIDATE_OUTPUT,
266+
PLAN_OUTPUT,
267+
FORMAT_OUTCOME,
268+
INIT_OUTCOME,
269+
VALIDATE_OUTCOME,
270+
PLAN_OUTCOME,
271+
WORKING_DIRECTORY,
272+
} = process.env;
273+
249274
/* Body is in the format of
250275
* <!-- @run-terraform -->
251276
* <!-- @run-terraform:start:jobid -->
@@ -256,47 +281,47 @@ jobs:
256281
const comment = comments.find(({ body }) => body.startsWith(bodyStartMarker));
257282
const id = comment?.id;
258283
let commentBody = comment?.body ?? bodyStartMarker;
259-
const bodyHasJobInfo = commentBody.includes('<!-- @run-terraform:start:${{ inputs.environment }} -->');
284+
const bodyHasJobInfo = commentBody.includes(`<!-- @run-terraform:start:${ENV} -->`);
260285
261-
const exitcode = '${{ needs.terraform_plan.outputs.plan_exitcode }}';
286+
const exitcode = PLAN_EXITCODE;
262287
const action = {
263288
0: 'No changes detected. Will not run Terraform apply job',
264289
1: 'An error occured! Will not run Terraform apply job',
265-
2: 'Changes detected. Will run Terraform apply job on merge to ${{ inputs.deploy_on }}'
290+
2: `Changes detected. Will run Terraform apply job on merge to ${DEPLOY_ON}`
266291
}[exitcode] ?? 'Terraform gave an unknown exit code, I don\'t know what happens next!';
267292
268-
const jobBody = `<!-- @run-terraform:start:${{ inputs.environment }} -->
269-
## Results for ${{ inputs.environment }} ${exitcode === '2' ? '– ❗ `CHANGED` ❗' : ''}
270-
#### Terraform Format and Style 🖌 \`${{ needs.terraform_check.outputs.format_outcome }}\`
271-
#### Terraform Initialization ⚙️ \`${{ needs.terraform_plan.outputs.init_outcome }}\`
272-
#### Terraform Validation 🤖 \`${{ needs.terraform_check.outputs.validate_outcome }}\`
293+
const jobBody = `<!-- @run-terraform:start:${ENV} -->
294+
## Results for ${ENV} ${exitcode === '2' ? '– ❗ `CHANGED` ❗' : ''}
295+
#### Terraform Format and Style 🖌 \`${FORMAT_OUTCOME}\`
296+
#### Terraform Initialization ⚙️ \`${INIT_OUTCOME}\`
297+
#### Terraform Validation 🤖 \`${VALIDATE_OUTCOME}\`
273298
<details><summary>Validation Output</summary>
274299
275300
\`\`\`\n
276-
${{ needs.terraform_check.outputs.validate_stdout }}
301+
${VALIDATE_OUTPUT}
277302
\`\`\`
278303
279304
</details>
280305
281-
#### Terraform Plan 📖 \`${{ needs.terraform_plan.outputs.plan_outcome }}\`
306+
#### Terraform Plan 📖 \`${PLAN_OUTCOME}\`
282307
283308
<details><summary>Show Plan</summary>
284309
285310
\`\`\`\n
286-
${{ needs.terraform_plan.outputs.plan_stdout }}
311+
${PLAN_OUTPUT}
287312
\`\`\`
288313
289314
</details>
290315
291316
#### Next action 🚀
292317
${action}
293318
294-
*Pusher: @${{ github.actor }}, Working Directory: \`${{ inputs.working_directory }}\`, Commit: ${{ github.sha }}, Generated at: \`${new Date().toLocaleString('nb')}\`*
295-
<!-- @run-terraform:end:${{ inputs.environment }} -->`;
319+
*Pusher: @${{ github.actor }}, Working Directory: \`${WORKING_DIRECTORY}\`, Commit: ${{ github.sha }}, Generated at: \`${new Date().toLocaleString('nb')}\`*
320+
<!-- @run-terraform:end:${ENV} -->`;
296321
297322
if (bodyHasJobInfo) {
298323
commentBody = commentBody.replace(
299-
/<!-- @run-terraform:start:${{ inputs.environment }} -->.*<!-- @run-terraform:end:${{ inputs.environment }} -->/s,
324+
new RegExp(`<!-- @run-terraform:start:${ENV} -->.*<!-- @run-terraform:end:${ENV} -->`, 's'),
300325
jobBody,
301326
);
302327
} else {
@@ -365,7 +390,9 @@ jobs:
365390

366391
- name: Authenticate with Kubernetes
367392
if: inputs.kubernetes_cluster != ''
368-
run: gcloud container hub memberships get-credentials ${{ inputs.kubernetes_cluster }} --verbosity debug
393+
env:
394+
KUBERNETES_CLUSTER: ${{ inputs.kubernetes_cluster }}
395+
run: gcloud container hub memberships get-credentials $KUBERNETES_CLUSTER --verbosity debug
369396

370397
- name: Authenticate with Vault
371398
if: inputs.vault_role != ''
@@ -394,8 +421,10 @@ jobs:
394421

395422
- name: Run Terraform
396423
if: inputs.destroy == false
424+
env:
425+
ENV: ${{ inputs.environment }}
397426
run: |
398-
terraform apply -input=false plan-${{ inputs.environment }}.tfplan
427+
terraform apply -input=false plan-$ENV.tfplan
399428
400429
attest-deploy:
401430
if: inputs.image_url != '' && (inputs.environment == 'dev' || inputs.environment == 'test')
@@ -417,14 +446,16 @@ jobs:
417446

418447
- name: Get Image Digest URL from Image Tag URL
419448
id: image-digest-url
449+
env:
450+
IMAGE_URL: ${{ inputs.image_url }}
420451
run: |
421452
# If image_url = registry/repository@digest
422-
if [[ "${{inputs.image_url}}" == *"@"* ]]
453+
if [[ "$IMAGE_URL" == *"@"* ]]
423454
then
424-
image_digest_url="${{ inputs.image_url }}"
455+
image_digest_url="$IMAGE_URL"
425456
else
426457
# Convert registry/repository:tag to registry/repository@digest
427-
image_tag_url=${{ inputs.image_url }}
458+
image_tag_url=$IMAGE_URL
428459
digest=$(docker manifest inspect $image_tag_url -v | jq -r .Descriptor.digest)
429460
base_image_url=${image_tag_url%:*}
430461
image_digest_url="${base_image_url}@${digest}"
@@ -433,8 +464,11 @@ jobs:
433464
434465
- name: Check if already attested
435466
id: get-attestation
467+
env:
468+
ENV: ${{ inputs.env }}
469+
PROJECT_ID: ${{ inputs.project_id }}
436470
run: |
437-
attestation=$(gcloud container binauthz attestations list --project="${{ inputs.project_id }}" --attestor="projects/${{ inputs.project_id }}/attestors/Deploy-${{ inputs.environment }}" --artifact-url="${{ steps.image-digest-url.outputs.image_url }}")
471+
attestation=$(gcloud container binauthz attestations list --project="$PROJECT_ID" --attestor="projects/$PROJECT_ID/attestors/Deploy-$ENV" --artifact-url="${{ steps.image-digest-url.outputs.image_url }}")
438472
attestationoutput=$(printf '%s' $attestation | jq --raw-input --slurp '.' --join-output)
439473
echo "::set-output name=attestation::$attestationoutput"
440474
@@ -443,15 +477,19 @@ jobs:
443477
id: attest-deploy
444478
# Note: msg is an empty string when no attestation is done
445479
if: steps.get-attestation.outputs.attestation == ''
480+
env:
481+
ENV: ${{ inputs.env }}
482+
PROJECT_ID: ${{ inputs.project_id }}
483+
IMAGE_URL: ${{ steps.image-digest-url.outputs.image_url }}
446484
run: |
447485
gcloud beta container binauthz attestations sign-and-create \
448-
--project="${{ inputs.project_id }}" \
449-
--artifact-url="${{ steps.image-digest-url.outputs.image_url }}" \
450-
--attestor="Deploy-${{ inputs.environment }}" \
451-
--attestor-project="${{ inputs.project_id }}" \
452-
--keyversion-project="${{ inputs.project_id }}" \
486+
--project="$PROJECT_ID" \
487+
--artifact-url="$IMAGE_URL" \
488+
--attestor="Deploy-$ENV" \
489+
--attestor-project="$PROJECT_ID" \
490+
--keyversion-project="$PROJECT_ID" \
453491
--keyversion-location="europe-north1" \
454492
--keyversion-keyring="Attestor-Keyring" \
455-
--keyversion-key="Deploy-${{ inputs.environment }}-Key" \
493+
--keyversion-key="Deploy-$ENV-Key" \
456494
--keyversion="1"
457495
echo Attestation succeded

0 commit comments

Comments
 (0)