Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/code_review_infra.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ permissions:

jobs:
code_review_dev:
uses: pagopa/dx/.github/workflows/infra_plan.yaml@main
# uses: pagopa/dx/.github/workflows/infra_plan.yaml@main
uses: ./.github/workflows/infra_plan.yaml
name: Code Review Infra Plan
secrets: inherit
with:
Expand Down
357 changes: 357 additions & 0 deletions .github/workflows/infra_apply.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
on:
workflow_call:
inputs:
environment:
description: Environment where the resources will be deployed.
type: string
required: true
base_path:
description: The base path on which the script will look for Terraform projects
type: string
required: true
env_vars:
description: List of environment variables to set up, given in env=value format.
type: string
required: false
use_private_agent:
description: Use a private agent to run the Terraform plan.
type: boolean
required: false
default: false
override_github_environment:
description: Set a value if GitHub Environment name is different than the TF environment folder
type: string
required: false
default: ""
use_labels:
description: Use labels to start the right environment's GitHub runner. If use_labels is true, also use_private_agent must be set to true
type: boolean
required: false
default: false
override_labels:
description: Needed for special cases where the environment alone is not sufficient as a distinguishing label
type: string
required: false
default: ""

env:
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
ARM_USE_OIDC: true
ARM_USE_AZUREAD: true
ARM_STORAGE_USE_AZUREAD: true
ROLE_ARN: ${{ secrets.ROLE_ARN }}
AWS_REGION: ${{ secrets.AWS_REGION }}
TF_IN_AUTOMATION: true

concurrency:
group: ${{ github.workflow }}-${{ inputs.environment }}-${{ inputs.base_path }}-cd
cancel-in-progress: false

