Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci/patch images #209

Closed
wants to merge 40 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b82f9df
chore(cosign): add cosign public key
R3DRUN3 Mar 7, 2024
4075bb0
chore(utilities): add python script to retrieve json image list
R3DRUN3 Mar 7, 2024
aa29984
ci: add image patch and sign task
R3DRUN3 Mar 7, 2024
d532466
chore(gitignore): exclude cosign private key and local macos files
R3DRUN3 Mar 7, 2024
6ffb97e
docs(readme): add image hardening section
R3DRUN3 Mar 7, 2024
96f1b3d
ci(patch): modify secret names
R3DRUN3 Mar 7, 2024
69083aa
ci(patch): modify comment
R3DRUN3 Mar 7, 2024
a40dc21
Update utilities/image_list_json.py
R3DRUN3 Mar 8, 2024
ccd3485
Update utilities/image_list_json.py
R3DRUN3 Mar 8, 2024
6a95c1a
Update utilities/image_list_json.py
R3DRUN3 Mar 8, 2024
4530071
Update utilities/image_list_json.py
R3DRUN3 Mar 8, 2024
5974972
chore(utilities): ignore latest tag in modules
R3DRUN3 Mar 27, 2024
dab017a
chore: merge main branch
R3DRUN3 Mar 27, 2024
69c26a1
fix(utilities): remove else case
R3DRUN3 Mar 27, 2024
9053412
feat(cve): generate cve report
R3DRUN3 Mar 27, 2024
cc9d069
feat(report): add argument for secured report
R3DRUN3 Mar 28, 2024
9106a0d
chore(utilities): add usage to scripts
R3DRUN3 Mar 28, 2024
7049f7b
chore: add cosign public key
R3DRUN3 Apr 18, 2024
8342bdb
chore: merge main
R3DRUN3 Apr 18, 2024
f23fae0
chore: update cve report
R3DRUN3 Apr 18, 2024
f8d5fea
git commit -m feat: update CI to include run on dev branch
nutellinoit Apr 18, 2024
79f5145
chore: bump cosign version to 2.2.4
nutellinoit Apr 18, 2024
91b858d
chore: bump also cosign installer action
nutellinoit Apr 18, 2024
322a3e9
feat: add parameter on images.yml "cve_patch_enabled" to trigger patc…
nutellinoit Apr 18, 2024
c8d7d41
feat: enable also HIGH cve on patch process
nutellinoit Apr 18, 2024
de04fbb
feat: enabled additional images with errors in different places
nutellinoit Apr 18, 2024
b137336
ci(patch): fix image tag
R3DRUN3 Apr 18, 2024
0411cd6
ci(patch): fix image tag
R3DRUN3 Apr 18, 2024
b04d602
ci(patch): test
R3DRUN3 Apr 18, 2024
3cc4854
ci(patch): test
R3DRUN3 Apr 18, 2024
560dfdc
ci(patch): test
R3DRUN3 Apr 18, 2024
48b0b29
ci(patch): test
R3DRUN3 Apr 18, 2024
8e69ef0
ci(patch): test
R3DRUN3 Apr 18, 2024
16f6f96
ci(patch): test
R3DRUN3 Apr 18, 2024
5afac4c
ci(patch): always push an image on secured path
R3DRUN3 Apr 18, 2024
a4b12b5
ci(patch): test
R3DRUN3 Apr 18, 2024
4ba46cf
ci(patch): test
R3DRUN3 Apr 18, 2024
8014e3a
ci(patch): test
R3DRUN3 Apr 18, 2024
5372780
ci(patch): test
R3DRUN3 Apr 18, 2024
6938d4d
ci(patch): test
R3DRUN3 Apr 18, 2024
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
132 changes: 132 additions & 0 deletions .github/workflows/patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
name: Patch Images

on:
schedule:
- cron: '0 2 */15 * *' # Every 15 days at 2 a.m.
# push:
# branches:
# - main

