Skip to content
Open
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
27 changes: 27 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Keeps the operator's Go modules, digest-pinned base images and SHA-pinned actions current.
version: 2
updates:
- package-ecosystem: gomod
directory: /operator
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
# k8s.io and controller-runtime only build against matching versions.
kubernetes:
patterns:
- k8s.io/*
- sigs.k8s.io/*
- package-ecosystem: docker
directories: ["/operator"]
schedule:
interval: weekly
cooldown:
default-days: 7
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
44 changes: 44 additions & 0 deletions .github/workflows/operator-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Operator CI

on:
pull_request:
paths:
- operator/**
- .github/workflows/operator-ci.yml
# On master and on release this runs from the publish workflows, before they push.
workflow_call:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: operator
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: operator/go.mod
cache-dependency-path: operator/go.sum
# Regenerates CRDs/RBAC/deepcopy first; a diff means they were not committed.
- name: Generated files are up to date
run: make manifests generate && git diff --exit-code
- name: Controller and spawn API tests (envtest)
run: make test

image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- name: Build notebook operator image
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: operator
platforms: linux/amd64
tags: mip-notebook-operator:ci
cache-from: type=gha,scope=operator
cache-to: type=gha,scope=operator,mode=max
123 changes: 123 additions & 0 deletions .github/workflows/operator-container-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: Operator container scanning

on:
pull_request:
paths:
- operator/**
- ci/**
- .github/workflows/operator-container-scan.yml
workflow_dispatch:
workflow_call: {}
schedule:
- cron: '0 2 * * 1' # weekly scan, every Monday 02:00 UTC

jobs:
container-scan:
name: Container Vulnerability Scanning (${{ matrix.image }})
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # required for uploading results to github security
strategy:
fail-fast: false
matrix:
include:
- image: mip-notebook-operator
context: operator
dockerfile: operator/Dockerfile
cache-scope: operator
env:
IMAGE_NAME: ${{ matrix.image }}:testing
DOCKERFILE: ${{ matrix.dockerfile }}
CONTAINER_SCAN_MERGED_SARIF_OUTPUT: container-scan-${{ matrix.image }}-merged.sarif

# SCA / CVE
TRIVY_IGNOREFILE: ci/suppress_trivy.yaml
OSV_IGNOREFILE: ci/suppress_osv_scanner.toml
TRIVY_SCA_SARIF_OUTPUT: sca-trivy-container.sarif
OSV_SCA_SARIF_OUTPUT: sca-osv-container.sarif

# SAST / Linting
SEMGREP_CONFIG_RULESETS: >-
semgrep-rules/dockerfile auto
OPENGREP_SAST_SARIF_OUTPUT: sast-opengrep-dockerfile.sarif
HADOLINT_SAST_SARIF_OUTPUT: sast-hadolint-dockerfile.sarif

steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 #v7.1.0
with:
python-version: '3.15.1'

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 #v4.4.1

# Reads the same GHA layer cache as operator-ci.yml, so unchanged layers are not rebuilt.
- name: Build Docker image
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc #v7.4.0
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
platforms: linux/amd64
tags: ${{ env.IMAGE_NAME }}
load: true
cache-from: type=gha,scope=${{ matrix.cache-scope }}

- name: Setup tools
run: |
bash ci/setup-tools.sh \
--install-tool trivy,osv-scanner,opengrep,hadolint,semgrep-rules

- name: Run SAST scanning
run: python ci/container_scan.py --scan-type sast

- name: Run SCA scanning
if: ${{ !cancelled() }}
run: python ci/container_scan.py --scan-type sca --image ${{ env.IMAGE_NAME }}

- name: Merge all SARIF reports
if: ${{ !cancelled() }}
run: |
python ci/container_scan.py \
--merge-sarif "${{ env.TRIVY_SCA_SARIF_OUTPUT }}" "${{ env.OSV_SCA_SARIF_OUTPUT }}" "${{ env.OPENGREP_SAST_SARIF_OUTPUT }}" "${{ env.HADOLINT_SAST_SARIF_OUTPUT }}" \
--merge-output "${{ env.CONTAINER_SCAN_MERGED_SARIF_OUTPUT }}"

- name: Upload merged SARIF to GitHub Security tab
if: ${{ !cancelled() && hashFiles(env.CONTAINER_SCAN_MERGED_SARIF_OUTPUT) != '' }}
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
sarif_file: ${{ env.CONTAINER_SCAN_MERGED_SARIF_OUTPUT }}
category: container-scan-${{ matrix.image }}

- name: Encrypt SARIF report
id: encrypt
if: ${{ !cancelled() && hashFiles(env.CONTAINER_SCAN_MERGED_SARIF_OUTPUT) != '' }}
env:
PW: ${{ secrets.ARTIFACT_PASSWORD }}
OUT: ${{ env.CONTAINER_SCAN_MERGED_SARIF_OUTPUT }}.gpg
run: |
if [ -z "$PW" ]; then
echo "::warning title=SARIF report not uploaded::ARTIFACT_PASSWORD is not available in this run (Dependabot, Renovate or fork PR). Skipping encryption and upload."
rm -f -- "$CONTAINER_SCAN_MERGED_SARIF_OUTPUT"
exit 0
fi
printf '%s' "$PW" | gpg --symmetric --batch --yes --quiet \
--pinentry-mode loopback --passphrase-fd 0 \
--no-symkey-cache \
--cipher-algo AES256 \
--s2k-mode 3 --s2k-digest-algo SHA512 --s2k-count 65011712 \
--output "$OUT" -- "$CONTAINER_SCAN_MERGED_SARIF_OUTPUT"
rm -f -- "$CONTAINER_SCAN_MERGED_SARIF_OUTPUT"
echo "encrypted=$OUT" >> "$GITHUB_OUTPUT"

- name: Upload encrypted SARIF artifact
if: ${{ !cancelled() && steps.encrypt.outputs.encrypted != '' }}
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: container-scan-sarif-report-${{ matrix.image }}
path: ${{ steps.encrypt.outputs.encrypted }}
retention-days: 30
if-no-files-found: error
49 changes: 49 additions & 0 deletions .github/workflows/operator-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# The dev image: every operator change on master, tagged dev and sha-<commit>. Pin it by
# digest, dev moves. A MIP release goes through operator-publish.yml instead.
name: Publish operator dev image

on:
push:
branches: [master]
paths:
- operator/**
- .github/workflows/operator-dev.yml

concurrency: operator-dev

permissions:
contents: read

jobs:
test:
uses: ./.github/workflows/operator-ci.yml

build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- name: Log in to Docker Hub
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: hbpmip/mip-notebook-operator
tags: |
type=raw,value=dev
type=sha
- name: Build and push
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: operator
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=operator
cache-to: type=gha,scope=operator,mode=max
118 changes: 118 additions & 0 deletions .github/workflows/operator-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
name: Publish operator image

on:
release:
types: [published]

env:
HARBOR_REGISTRY: docker-registry.ebrains.eu
DOCKER_BUILDKIT: 1
DOCKER_CLIENT_TIMEOUT: 300
COMPOSE_HTTP_TIMEOUT: 300
RETRY_ATTEMPTS: "4"
RETRY_SLEEP: "10"

# Registries use their own credentials; GITHUB_TOKEN only reads.
permissions:
contents: read
# The called scan workflows upload their SARIF to the Security tab.
security-events: write

jobs:
test:
uses: ./.github/workflows/operator-ci.yml

sca:
name: Operator SCA
uses: ./.github/workflows/operator-sca.yml
secrets:
ARTIFACT_PASSWORD: ${{ secrets.ARTIFACT_PASSWORD }}

container-scan:
name: Operator container scanning
uses: ./.github/workflows/operator-container-scan.yml
secrets:
ARTIFACT_PASSWORD: ${{ secrets.ARTIFACT_PASSWORD }}

sast:
name: Operator SAST
uses: ./.github/workflows/operator-sast.yml
secrets:
ARTIFACT_PASSWORD: ${{ secrets.ARTIFACT_PASSWORD }}

secrets-scan:
name: Operator secret scanning
uses: ./.github/workflows/operator-secrets-scan.yml

build_and_push:
name: Build ${{ matrix.image }} image and push to registries
needs: [test, sca, container-scan, sast, secrets-scan]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- image: mip-notebook-operator
context: operator
dockerfile: operator/Dockerfile
cache-scope: operator

steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 #v4.4.1

- name: Harbor connectivity probe
run: |
set -x
getent hosts ${{ env.HARBOR_REGISTRY }} || true
curl -vI --connect-timeout 10 https://${{ env.HARBOR_REGISTRY }}/v2/ || true

- name: Disable IPv6 (job-scoped)
run: |
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1

- name: Log in to Docker Hub
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 #v4.7.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Log in to EBRAINS HARBOR (retry)
env:
HARBOR_USERNAME: ${{ secrets.HARBOR_USERNAME }}
HARBOR_PASSWORD: ${{ secrets.HARBOR_PASSWORD }}
run: |
for i in $(seq 1 ${{ env.RETRY_ATTEMPTS }}); do
printf '%s' "$HARBOR_PASSWORD" | \
docker login ${{ env.HARBOR_REGISTRY }} \
-u "$HARBOR_USERNAME" --password-stdin && exit 0
echo "Harbor login attempt $i failed; retrying in ${{ env.RETRY_SLEEP }}s..."
sleep ${{ env.RETRY_SLEEP }}
done
echo "Harbor login failed after ${{ env.RETRY_ATTEMPTS }} attempts" >&2
exit 1

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 #v6.2.0
with:
images: |
hbpmip/${{ matrix.image }}
${{ env.HARBOR_REGISTRY }}/medical-informatics-platform/${{ matrix.image }}

# Shares the GHA layer cache with operator-ci.yml and operator-container-scan.yml.
- name: Build ${{ matrix.image }} docker image
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc #v7.4.0
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=${{ matrix.cache-scope }}
cache-to: type=gha,scope=${{ matrix.cache-scope }},mode=max
Loading
Loading