jobs:
get-terraform-version:
name: Get Terraform Version
runs-on: ubuntu-latest
outputs:
terraform_version: ${{ steps.get-version.outputs.terraform_version }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Get terraform version from .terraform-version file
id: get-version
uses: pagopa/dx/.github/actions/get-terraform-version@main

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'infra_apply.yaml' step
Uses Step: get-version
uses 'pagopa/dx/.github/actions/get-terraform-version' with ref 'main', not a pinned commit hash
with:
default_version: "1.10.4"

detect-projects:
Comment on lines +52 to +66

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 4 months ago

In general, fix this by explicitly setting a permissions block that grants the minimal required scopes for the GITHUB_TOKEN. You can add it at the workflow root so it applies to all jobs, or per job if some need additional rights. For a workflow that only needs to read repository contents (e.g., checkout code, run Terraform, static analysis) and not update PRs or issues, contents: read is usually sufficient.

For this specific file, the best minimal non-breaking fix is to add a workflow-level permissions block right after the on: section and before env:. This will apply to all jobs (get-terraform-version, detect-projects, tf-modules-check, tf_plan, etc.) unless a job explicitly overrides it. Based on the visible snippets, these jobs only need to read repository contents, so contents: read is an appropriate least-privilege setting. No imports or additional methods are required; this is purely a YAML configuration change in .github/workflows/infra_apply.yaml.

Concretely:

  • In .github/workflows/infra_apply.yaml, insert:
permissions:
  contents: read

after the workflow_call inputs block (line 35/36) and before the existing env: block (line 37). No other changes are necessary.

Suggested changeset 1
.github/workflows/infra_apply.yaml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/infra_apply.yaml b/.github/workflows/infra_apply.yaml
--- a/.github/workflows/infra_apply.yaml
+++ b/.github/workflows/infra_apply.yaml
@@ -34,6 +34,9 @@
         required: false
         default: ""
 
+permissions:
+  contents: read
+
 env:
   ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
   ARM_USE_OIDC: true
EOF
@@ -34,6 +34,9 @@
required: false
default: ""

permissions:
contents: read

env:
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
ARM_USE_OIDC: true
Copilot is powered by AI and may make mistakes. Always verify output.
name: Detect Terraform Projects
runs-on: ubuntu-latest
needs: [get-terraform-version]
outputs:
matrix: ${{ steps.detect.outputs.matrix }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- name: Detect Terraform projects
id: detect
env:
BASE_PATH: ${{ inputs.base_path }}
ENVIRONMENT: ${{ inputs.environment }}
EVENT_NAME: ${{ github.event_name }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
BEFORE: ${{ github.event.before }}
SHA: ${{ github.sha }}
run: |
set -euo pipefail
BASE_PATH="${BASE_PATH%/}"
DIR="$BASE_PATH/$ENVIRONMENT"
MODULES_DIR="$BASE_PATH/_modules"

if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
CHANGED=$(find "$DIR" -maxdepth 2 -name '*.tf' -not -path "*/_modules/*" 2>/dev/null || true)
elif [ "$EVENT_NAME" = "pull_request" ]; then
CHANGED=$(git diff --name-only "origin/${BASE_REF}...HEAD" -- "$DIR" || true)
MODULES_CHANGED=$(git diff --name-only "origin/${BASE_REF}...HEAD" -- "$MODULES_DIR" || true)
else
CHANGED=$(git diff --name-only "$BEFORE" "$SHA" -- "$DIR" || true)
MODULES_CHANGED=$(git diff --name-only "$BEFORE" "$SHA" -- "$MODULES_DIR" || true)
fi

if [ "$EVENT_NAME" != "workflow_dispatch" ] && [ -n "${MODULES_CHANGED:-}" ]; then
CHANGED=$(find "$DIR" -maxdepth 2 -name '*.tf' -not -path "*/_modules/*" 2>/dev/null || true)
fi

# The two layouts are mutually exclusive:
# - flat: a .tf exists directly in $DIR → $DIR is the single project root
# - multi-project: no .tf directly in $DIR → each first-level subdir is a project root
IS_FLAT=$(printf '%s\n' "$CHANGED" | while IFS= read -r file; do
rel="${file#"${DIR}/"}"
case "$rel" in
*/*) ;; # file in a subdir: not a flat indicator
*.tf) echo 1; break ;; # .tf directly in $DIR: flat project
esac
done)

if [ -n "$IS_FLAT" ]; then
MATRIX=$(jq -cn --arg d "$DIR" '[$d]')
else
MATRIX=$(printf '%s\n' "$CHANGED" \
| while IFS= read -r file; do
[ -z "$file" ] && continue
rel="${file#"${DIR}/"}"
case "$rel" in */*) echo "${DIR}/${rel%%/*}" ;; esac
done \
| sort -u \
| jq -R . | jq -sc .)
fi

MATRIX=${MATRIX:-[]}
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
echo "Detected matrix: $MATRIX"

tf-modules-check:
Comment on lines +67 to +134

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 4 months ago

To fix the problem, explicitly define a permissions block that restricts the GITHUB_TOKEN to the least privileges needed. In this workflow, jobs primarily read repository contents (checkout, git diff) and do not appear to need write access or special scopes like pull-requests: write. The minimal safe baseline is contents: read at the workflow root so that it applies to all jobs (get-terraform-version, detect-projects, tf-modules-check, tf_plan, tf_apply, etc.) that don’t override it.

The best fix without changing existing functionality is to add a root-level permissions block just after the on: trigger configuration (before env: or concurrency:). This will ensure all jobs use a read-only token for repository contents. No other code changes are required in the jobs themselves. Concretely, in .github/workflows/infra_apply.yaml, insert:

permissions:
  contents: read

between the on: block and the env: block (around existing lines 36–37). This does not affect any other behavior of the workflow, only the GitHub token’s allowed operations.

Suggested changeset 1
.github/workflows/infra_apply.yaml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/infra_apply.yaml b/.github/workflows/infra_apply.yaml
--- a/.github/workflows/infra_apply.yaml
+++ b/.github/workflows/infra_apply.yaml
@@ -34,6 +34,9 @@
         required: false
         default: ""
 
+permissions:
+  contents: read
+
 env:
   ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
   ARM_USE_OIDC: true
EOF
@@ -34,6 +34,9 @@
required: false
default: ""

permissions:
contents: read

env:
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
ARM_USE_OIDC: true
Copilot is powered by AI and may make mistakes. Always verify output.
uses: pagopa/dx/.github/workflows/static_analysis.yaml@main
name: Check terraform registry modules hashes
needs: [get-terraform-version, detect-projects]
if: ${{ needs.detect-projects.outputs.matrix != '[]' }}
secrets: inherit
strategy:
fail-fast: false
matrix:
dir: ${{ fromJSON(needs.detect-projects.outputs.matrix) }}
with:
terraform_version: ${{ needs.get-terraform-version.outputs.terraform_version }}
check_to_run: lock_modules
folder: ${{ matrix.dir }}/
verbose: true

tf_plan:
Comment on lines +135 to +150

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 4 months ago

To fix the problem, explicitly declare a permissions block to restrict the GITHUB_TOKEN on the tf-modules-check job (and optionally at the workflow root, but the reported issue is specifically on this job). Since this job runs static analysis and does not obviously need to write to the repo, a safe and minimal starting point is contents: read. If the reusable workflow requires additional scopes, they can be added later, but we should not assume write access is needed.

Concretely:

  • Edit .github/workflows/infra_apply.yaml.
  • In the tf-modules-check job definition (around line 134–149), add a permissions: section indented to the same level as needs: and strategy:.
  • Set:
    • contents: read as a minimal permission.
    • If you know this job needs nothing else (no PR comments, etc.), keep only that. Given the provided snippet, we will add just contents: read.

No imports or other code changes are needed; only the YAML for this job is updated.

Suggested changeset 1
.github/workflows/infra_apply.yaml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/infra_apply.yaml b/.github/workflows/infra_apply.yaml
--- a/.github/workflows/infra_apply.yaml
+++ b/.github/workflows/infra_apply.yaml
@@ -137,6 +137,8 @@
     needs: [get-terraform-version, detect-projects]
     if: ${{ needs.detect-projects.outputs.matrix != '[]' }}
     secrets: inherit
+    permissions:
+      contents: read
     strategy:
       fail-fast: false
       matrix:
EOF
@@ -137,6 +137,8 @@
needs: [get-terraform-version, detect-projects]
if: ${{ needs.detect-projects.outputs.matrix != '[]' }}
secrets: inherit
permissions:
contents: read
strategy:
fail-fast: false
matrix:
Copilot is powered by AI and may make mistakes. Always verify output.
name: "Terraform Plan"
strategy:
fail-fast: false
matrix:
dir: ${{ fromJSON(needs.detect-projects.outputs.matrix) }}
# Use inputs.override_labels if set; otherwise, fall back to inputs.environment.
# When inputs.use_labels and inputs.use_private_agent are true, apply the selected labels.
# Default to 'self-hosted' if inputs.use_private_agent is true, or 'ubuntu-latest' otherwise.
runs-on: ${{ inputs.use_labels && inputs.use_private_agent && (inputs.override_labels != '' && inputs.override_labels || inputs.environment) || inputs.use_private_agent && 'self-hosted' || 'ubuntu-latest' }}
needs: [get-terraform-version, tf-modules-check, detect-projects]
if: ${{ needs.detect-projects.outputs.matrix != '[]' }}
environment: ${{ inputs.override_github_environment == '' && inputs.environment || inputs.override_github_environment}}-ci
permissions:
id-token: write
contents: read
pull-requests: write
env:
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
TFPLAN_FILE: tf-outcome-${{ github.sha }}
TF_HTTP_USERNAME: ${{ secrets.STATEGRAPH_USERNAME }}
TF_HTTP_PASSWORD: ${{ secrets.STATEGRAPH_TOKEN }}
outputs:
terraform_version: ${{ needs.get-terraform-version.outputs.terraform_version }}

steps:
# The working directory comes from the matrix; available in ${{ steps.directory.outputs.dir }}
- name: Set directory
id: directory
env:
WORKING_DIR: ${{ matrix.dir }}
run: |
set -euo pipefail
printf "dir=%q" "$WORKING_DIR" >> "$GITHUB_OUTPUT"

- name: Set artifact name
id: artifact
env:
WORKING_DIR: ${{ matrix.dir }}
run: |
set -euo pipefail
SLUG=$(echo "$WORKING_DIR" | tr '/' '-')
echo "name=terraform-bundle-${SLUG}" >> "$GITHUB_OUTPUT"

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
name: Checkout

- name: Set Environment Variables
if: ${{ inputs.env_vars }}
env:
ENV_VARS: ${{ inputs.env_vars }}
run: |
set -euo pipefail
for i in "$ENV_VARS[@]"
do
printf "%q\n" "$i" >> "$GITHUB_ENV"
done

- name: Cloud Login
uses: pagopa/dx/actions/csp-login@main

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'infra_apply.yaml' step
Uses Step
uses 'pagopa/dx/actions/csp-login' with ref 'main', not a pinned commit hash

- name: Terraform Setup
uses: pagopa/dx/.github/actions/terraform-setup@main

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'infra_apply.yaml' step
Uses Step: set-terraform-version
uses 'pagopa/dx/.github/actions/terraform-setup' with ref 'main', not a pinned commit hash
id: set-terraform-version
with:
terraform_version: ${{ needs.get-terraform-version.outputs.terraform_version }}

- name: Terraform Init
working-directory: ${{ steps.directory.outputs.dir }}
run: |
terraform init -input=false

- name: Terraform Plan
working-directory: ${{ steps.directory.outputs.dir }}
run: |
set -o pipefail
terraform plan \
-lock-timeout=120s \
-out="$TFPLAN_FILE" \
-input=false \
| grep -v "hidden-link:"
# Bundle the plan file together with .terraform.lock.hcl and .terraform/modules/
# and upload it to the same storage backend used for the Terraform state.
# The bundle path is derived automatically from the backend config.
# Outputs (provider, plan-path, credentials) are passed to the apply job.
- name: Upload Terraform Plan
id: upload_plan
uses: pagopa/dx/actions/terraform-plan-storage-upload@main

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'infra_apply.yaml' step
Uses Step: upload_plan
uses 'pagopa/dx/actions/terraform-plan-storage-upload' with ref 'main', not a pinned commit hash
with:
plan-file: ${{ env.TFPLAN_FILE }}
working-directory: ${{ steps.directory.outputs.dir }}

- name: Save plan outputs to artifact
env:
PROVIDER: ${{ steps.upload_plan.outputs.provider }}
PLAN_PATH: ${{ steps.upload_plan.outputs['plan-path'] }}
AZURE_STORAGE_ACCOUNT: ${{ steps.upload_plan.outputs['azure-storage-account'] }}
AZURE_CONTAINER: ${{ steps.upload_plan.outputs['azure-container'] }}
AWS_BUCKET: ${{ steps.upload_plan.outputs['aws-bucket'] }}
AWS_REGION: ${{ steps.upload_plan.outputs['aws-region'] }}
run: |
set -euo pipefail
jq -n \
--arg provider "$PROVIDER" \
--arg plan_path "$PLAN_PATH" \
--arg azure_storage_account "$AZURE_STORAGE_ACCOUNT" \
--arg azure_container "$AZURE_CONTAINER" \
--arg aws_bucket "$AWS_BUCKET" \
--arg aws_region "$AWS_REGION" \
'{provider: $provider, plan_path: $plan_path, azure_storage_account: $azure_storage_account, azure_container: $azure_container, aws_bucket: $aws_bucket, aws_region: $aws_region}' \
> plan-outputs.json

- name: Upload plan outputs artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact.outputs.name }}-outputs
path: plan-outputs.json