jobs:
setup:
runs-on: ubuntu-latest
outputs:
mymatrix: ${{ steps.dataStep.outputs.myoutput }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4

# Retrieve image list via python (you can choose to include the last 3 tags by passing the '--include-last-3-tags' argument to the script)
- name: Export Image List With Python
id: dataStep
run: |
cd utilities
TARGETS=$(python3 image_list_json.py)
echo $TARGETS
echo "myoutput=$(jq -cn --argjson environments "$TARGETS" '{target: $environments}')" >> $GITHUB_OUTPUT

patch:
needs: setup
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{fromJson(needs.setup.outputs.mymatrix)}}
steps:
- name: Print image name
run: echo ${{matrix.target}}

- name: Install Cosign
uses: sigstore/[email protected]
with:
cosign-release: 'v2.2.3'

- name: Generate Trivy Report
uses: aquasecurity/trivy-action@69cbbc0cbbf6a2b0bab8dcf0e9f2d7ead08e87e4
with:
scan-type: 'image'
format: 'json'
output: 'report.json'
ignore-unfixed: true
vuln-type: 'os'
R3DRUN3 marked this conversation as resolved.
Show resolved Hide resolved
severity: 'MEDIUM,HIGH,CRITICAL'
image-ref: ${{ matrix.target }}
R3DRUN3 marked this conversation as resolved.
Show resolved Hide resolved

- name: Check Vuln Count
id: vuln_count
run: |
report_file="report.json"
vuln_count=$(jq '.Results | length' "$report_file")
echo "vuln_count=$vuln_count" >> $GITHUB_OUTPUT
echo $vuln_count

- name: Set Image Tag
id: set_tag
run: |
TAG=$(echo "${{ matrix.target }}" | grep -oP '(?<=:).*' | grep -oP '^[^/]+')
IMMUNIZED_TAG="${TAG}"
PATCHED_TAG_SBOM=$(echo "${{matrix.target}}" | tr '/:' '-')
IMAGE_NAME=$(echo "${{ matrix.target }}" | sed -E 's|^.+/([^:/]+).*|\1|')
echo "PATCHED_TAG=${IMMUNIZED_TAG}" >> $GITHUB_ENV
echo "PATCHED_TAG_SBOM=${PATCHED_TAG_SBOM}" >> $GITHUB_ENV
echo "IMAGE_NAME=${IMAGE_NAME}" >> $GITHUB_ENV

- name: Copa Action
if: steps.vuln_count.outputs.vuln_count != '0'
id: copa
uses: project-copacetic/[email protected]
with:
image: ${{ matrix.target }}
image-report: 'report.json'
patched-tag: ${{ env.PATCHED_TAG }}

- name: Log into harbor
if: steps.copa.conclusion == 'success'
id: login
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d
with:
registry: registry.sighup.io
username: ${{ secrets.SIGHUP_REGISTRY_USERNAME }}
password: ${{ secrets.SIGHUP_REGISTRY_PASSWORD }}

- name: Tag Image for Harbor
if: steps.login.conclusion == 'success'
run: |
docker tag ${{ steps.copa.outputs.patched-image }} registry.sighup.io/fury/secured/${{ env.IMAGE_NAME }}:${{ env.PATCHED_TAG }}


- name: Docker Push Patched Image
id: push
if: steps.login.conclusion == 'success'
run: |
docker push registry.sighup.io/fury/secured/${{ env.IMAGE_NAME }}:${{ env.PATCHED_TAG }}

- name: Produce Image SBOM
id: sbom
if: steps.login.conclusion == 'success'
uses: anchore/sbom-action@v0
with:
image: "registry.sighup.io/fury/secured/${{ env.IMAGE_NAME }}:${{ env.PATCHED_TAG }}"
artifact-name: ${{ env.PATCHED_TAG_SBOM }}.spdx.json

- name: Sign Image with Cosign
if: steps.login.conclusion == 'success'
run: |
cosign sign --yes --key env://COSIGN_PRIVATE_KEY "registry.sighup.io/fury/secured/${{ env.IMAGE_NAME }}:${{ env.PATCHED_TAG }}"
env:
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}

