diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..b5e5c09 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,8 @@ +# Global owner +* @bgauduch + +# Critical files require explicit review +Dockerfile @bgauduch +.github/workflows/ @bgauduch +security/ @bgauduch +supported_versions.json @bgauduch diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c14160d..f651aed 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,6 +18,21 @@ on: - ".dockerignore" - "hadolint.yaml" - ".github/workflows/build-test.yml" + pull_request: + branches: + - master + paths: + - "Dockerfile" + - "supported_versions.json" + - "tests/**" + - ".github/workflows/build-test.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read env: IMAGE_NAME: "terraform-aws-cli" @@ -97,5 +112,6 @@ jobs: AWS_CLI_VERSION=${{ matrix.awscli_versions }} tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} push: false + load: false cache-from: type=gha cache-to: type=gha,mode=max diff --git a/.github/workflows/dockerhub-description-update.yml b/.github/workflows/dockerhub-description-update.yml index 6e3fc2c..b7e5913 100644 --- a/.github/workflows/dockerhub-description-update.yml +++ b/.github/workflows/dockerhub-description-update.yml @@ -9,6 +9,14 @@ on: paths: - README.md - .github/workflows/dockerhub-description.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + jobs: dockerHubDescription: runs-on: ubuntu-22.04 diff --git a/.github/workflows/lint-dockerfile.yml b/.github/workflows/lint-dockerfile.yml index 020a694..2ae6b61 100644 --- a/.github/workflows/lint-dockerfile.yml +++ b/.github/workflows/lint-dockerfile.yml @@ -12,7 +12,22 @@ on: - "!master" paths: - "Dockerfile" + - "hadolint.yaml" - ".github/workflows/lint-dockerfile.yml" + pull_request: + branches: + - master + paths: + - "Dockerfile" + - "hadolint.yaml" + - ".github/workflows/lint-dockerfile.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read jobs: lint: diff --git a/.github/workflows/push-latest.yml b/.github/workflows/push-latest.yml index eda62d0..5e1125e 100644 --- a/.github/workflows/push-latest.yml +++ b/.github/workflows/push-latest.yml @@ -3,6 +3,7 @@ name: push-latest # trigger on push to master # only on Dockerfile related modifications on: + workflow_dispatch: push: branches: - "master" @@ -16,6 +17,14 @@ on: - "supported_platforms.json" - "tests/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + packages: write + jobs: build_push_latest: runs-on: ubuntu-22.04 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 494c841..d1f73b6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,15 @@ on: release: types: [published] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + packages: write + id-token: write # required for cosign keyless OIDC signing + jobs: load_supported_versions: runs-on: ubuntu-22.04 @@ -61,3 +70,26 @@ jobs: push: true cache-from: type=gha cache-to: type=gha,mode=max + + - name: Generate SBOM + uses: anchore/sbom-action@v0.24.0 + with: + image: zenika/terraform-aws-cli:release-${{ env.RELEASE_TAG }}_terraform-${{ matrix.tf_versions }}_awscli-${{ matrix.awscli_versions }} + format: spdx-json + output-file: sbom-tf${{ matrix.tf_versions }}-aws${{ matrix.awscli_versions }}.spdx.json + + - name: Upload SBOM to release + uses: anchore/sbom-action/publish-sbom@v0.24.0 + with: + sbom-artifact-match: ".*\\.spdx\\.json$" + + - name: Install cosign + uses: sigstore/cosign-installer@v4.1.1 + + - name: Sign published image + env: + COSIGN_EXPERIMENTAL: "true" + run: | + IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' \ + zenika/terraform-aws-cli:release-${{ env.RELEASE_TAG }}_terraform-${{ matrix.tf_versions }}_awscli-${{ matrix.awscli_versions }}) + cosign sign --yes "${IMAGE_DIGEST}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..ac39f2f --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,37 @@ +name: scorecard +on: + branch_protection_rule: + schedule: + - cron: "30 1 * * 1" + push: + branches: + - master + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-22.04 + permissions: + security-events: write + id-token: write + contents: read + actions: read + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: ossf/scorecard-action@v2.4.3 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + - uses: actions/upload-artifact@v4 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + - uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: results.sarif diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..476c938 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,67 @@ +# Contributing + +Thank you for your interest in contributing to this project! + +## How to contribute + +1. Fork the repository +2. Create a branch from `master`: `git checkout -b feat/my-feature` +3. Make your changes +4. Ensure tests pass locally: `./dev.sh` +5. Commit using [Conventional Commits](#commit-convention) +6. Push and open a Pull Request targeting `master` + +## Commit convention + +This project follows the [Conventional Commits](https://www.conventionalcommits.org/) specification. + +Format: `(): ` + +Common types: +| Type | Use for | +|------|---------| +| `feat` | New feature | +| `fix` | Bug fix | +| `chore` | Maintenance, dependency updates | +| `ci` | CI/CD changes | +| `docs` | Documentation only | +| `refactor` | Code refactoring | +| `test` | Test additions or fixes | +| `security` | Security improvements | + +Examples: +``` +feat: add support for terraform 2.0 +fix(dev.sh): correct platform detection on arm64 +chore(deps): update debian base image to bookworm-20250101-slim +ci: add trivy vulnerability scanning +``` + +PR titles must also follow this convention — the title becomes the squash-merge commit message. + +## Local development + +Requirements: Docker, `jq`, `envsubst` + +```bash +# Build and test with latest supported versions +./dev.sh + +# Build and test with specific versions +./dev.sh -a 2.17.0 -t 1.9.0 + +# Build with a custom tag +./dev.sh -a 2.17.0 -t 1.9.0 -i my-test +``` + +## Adding a new tool version + +When Terraform or AWS CLI releases a new version: + +1. Add the version to `supported_versions.json` +2. Download the verification files to `security/` — see [`docs/binaries-verifications.md`](docs/binaries-verifications.md) +3. Open a PR with the changes + +## Code review + +All PRs require at least one approval. The CI must pass (lint, build, tests) before merging. diff --git a/Dockerfile b/Dockerfile index 2af6f55..9c42882 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,12 +8,14 @@ ARG DEBIAN_FRONTEND=noninteractive FROM debian:${DEBIAN_VERSION} as terraform ARG TARGETARCH ARG TERRAFORM_VERSION -RUN apt-get update -# RUN apt-get install --no-install-recommends -y libcurl4=7.74.0-1.3+deb11u7 -RUN apt-get install --no-install-recommends -y ca-certificates=20230311 -RUN apt-get install --no-install-recommends -y curl=7.88.1-10+deb12u4 -RUN apt-get install --no-install-recommends -y gnupg=2.2.40-1.1 -RUN apt-get install --no-install-recommends -y unzip=6.0-28 +RUN apt-get update \ + && apt-get install --no-install-recommends -y \ + ca-certificates=20230311 \ + curl=7.88.1-10+deb12u4 \ + gnupg=2.2.40-1.1 \ + unzip=6.0-28 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* WORKDIR /workspace RUN curl --silent --show-error --fail --remote-name https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_${TARGETARCH}.zip COPY security/hashicorp.asc ./ @@ -26,13 +28,16 @@ RUN unzip -j terraform_${TERRAFORM_VERSION}_linux_${TARGETARCH}.zip # Install AWS CLI version 2 FROM debian:${DEBIAN_VERSION} as aws-cli ARG AWS_CLI_VERSION -RUN apt-get update -RUN apt-get install -y --no-install-recommends ca-certificates=20230311 -RUN apt-get install -y --no-install-recommends curl=7.88.1-10+deb12u4 -RUN apt-get install -y --no-install-recommends gnupg=2.2.40-1.1 -RUN apt-get install -y --no-install-recommends unzip=6.0-28 -RUN apt-get install -y --no-install-recommends git=1:2.39.2-1.1 -RUN apt-get install -y --no-install-recommends jq=1.6-2.1 +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates=20230311 \ + curl=7.88.1-10+deb12u4 \ + gnupg=2.2.40-1.1 \ + unzip=6.0-28 \ + git=1:2.39.2-1.1 \ + jq=1.6-2.1 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* WORKDIR /workspace RUN curl --show-error --fail --output "awscliv2.zip" --remote-name "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-${AWS_CLI_VERSION}.zip" COPY security/awscliv2.asc ./ diff --git a/README.md b/README.md index 68d7af0..bd60ab0 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ # Terraform and AWS CLI Docker image +[![CI](https://github.com/bgauduch/terraform-aws-cli/actions/workflows/build-test.yml/badge.svg)](https://github.com/bgauduch/terraform-aws-cli/actions/workflows/build-test.yml) +[![Latest Release](https://img.shields.io/github/v/release/bgauduch/terraform-aws-cli)](https://github.com/bgauduch/terraform-aws-cli/releases/latest) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/bgauduch/terraform-aws-cli/badge)](https://securityscorecards.dev/viewer/?uri=github.com/bgauduch/terraform-aws-cli) + ## 📦 Supported tags and respective Dockerfile links Available image tags can be found on the Docker Hub registry: [zenika/terraform-aws-cli](https://hub.docker.com/r/zenika/terraform-aws-cli/tags) @@ -90,6 +94,14 @@ TERRAFORM_VERSION=1.5.2 ## 🙏 Contributions Do not hesitate to contribute by [filling an issue](https://github.com/Zenika/terraform-aws-cli/issues) or [a PR](https://github.com/Zenika/terraform-aws-cli/pulls) ! +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on how to contribute to this project. + +## Security + +See [SECURITY.md](SECURITY.md) for the security policy and how to report vulnerabilities. + ## 📚 Documentations * [Dependencies upgrades checklist](https://github.com/zenika-open-source/terraform-aws-cli/tree/master/docs/dependencies-upgrades.md) diff --git a/dev.sh b/dev.sh index c9f83f4..461a9fd 100755 --- a/dev.sh +++ b/dev.sh @@ -1,27 +1,58 @@ #!/usr/bin/env bash -set -eo pipefail +set -euo pipefail -# FIXME: use getopts function to parse aguments -# FIXME: if provided, both TF and AWS CLI semvers should be regex-validated +trap 'echo "Error: script failed at line ${LINENO}" >&2' ERR + +usage() { + echo "Usage: $0 [-a AWS_CLI_VERSION] [-t TF_VERSION] [-i IMAGE_TAG]" + echo " -a AWS CLI version (default: latest from supported_versions.json)" + echo " -t Terraform version (default: latest from supported_versions.json)" + echo " -i Image tag (default: dev)" + exit 1 +} + +while getopts "a:t:i:h" opt; do + case ${opt} in + a) AWS_VERSION="${OPTARG}" ;; + t) TF_VERSION="${OPTARG}" ;; + i) IMAGE_TAG="${OPTARG}" ;; + h) usage ;; + *) usage ;; + esac +done # Set AWS and TF CLI to latest supported versions if not specified -[[ -n $1 ]] && AWS_VERSION=$1 || AWS_VERSION=$(jq -r '.awscli_versions | sort | .[-1]' supported_versions.json) -[[ -n $2 ]] && TF_VERSION=$2 || TF_VERSION=$(jq -r '.tf_versions | sort | .[-1]' supported_versions.json) +AWS_VERSION="${AWS_VERSION:-$(jq -r '.awscli_versions | sort | .[-1]' supported_versions.json)}" +TF_VERSION="${TF_VERSION:-$(jq -r '.tf_versions | sort | .[-1]' supported_versions.json)}" # Set image name and tag (dev if not specified) IMAGE_NAME="zenika/terraform-aws-cli" -[[ -n $3 ]] && IMAGE_TAG=$3 || IMAGE_TAG="dev" +IMAGE_TAG="${IMAGE_TAG:-dev}" + +SEMVER_REGEX="^[0-9]+\.[0-9]+\.[0-9]+$" + +validate_semver() { + local version="$1" + local name="$2" + if [[ ! "${version}" =~ ${SEMVER_REGEX} ]]; then + echo "Error: ${name} '${version}' is not a valid semver (expected X.Y.Z)" + exit 1 + fi +} + +validate_semver "${AWS_VERSION}" "AWS_CLI_VERSION" +validate_semver "${TF_VERSION}" "TERRAFORM_VERSION" -# Set platform for Hadolint image (only linux/arm64 or linux/arm64 supported) -PLATEFORM="linux/$(uname -m)" +# Set platform for Hadolint image (only linux/amd64 or linux/arm64 supported) +PLATFORM="linux/$(uname -m)" # Lint Dockerfile echo "Linting Dockerfile..." docker container run --rm --interactive \ --volume "${PWD}":/data \ --workdir /data \ - --platform "${PLATEFORM}" \ + --platform "${PLATFORM}" \ hadolint/hadolint:2.12.0-alpine /bin/hadolint \ --config hadolint.yaml Dockerfile echo "Lint Successful!" @@ -30,7 +61,7 @@ echo "Lint Successful!" echo "Building images with AWS_CLI_VERSION=${AWS_VERSION} and TERRAFORM_VERSION=${TF_VERSION}..." docker buildx build \ --progress plain \ - --platform "${PLATEFORM}" \ + --platform "${PLATFORM}" \ --build-arg AWS_CLI_VERSION="${AWS_VERSION}" \ --build-arg TERRAFORM_VERSION="${TF_VERSION}" \ --tag ${IMAGE_NAME}:${IMAGE_TAG} . diff --git a/docs/dependencies-upgrades.md b/docs/dependencies-upgrades.md index 7819ffe..9c9e2e8 100644 --- a/docs/dependencies-upgrades.md +++ b/docs/dependencies-upgrades.md @@ -1,5 +1,11 @@ # ⬆️ Dependencies upgrades checklist +> **Note:** Dependency management is now partially automated via [Renovate](https://docs.renovatebot.com/): +> - **GitHub Actions** versions are updated automatically by Renovate. +> - **Base Debian image** version is updated automatically by Renovate. +> +> **Tool versions (Terraform, AWS CLI) still require manual steps:** when adding a new supported version, the binary verification files must be downloaded manually to `security/` — see [`docs/binaries-verifications.md`](binaries-verifications.md). + * Supported tools versions: * [Report to the doc](https://github.com/zenika-open-source/terraform-aws-cli/tree/master/docs/binaries-verifications.md) to add required security files when adding a new supported versions * check available **AWS CLI** version on the [project release page](https://github.com/aws/aws-cli/tags) diff --git a/tests/container-structure-tests.yml.template b/tests/container-structure-tests.yml.template index 619fc87..efe73d4 100644 --- a/tests/container-structure-tests.yml.template +++ b/tests/container-structure-tests.yml.template @@ -33,6 +33,31 @@ commandTests: args: ["--version"] expectedOutput: ["aws-cli/${AWS_VERSION}"] + - name: "Check terraform is in PATH" + command: "which" + args: ["terraform"] + expectedOutput: ["/usr/local/bin/terraform"] + + - name: "Check aws is in PATH" + command: "which" + args: ["aws"] + expectedOutput: ["/usr/local/bin/aws"] + + - name: "Check running as non-root user" + command: "id" + args: ["-u"] + expectedOutput: ["1001"] + + - name: "Check running as nonroot user" + command: "id" + args: ["-un"] + expectedOutput: ["nonroot"] + + - name: "Check HOME directory is set" + command: "sh" + args: ["-c", "echo $HOME"] + expectedOutput: ["/home/nonroot"] + fileExistenceTests: - name: 'Check non-root user home' path: '/home/nonroot'