tf_apply:
name: "Terraform Apply"
strategy:
fail-fast: false
matrix:
dir: ${{ fromJSON(needs.detect-projects.outputs.matrix) }}
runs-on: ${{ inputs.use_labels && inputs.use_private_agent && (inputs.override_labels != '' && inputs.override_labels || inputs.environment) || inputs.use_private_agent && 'self-hosted' || 'ubuntu-latest' }}
needs: [get-terraform-version, tf_plan, detect-projects]
if: ${{ needs.detect-projects.outputs.matrix != '[]' }}
environment: ${{ inputs.override_github_environment == '' && inputs.environment || inputs.override_github_environment}}-cd
permissions:
id-token: write
contents: read
env:
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TFPLAN_FILE: tf-outcome-${{ github.sha }}
TF_HTTP_USERNAME: ${{ secrets.STATEGRAPH_USERNAME }}
TF_HTTP_PASSWORD: ${{ secrets.STATEGRAPH_TOKEN }}

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
name: Checkout

- name: Set artifact name
id: artifact
env:
WORKING_DIR: ${{ matrix.dir }}
run: |
set -euo pipefail
SLUG=$(echo "$WORKING_DIR" | tr '/' '-')
echo "name=terraform-bundle-${SLUG}" >> "$GITHUB_OUTPUT"

