forked from Kong/public-shared-actions
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(SLSA/SEC-973): container image signing action (Kong#65)
* feat(slsa/image-sign): Keyless image signing using Cosign filter uniq registry and sign using digest Always recursively sign manifest digests if present * update readme for sign action * fix readme Signed-off-by: saisatishkarra <saisatish.karra@konghq.com> * enable signing transaprency by default Signed-off-by: saisatishkarra <saisatish.karra@konghq.com> --------- Signed-off-by: saisatishkarra <saisatish.karra@konghq.com>
- Loading branch information
1 parent
c03e30a
commit b7def0b
Showing
4 changed files
with
385 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
name: Docker Sign Test | ||
|
||
on: | ||
pull_request: | ||
branches: | ||
- main | ||
push: | ||
branches: | ||
- main | ||
tags: | ||
- '*' | ||
workflow_dispatch: {} | ||
|
||
jobs: | ||
test-sign-docker-image: | ||
|
||
permissions: | ||
contents: read | ||
packages: write # needed to upload to packages to registry | ||
id-token: write # needed for signing the images with GitHub OIDC Token | ||
|
||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} | ||
name: Test Sign Docker Image | ||
runs-on: ubuntu-22.04 | ||
env: | ||
PRERELEASE_IMAGE: kongcloud/security-test-repo-pub:ubuntu_23_10 #particular reason for the choice of image: test multi arch image | ||
TAGS: kongcloud/security-test-repo-pub:ubuntu_23_10,kongcloud/security-test-repo:ubuntu_23_10 | ||
steps: | ||
|
||
- uses: actions/checkout@v3 | ||
|
||
- name: Install regctl | ||
uses: regclient/actions/regctl-installer@main | ||
|
||
- name: Parse Image Manifest Digest | ||
id: image_manifest_metadata | ||
run: | | ||
manifest_list_exists="$( | ||
if regctl manifest get "${PRERELEASE_IMAGE}" --format raw-body --require-list -v panic &> /dev/null; then | ||
echo true | ||
else | ||
echo false | ||
fi | ||
)" | ||
echo "manifest_list_exists=$manifest_list_exists" | ||
echo "manifest_list_exists=$manifest_list_exists" >> $GITHUB_OUTPUT | ||
manifest_sha="$(regctl image digest "${PRERELEASE_IMAGE}")" | ||
echo "manifest_sha=$manifest_sha" | ||
echo "manifest_sha=$manifest_sha" >> $GITHUB_OUTPUT | ||
- name: Sign Image digest | ||
id: sign_image | ||
if: steps.image_manifest_metadata.outputs.manifest_sha != '' | ||
uses: ./security-actions/sign-docker-image | ||
with: | ||
cosign_output_prefix: ubuntu-23-10 | ||
signature_registry: kongcloud/security-test-repo-sig-pub | ||
tags: ${{ env.TAGS }} | ||
image_digest: ${{ steps.image_manifest_metadata.outputs.manifest_sha }} | ||
local_save_cosign_assets: true | ||
registry_username: ${{ secrets.DOCKERHUB_PUSH_USERNAME }} | ||
registry_password: ${{ secrets.DOCKERHUB_PUSH_TOKEN }} | ||
|
||
- name: Push Images | ||
env: | ||
RELEASE_TAG: kongcloud/security-test-repo:v1 | ||
run: | | ||
docker pull ${PRERELEASE_IMAGE} | ||
for tag in $RELEASE_TAG; do | ||
regctl -v debug image copy ${PRERELEASE_IMAGE} $tag | ||
done | ||
- name: Sign Image digest | ||
id: sign_image_v1 | ||
if: steps.image_manifest_metadata.outputs.manifest_sha != '' | ||
uses: ./security-actions/sign-docker-image | ||
env: | ||
RELEASE_TAG: kongcloud/security-test-repo:v1 | ||
with: | ||
cosign_output_prefix: v1 # Optional | ||
local_save_cosign_assets: true # Optional | ||
signature_registry: kongcloud/security-test-repo-sig-pub | ||
tags: ${{ env.RELEASE_TAG }} | ||
image_digest: ${{ steps.image_manifest_metadata.outputs.manifest_sha }} | ||
registry_username: ${{ secrets.DOCKERHUB_PUSH_USERNAME }} | ||
registry_password: ${{ secrets.DOCKERHUB_PUSH_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
# Security actions | ||
|
||
## Action implemented | ||
|
||
- [Sign Docker Image](./sign-docker-image/action.yml) is a unified action for container image signing. The action leverages keyless signing to produce an Signature and uploads to Docker Image layer and Public Rekor for transaprency. | ||
|
||
- Tools used: | ||
- [Cosign](https://github.com/sigstore/cosign) | ||
### Signing Docker Image | ||
|
||
- For workflows where image artifact are being pushed to the registry, this needs to be implemented `after the scan and before the publish` step. | ||
|
||
#### Workflow / Job Permissions for Keyless OIDC Signing: | ||
```yaml | ||
permissions: | ||
packages: write | ||
id-token: write # needed for signing the images with GitHub OIDC Token | ||
``` | ||
#### Signature Publishing | ||
- `cosign sign` to: | ||
- Generate an signature based on keyless identities using `Github` OIDC provider within workflows | ||
- Be authenicated access to publish docker hub registry | ||
- Uploads the [mapping identities](https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md) to Public Rekor Instance logged forever. | ||
- **May Contain senstitive information for private repositories**; Yet no way to protect PII being uploaded / masked. | ||
|
||
#### Verification | ||
- `cosign verify` needs to have: | ||
- access to public rekor instance | ||
- authenicated access to private docker hub registry | ||
- un-authenticated access to public registry | ||
|
||
#### Input specification | ||
|
||
#### Parameters | ||
```yaml | ||
local_save_cosign_assets: | ||
description: 'Save cosign output assets locally on disk. Ex: certificate and signature of signed artifacts' | ||
required: false | ||
default: false | ||
cosign-output-prefix: | ||
description: 'cosign output prefix. Ex: certificate and signature of signed artifacts' | ||
required: true | ||
signature_registry: | ||
description: 'Separate registry to store image signature to avoid polluting image registry' | ||
required: false | ||
default: '' | ||
tags: | ||
description: 'Comma separated <image>:<tag> that have same digest' | ||
required: true | ||
image_digest: | ||
description: 'specify single sha256 digest associated with the specified image_registries' | ||
required: true | ||
registry_username: | ||
description: 'docker username to login against private docker registry' | ||
required: false | ||
registry_password: | ||
description: 'docker password to login against private docker registry' | ||
required: false | ||
``` | ||
#### Output specification | ||
|
||
- Generates a signature that is pushed to registry for every single platform and manifest digest | ||
|
||
- Generates a log entry in Public Rekor transaprency for every digest being signed with a unique docker repository | ||
|
||
- No Build Time artifacts are generated | ||
|
||
#### Verification: | ||
Use `cosign verify` command to specify claims and image digest to be verified against the rekor transaparency log and signature / certificate subject identity in the Docker registry | ||
|
||
Example: | ||
``` | ||
COSIGN_REPOSITORY=kong/notary cosign verify -a repo="Kong/kong-ee" -a workflow="Package & Release" --certificate-oidc-issuer="https://token.actions.githubusercontent.com" --certificate-identity-regexp="https://github.com/Kong/kong-ee/.github/workflows/release.yml*" <image>:<tag>@sha256:<disgest> | ||
``` | ||
|
||
#### Usage Examples | ||
|
||
```yaml | ||
jobs: | ||
test-sign-docker-image: | ||
permissions: | ||
contents: read | ||
packages: write # needed to upload to packages to registry | ||
id-token: write # needed for signing the images with GitHub OIDC Token | ||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} | ||
name: Test Sign Docker Image | ||
runs-on: ubuntu-22.04 | ||
env: | ||
PRERELEASE_IMAGE: kongcloud/security-test-repo-pub:ubuntu_23_10 #particular reason for the choice of image: test multi arch image | ||
TAGS: kongcloud/security-test-repo-pub:ubuntu_23_10,kongcloud/security-test-repo:ubuntu_23_10 | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Install regctl | ||
uses: regclient/actions/regctl-installer@main | ||
- name: Parse Image Manifest Digest | ||
id: image_manifest_metadata | ||
run: | | ||
manifest_list_exists="$( | ||
if regctl manifest get "${PRERELEASE_IMAGE}" --format raw-body --require-list -v panic &> /dev/null; then | ||
echo true | ||
else | ||
echo false | ||
fi | ||
)" | ||
echo "manifest_list_exists=$manifest_list_exists" | ||
echo "manifest_list_exists=$manifest_list_exists" >> $GITHUB_OUTPUT | ||
manifest_sha="$(regctl image digest "${PRERELEASE_IMAGE}")" | ||
echo "manifest_sha=$manifest_sha" | ||
echo "manifest_sha=$manifest_sha" >> $GITHUB_OUTPUT | ||
- name: Sign Image digest | ||
id: sign_image_pre_release | ||
if: steps.image_manifest_metadata.outputs.manifest_sha != '' | ||
uses: ./security-actions/sign-docker-image | ||
with: | ||
cosign_output_prefix: ubuntu-23-10 | ||
signature_registry: kongcloud/security-test-repo-sig-pub | ||
tags: ${{ env.TAGS }} | ||
image_digest: ${{ steps.image_manifest_metadata.outputs.manifest_sha }} | ||
local_save_cosign_assets: true | ||
registry_username: ${{ secrets.GHA_DOCKERHUB_PUSH_USER }} | ||
registry_password: ${{ secrets.GHA_KONG_ORG_DOCKERHUB_PUSH_TOKEN }} | ||
- name: Push Images | ||
env: | ||
RELEASE_TAG: kongcloud/security-test-repo:v1 | ||
run: | | ||
docker pull ${PRERELEASE_IMAGE} | ||
for tag in $RELEASE_TAG; do | ||
regctl -v debug image copy ${PRERELEASE_IMAGE} $tag | ||
done | ||
- name: Sign Image digest | ||
id: sign_image_promotion | ||
if: steps.image_manifest_metadata.outputs.manifest_sha != '' | ||
uses: ./security-actions/sign-docker-image | ||
env: | ||
RELEASE_TAG: kongcloud/security-test-repo:v1 | ||
with: | ||
cosign_output_prefix: v1 | ||
signature_registry: kongcloud/security-test-repo-sig-pub | ||
tags: ${{ env.RELEASE_TAG }} | ||
image_digest: ${{ steps.image_manifest_metadata.outputs.manifest_sha }} | ||
local_save_cosign_assets: true | ||
registry_username: ${{ secrets.GHA_DOCKERHUB_PUSH_USER }} | ||
registry_password: ${{ secrets.GHA_DOCKERHUB_PUSH_TOKEN }} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
name: Sign Docker Image | ||
description: Keyeless Image signing with transaprency and uploads to registry for specified docker image | ||
author: 'Kong' | ||
inputs: | ||
local_save_cosign_assets: | ||
description: 'Save cosign output assets locally on disk. Ex: certificate and signature of signed artifacts' | ||
required: false | ||
default: false | ||
cosign_output_prefix: | ||
description: 'cosign file prefix for storing local signatures and certificates. Works when input local_save_cosign_assets is enabled' | ||
required: false | ||
default: '' | ||
signature_registry: | ||
description: 'Separate registry to store image signature to avoid polluting image registry' | ||
required: false | ||
default: '' | ||
tags: | ||
description: 'Comma separated <image>:<tag> that have same digest' | ||
required: true | ||
image_digest: | ||
description: 'specify single sha256 digest associated with the specified image_registries' | ||
required: true | ||
registry_username: | ||
description: 'docker username to login against private docker registry' | ||
required: false | ||
registry_password: | ||
description: 'docker password to login against private docker registry' | ||
required: false | ||
|
||
#outputs: | ||
# sbom-cyclonedx-report: | ||
# description: 'SBOM cyclonedx report' | ||
# value: ${{ steps.meta.outputs.sbom_cyclonedx_file }} | ||
|
||
runs: | ||
using: composite | ||
steps: | ||
|
||
- name: Set Cosign metadata | ||
shell: bash | ||
id: meta | ||
env: | ||
LOCAL_SAVE_COSIGN_ASSETS: ${{ inputs.local_save_cosign_assets }} | ||
ASSET_PREFIX: ${{ inputs.cosign_output_prefix }} | ||
run: $GITHUB_ACTION_PATH/scripts/cosign-metadata.sh | ||
|
||
- name: Install Cosign | ||
uses: sigstore/cosign-installer@v3.1.1 | ||
|
||
- name: Check install! | ||
shell: bash | ||
run: cosign version | ||
|
||
- name: Setup image namespace for signing, strip off the tag | ||
shell: bash | ||
env: | ||
INPUT_TAGS: ${{ inputs.tags }} | ||
run: | | ||
set -euox pipefail | ||
TAGS="${INPUT_TAGS//,/ }" | ||
IMAGE_REPOS=$(for tag in \ | ||
`echo "${TAGS}"`; do | ||
echo -n "${tag}" | awk -F ":" '{print $1}' -;done|sort -u) | ||
echo $IMAGE_REPOS | ||
echo 'IMAGES<<EOF' >> $GITHUB_ENV | ||
echo $IMAGE_REPOS >> $GITHUB_ENV | ||
echo 'EOF' >> $GITHUB_ENV | ||
- name: Login to Container Registry | ||
uses: docker/login-action@v2.1.0 | ||
if: ${{ inputs.registry_username != '' && inputs.registry_password != '' }} | ||
with: | ||
username: ${{ inputs.registry_username }} | ||
password: ${{ inputs.registry_password }} | ||
|
||
- name: Sign the images with GitHub OIDC Token | ||
id: sign | ||
env: | ||
COSIGN_REPOSITORY: ${{ inputs.signature_registry }} | ||
COSIGN_ARGS: ${{ steps.meta.outputs.cosign_signing_args}} | ||
IMAGE_REGISTRIES: ${{ env.IMAGES }} # Space separated image registries that have same digest | ||
IMAGE_DIGEST: ${{ inputs.image_digest }} # Single Digest associated with the registry images | ||
shell: bash | ||
run: | | ||
set -euox pipefail | ||
for img in $IMAGES; do | ||
cosign sign ${{ env.COSIGN_ARGS }} \ | ||
-a "repo=${{ github.repository }}" \ | ||
-a "workflow=${{ github.workflow }}" \ | ||
-a "sha=${{ github.sha }}" \ | ||
"$img"@${{ env.IMAGE_DIGEST }} | ||
done | ||
# Upload Cosign Artifacts (public cert and signatures) | ||
- name: Upload Cosign Artifacts | ||
uses: actions/upload-artifact@v3 | ||
if: ${{ inputs.local_save_cosign_assets == 'true' && inputs.cosign_output_prefix != '' }} | ||
with: | ||
name: signed-image-assets | ||
path: | | ||
${{inputs.cosign_output_prefix}}*.crt | ||
${{inputs.cosign_output_prefix}}.sig* | ||
if-no-files-found: warn |
34 changes: 34 additions & 0 deletions
34
security-actions/sign-docker-image/scripts/cosign-metadata.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -euo pipefail | ||
|
||
readonly signature_ext=".sig" | ||
readonly signing_cert_ext=".crt" | ||
|
||
readonly rekor_transparency="true" | ||
|
||
# Always Recurisvely sign one/ all manifest digests for docker manifest distribution /list mediaType | ||
signing_args="--yes --recursive --tlog-upload=${rekor_transparency}" | ||
|
||
# if [[ ${MULTI_PLATFORM} ]]; then | ||
# signing_args+=" --recursive" | ||
# fi | ||
|
||
if [[ "${LOCAL_SAVE_COSIGN_ASSETS}" == "true" ]]; then | ||
if [[ -n "${ASSET_PREFIX}" ]]; then | ||
signature_file="${ASSET_PREFIX##*/}${signature_ext}" | ||
certificate_file="${ASSET_PREFIX##*/}${signing_cert_ext}" | ||
else | ||
echo '::error ::set input cosign_output_prefix in $0' | ||
exit 1 | ||
# signature_file="${ASSET_PREFIX##*/}${signature_ext}" | ||
# certificate_file="${ASSET_PREFIX##*/}${signing_cert_ext}" | ||
fi | ||
|
||
echo "signature_file=${signature_file}" >> $GITHUB_OUTPUT | ||
echo "certificate_file=${certificate_file}" >> $GITHUB_OUTPUT | ||
signing_args+=" --output-certificate=${certificate_file} --output-signature=${signature_file}" | ||
fi | ||
|
||
echo "COSIGN SIGNING ARGS: ${signing_args}" | ||
echo "cosign_signing_args=${signing_args}" >> $GITHUB_OUTPUT |