- name: Attest the Image with SBOM
if: steps.sbom.conclusion == 'success'
run: |
echo "${{ env.PATCHED_TAG_SBOM }}"
SBOM_FILE=$(find /tmp/sbom-action-* -name "*${{ env.PATCHED_TAG_SBOM }}*.spdx.json" -type f)
echo "${SBOM_FILE}"
if [ -z "$SBOM_FILE" ]; then
echo "Error: .spdx file not found"
exit 1
fi
cosign attest --yes --key env://COSIGN_PRIVATE_KEY --type spdx --predicate "${SBOM_FILE}" "registry.sighup.io/fury/secured/${{ env.IMAGE_NAME }}:${{ env.PATCHED_TAG }}"
env:
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
login
login

# macos
.DS_Store

# cosign
cosign.key
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,20 @@ Example `images.yml`:
## Automated execution

This automation runs once a day: `"0 2 * * *"` and every time someone pushes to the `main` branch.


## Image Hardening

The [*patch*](https://github.com/sighupio/fury-distribution-container-image-sync/blob/main/.github/workflows/patch.yaml) Github action is designed to automatically fix vulnerabilities in the *OS layer* of images wherever possible.
Additionally, it signs the images using [Cosign](https://github.com/sigstore/cosign) with a private key owned by Sighup, generates the SBOM (Software Bill of Materials) for the images, and [attests](https://edu.chainguard.dev/open-source/sbom/sboms-and-attestations) them using the SBOM as a predicate.

In order to verify the signature on an image, use the following command:
```console
export IMAGE_NAME=<IMAGE-NAME-HERE>
cosign verify --key cosign/cosign.pub $IMAGE_NAME
```
In order to verify the attestation, use the following command:
```console
cosign verify-attestation --type spdx --key ./cosign/cosign.pub $IMAGE_NAME | jq -r .payload | base64 -D | jq .
```

Empty file added cosign/cosign.pub
Empty file.
54 changes: 54 additions & 0 deletions utilities/image_list_json.py
R3DRUN3 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# USAGE python3 images_list_python.py --retrieve-last-3-tags (OPTIONAL)

# OUTPUT SAMPLE:
# [
# "registry.sighup.io/fury/acid/postgres-operator:v1.6.3",
# "registry.sighup.io/fury/acid/spilo-13:2.0-p7",
# "registry.sighup.io/fury/acid/pgbouncer:master-16",
# "registry.sighup.io/fury/acid/logical-backup:v1.6.3",
# "registry.sighup.io/fury/wrouesnel/postgres_exporter:v0.8.0",
# "registry.sighup.io/fury/grafana/tempo:2.3.1",
# "registry.sighup.io/fury/memcached:1.5.17-alpine",
# "registry.sighup.io/fury/velero/velero:v1.13.0"
# ]

import os
import yaml
import json
import argparse
R3DRUN3 marked this conversation as resolved.
Show resolved Hide resolved
import sys

def get_images_and_tags(directory, include_last_3_tags=False):
image_list = []

for root, dirs, files in os.walk(directory):
for file in files:
if file == 'images.yml':
file_path = os.path.join(root, file)
with open(file_path, 'r') as f:
data = yaml.safe_load(f)
for image_data in data.get('images', []):
image_name = image_data.get('destinations', [''])[0]
if image_name:
tags = image_data.get('tag', [])
if include_last_3_tags and len(tags) >= 3:
tags = tags[-3:]
elif len(tags) >= 1:
tags = tags[-1:] # Only the last tag
else:
print(f"WARNING: the {image_name} does not have tags defined", file=sys.stderr)
image_list.extend([f"{image_name}:{tag}" for tag in tags])

return image_list

def main():
parser = argparse.ArgumentParser(description='Process images YAML files.')
parser.add_argument('--retrieve-last-3-tags', action='store_true', help='Include the last 3 tags for each image')
args = parser.parse_args()

parent_folder = os.path.dirname(os.getcwd())
image_list = get_images_and_tags(parent_folder, include_last_3_tags=args.retrieve_last_3_tags)
json.dump(image_list, sys.stdout, indent=2)

if __name__ == "__main__":
main()
Loading