- name: Cloud Login
uses: pagopa/dx/actions/csp-login@main

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'infra_apply.yaml' step
Uses Step
uses 'pagopa/dx/actions/csp-login' with ref 'main', not a pinned commit hash

- name: Terraform Setup
uses: pagopa/dx/.github/actions/terraform-setup@main

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'infra_apply.yaml' step
Uses Step
uses 'pagopa/dx/.github/actions/terraform-setup' with ref 'main', not a pinned commit hash
with:
terraform_version: ${{ needs.get-terraform-version.outputs.terraform_version }}

# Download plan outputs artifact from tf_plan
- name: Download plan outputs artifact
uses: actions/download-artifact@v4
with:
name: ${{ steps.artifact.outputs.name }}-outputs
path: /tmp/plan-outputs

- name: Read plan outputs
id: plan_outputs
run: |
set -euo pipefail
FILE=/tmp/plan-outputs/plan-outputs.json
echo "provider=$(jq -r .provider "$FILE")" >> "$GITHUB_OUTPUT"
echo "plan_path=$(jq -r .plan_path "$FILE")" >> "$GITHUB_OUTPUT"
echo "azure_storage_account=$(jq -r .azure_storage_account "$FILE")" >> "$GITHUB_OUTPUT"
echo "azure_container=$(jq -r .azure_container "$FILE")" >> "$GITHUB_OUTPUT"
echo "aws_bucket=$(jq -r .aws_bucket "$FILE")" >> "$GITHUB_OUTPUT"
echo "aws_region=$(jq -r .aws_region "$FILE")" >> "$GITHUB_OUTPUT"

# Download and extract the plan bundle (plan file + .terraform.lock.hcl + .terraform/modules/).
# Storage coordinates come from the plan outputs artifact.
- name: Download Terraform Plan
uses: pagopa/dx/actions/terraform-plan-storage-download@main

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'infra_apply.yaml' step
Uses Step
uses 'pagopa/dx/actions/terraform-plan-storage-download' with ref 'main', not a pinned commit hash
with:
provider: ${{ steps.plan_outputs.outputs.provider }}
plan-path: ${{ steps.plan_outputs.outputs.plan_path }}
working-directory: ${{ matrix.dir }}
azure-storage-account: ${{ steps.plan_outputs.outputs.azure_storage_account }}
azure-container: ${{ steps.plan_outputs.outputs.azure_container }}
aws-bucket: ${{ steps.plan_outputs.outputs.aws_bucket }}
aws-region: ${{ steps.plan_outputs.outputs.aws_region }}

- name: Terraform Init
working-directory: ${{ matrix.dir }}
run: |
terraform init -input=false

- name: Terraform Apply
working-directory: ${{ matrix.dir }}
run: |
set -o pipefail
terraform apply \
-lock-timeout=120s \
-auto-approve \
-input=false \
"$TFPLAN_FILE" \
| grep -v "hidden-link:"
Loading
Loading