From 28811efdc255256bc6b01f0c955a1677a80f6841 Mon Sep 17 00:00:00 2001 From: Christoph Hamsen Date: Thu, 1 Feb 2024 13:50:23 +0100 Subject: [PATCH 1/9] ci: add main ci jobs --- .github/actions/build/action.yml | 173 +++++++++ .github/actions/context/action.yaml | 153 ++++++++ .github/actions/grype/action.yaml | 49 +++ .github/actions/k3s-cluster/action.yaml | 74 ++++ .../actions/k8s-version-config/action.yaml | 25 ++ .github/actions/trivy-config/action.yaml | 53 +++ .github/actions/trivy-image/action.yaml | 54 +++ .github/workflows/.reusable-build.yml | 99 +++++ .github/workflows/.reusable-ci.yml | 177 +++++++++ .../workflows/.reusable-cleanup-registry.yml | 40 ++ .github/workflows/.reusable-compliance.yml | 84 +++++ .github/workflows/.reusable-docs.yaml | 43 +++ .../workflows/.reusable-integration-test.yml | 344 ++++++++++++++++++ .github/workflows/.reusable-sast.yml | 227 ++++++++++++ .github/workflows/.reusable-sca.yml | 100 +++++ .github/workflows/.reusable-unit-test.yml | 32 ++ .github/workflows/pr.yml | 29 ++ .github/workflows/push.yml | 29 ++ Makefile | 6 +- README.md | 8 +- {docker => build}/Dockerfile | 0 {helm => charts/semgr8s}/Chart.yaml | 0 ...ivilege-escalation-no-securitycontext.yaml | 0 .../semgr8s}/rules/hostnetwork-pod.yaml | 0 .../semgr8s}/rules/privileged-container.yaml | 0 .../semgr8s}/rules/run-as-non-root.yaml | 0 {helm => charts/semgr8s}/templates/NOTES.txt | 0 .../semgr8s}/templates/_helpers.tpl | 0 .../semgr8s}/templates/deployment.yaml | 0 {helm => charts/semgr8s}/templates/env.yaml | 0 {helm => charts/semgr8s}/templates/role.yaml | 0 .../semgr8s}/templates/rolebinding.yaml | 0 {helm => charts/semgr8s}/templates/rules.yaml | 0 .../semgr8s}/templates/service.yaml | 0 .../semgr8s}/templates/serviceaccount.yaml | 0 .../semgr8s}/templates/webhook.yaml | 0 {helm => charts/semgr8s}/values.yaml | 0 helm/.helmignore | 23 -- 38 files changed, 1792 insertions(+), 30 deletions(-) create mode 100644 .github/actions/build/action.yml create mode 100644 .github/actions/context/action.yaml create mode 100644 .github/actions/grype/action.yaml create mode 100644 .github/actions/k3s-cluster/action.yaml create mode 100644 .github/actions/k8s-version-config/action.yaml create mode 100644 .github/actions/trivy-config/action.yaml create mode 100644 .github/actions/trivy-image/action.yaml create mode 100644 .github/workflows/.reusable-build.yml create mode 100644 .github/workflows/.reusable-ci.yml create mode 100644 .github/workflows/.reusable-cleanup-registry.yml create mode 100644 .github/workflows/.reusable-compliance.yml create mode 100644 .github/workflows/.reusable-docs.yaml create mode 100644 .github/workflows/.reusable-integration-test.yml create mode 100644 .github/workflows/.reusable-sast.yml create mode 100644 .github/workflows/.reusable-sca.yml create mode 100644 .github/workflows/.reusable-unit-test.yml create mode 100644 .github/workflows/pr.yml create mode 100644 .github/workflows/push.yml rename {docker => build}/Dockerfile (100%) rename {helm => charts/semgr8s}/Chart.yaml (100%) rename {helm => charts/semgr8s}/rules/allow-privilege-escalation-no-securitycontext.yaml (100%) rename {helm => charts/semgr8s}/rules/hostnetwork-pod.yaml (100%) rename {helm => charts/semgr8s}/rules/privileged-container.yaml (100%) rename {helm => charts/semgr8s}/rules/run-as-non-root.yaml (100%) rename {helm => charts/semgr8s}/templates/NOTES.txt (100%) rename {helm => charts/semgr8s}/templates/_helpers.tpl (100%) rename {helm => charts/semgr8s}/templates/deployment.yaml (100%) rename {helm => charts/semgr8s}/templates/env.yaml (100%) rename {helm => charts/semgr8s}/templates/role.yaml (100%) rename {helm => charts/semgr8s}/templates/rolebinding.yaml (100%) rename {helm => charts/semgr8s}/templates/rules.yaml (100%) rename {helm => charts/semgr8s}/templates/service.yaml (100%) rename {helm => charts/semgr8s}/templates/serviceaccount.yaml (100%) rename {helm => charts/semgr8s}/templates/webhook.yaml (100%) rename {helm => charts/semgr8s}/values.yaml (100%) delete mode 100644 helm/.helmignore diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml new file mode 100644 index 0000000..54be081 --- /dev/null +++ b/.github/actions/build/action.yml @@ -0,0 +1,173 @@ +name: build +description: 'Build connaisseur image' +inputs: + image_registry: + description: 'Image registry to be used' + required: true + image_repo: + description: 'Image repository to be used' + required: true + image_tag: + description: 'Image tag to be used' + required: true + ref_tags: + description: 'Reference tags to be used' + required: true + image_labels: + description: 'Image labels to be used' + required: true + repo_owner: + description: 'Name of repository owner, e.g. "github.repository_owner" for ghcr.io' + required: true + repo_token: + description: 'Access token for repository owner, e.g. "secrets.GITHUB_TOKEN" for ghcr.io' + required: true + cosign_version: + description: 'Cosign version to be used' + required: true + cosign_private_key: + description: 'Cosign private key' + required: true + cosign_password: + description: 'Cosign private key password' + required: true +outputs: + cosign_public_key: + description: 'Cosign public key' + value: ${{ steps.verify.outputs.public_key }} +runs: + using: "composite" + steps: + - name: Install Cosign + uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # v3.1.2 (probably) + - name: Set up Docker buildx + uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # v2.4.1 + - name: Login with registry + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0 + with: + registry: ${{ inputs.image_registry }} + username: ${{ inputs.repo_owner }} + password: ${{ inputs.repo_token }} + - name: Generate tags + id: tags + run: | + echo "${{ inputs.ref_tags }}" + export PREFIX="${{ inputs.image_registry }}/${{ inputs.image_repo }}:" + TAGS="${PREFIX}${{ inputs.image_tag }},$(echo ${{ inputs.ref_tags }} | tr ' ' '\n' | awk '{print "${PREFIX}"$1}' | envsubst | tr '\n' ',')" + echo tags=${TAGS} >> ${GITHUB_OUTPUT} + shell: bash + - name: Build and push image + id: build + uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 # v4.0.0 + with: + push: true + cache-from: type=gha + cache-to: type=gha,mode=max + file: build/Dockerfile + labels: ${{ inputs.image_labels }} + tags: ${{ steps.tags.outputs.tags }} + provenance: true + sbom: true + - name: Create SBOM + uses: anchore/sbom-action@07978da4bdb4faa726e52dfc6b1bed63d4b56479 # v0.13.3 + with: + image: ${{ inputs.image_registry }}/${{ inputs.image_repo }}@${{ steps.build.outputs.digest }} + format: cyclonedx-json + artifact-name: sbom.cdx + output-file: sbom.cdx + - name: Sign image + id: sign + run: | + cosign sign --key env://COSIGN_PRIVATE_KEY -a tag=${{ inputs.image_tag }} -y ${TAGS} + cosign attach sbom --sbom sbom.cdx --type cyclonedx ${TAGS} + cosign sign --key env://COSIGN_PRIVATE_KEY --attachment sbom -y ${TAGS} + env: + TAGS: ${{ inputs.image_registry }}/${{ inputs.image_repo }}@${{ steps.build.outputs.digest }} + COSIGN_PRIVATE_KEY: ${{ inputs.cosign_private_key }} + COSIGN_PASSWORD: ${{ inputs.cosign_password }} + shell: bash + - name: Verify build data + id: verify + run: | + mkdir ci + cosign public-key --key env://COSIGN_PRIVATE_KEY > ci/cosign.pub + PUBLIC_KEY="$(cat ci/cosign.pub)" + cosign tree ${TAGS} + PUBLIC_KEY=${PUBLIC_KEY} cosign verify --key env://PUBLIC_KEY ${TAGS} + PUBLIC_KEY=${PUBLIC_KEY} cosign verify --key env://PUBLIC_KEY --attachment sbom ${TAGS} + SIGNATURE=$(cosign triangulate ${TAGS}) + PUBLIC_KEY="${PUBLIC_KEY//$'\n'/'
'}" + SBOM="${SIGNATURE::-4}.sbom" + echo public_key="${PUBLIC_KEY}" >> ${GITHUB_OUTPUT} + echo signature=${SIGNATURE} >> ${GITHUB_OUTPUT} + echo sbom=${SBOM} >> ${GITHUB_OUTPUT} + env: + TAGS: ${{ inputs.image_registry }}/${{ inputs.image_repo }}@${{ steps.build.outputs.digest }} + COSIGN_PRIVATE_KEY: ${{ inputs.cosign_private_key }} + COSIGN_PASSWORD: ${{ inputs.cosign_password }} + shell: bash + - name: Upload public key + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + with: + name: cosign.pub + path: ci/cosign.pub + - name: Show build and signature information + run: | + CONFIGURE="yq '. *+ load(\"tests/integration/var-img.yaml\")' tests/integration/ghcr-values.yaml > ghcr.yaml &&\n\t IMAGE=\"${{ inputs.image_registry }}/${{ inputs.image_repo }}\" TAG=\"${{ inputs.image_tag }}\" IMAGEPULLSECRET=\"\" envsubst < ghcr.yaml > update &&\n\t yq '. *+ load(\"update\")' -i charts/connaisseur/values.yaml &&\n\t rm ghcr.yaml update" + CONFIGURE=$(printf -- "${CONFIGURE}") + PUBLIC_KEY="${{ steps.verify.outputs.public_key }}" + PUBLIC_KEY="$(printf -- "${PUBLIC_KEY//'
'/'\n'}")" + HELM_PATCH="yq e '.kubernetes.deployment.image.repository = \"${{ inputs.image_registry }}/${{ inputs.image_repo }}\"' -i charts/connaisseur/values.yaml\nyq e '.kubernetes.deployment.image.tag = \"${{ inputs.image_tag }}\"' -i charts/connaisseur/values.yaml" + HELM_PATCH=$(printf -- "${HELM_PATCH}") + echo "# :building_construction: Build Information" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "
Build artifactsValue
Registry${{ inputs.image_registry }}
Repository${{ inputs.image_repo }}
Tags${{ inputs.image_tag }}, ${{ inputs.ref_tags }}
Workflow image${{ inputs.image_registry }}/${{ inputs.image_repo }}:${{ inputs.image_tag }}
All reference tags$(echo ${{ steps.tags.outputs.tags }} | tr ',' '\n')
Digest${{ steps.build.outputs.digest }}
Signature${{ steps.verify.outputs.signature }}
Public key${PUBLIC_KEY}
SBOM (cyclonedx-json)${{ steps.verify.outputs.sbom }}
" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "
:bookmark_tabs: Metadata" >> ${GITHUB_STEP_SUMMARY} + echo "
${{ steps.build.outputs.metadata }}
" >> ${GITHUB_STEP_SUMMARY} + echo "
" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "
:hammer_and_wrench: Use Build Artifacts" >> ${GITHUB_STEP_SUMMARY} + echo "(needs docker login via PAT with package:read permission)" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "
" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "
:mag: Verify Build" >> ${GITHUB_STEP_SUMMARY} + echo "(needs Docker login via PAT with package:read permission)" >> ${GITHUB_STEP_SUMMARY} + echo "
    " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Show BuildKit image details:
    docker buildx imagetools inspect ${{ inputs.image_registry }}/${{ inputs.image_repo }}:${{ inputs.image_tag }}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Extract BuildKit SBOM (can be consumed by e.g. syft or grype):
    docker buildx imagetools inspect ${{ inputs.image_registry }}/${{ inputs.image_repo }}:${{ inputs.image_tag }} --format \"{{ json .SBOM.SPDX }}\"
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Extract BuildKit provenance (SLSA) data:
    docker buildx imagetools inspect ${{ inputs.image_registry }}/${{ inputs.image_repo }}:${{ inputs.image_tag }} --format \"{{ json .Provenance.SLSA }}\"
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Cosign public key:
    ${PUBLIC_KEY}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Store Cosign public key:
    echo \"${PUBLIC_KEY}\" > cosign.pub 
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Verify Cosign signature using cosign.pub file:
    cosign verify --key cosign.pub ${{ inputs.image_registry }}/${{ inputs.image_repo }}:${{ inputs.image_tag }} 
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Verify Cosign signature :
    COSIGN_PUBLIC_KEY='${PUBLIC_KEY}' cosign verify --key env://COSIGN_PUBLIC_KEY ${{ inputs.image_registry }}/${{ inputs.image_repo }}:${{ inputs.image_tag }} 
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Display all Cosign supply chain security artifacts:
    cosign tree ${{ inputs.image_registry }}/${{ inputs.image_repo }}:${{ inputs.image_tag }} 
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Download Cosign-attached SBOM (syft-generated cyclonedx-json):
    
    +        COSIGN_PUBLIC_KEY='${PUBLIC_KEY}' cosign verify --key env://COSIGN_PUBLIC_KEY --attachment sbom ${{ inputs.image_registry }}/${{ inputs.image_repo }}:${{ inputs.image_tag }} && # verify signature before download
    +        cosign download sbom ${{ inputs.image_registry }}/${{ inputs.image_repo }}:${{ inputs.image_tag }} > sbom.cdx # perform actual download
    +          
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
" >> ${GITHUB_STEP_SUMMARY} + echo "
" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "Let's start testing :rocket:" >> ${GITHUB_STEP_SUMMARY} + shell: bash diff --git a/.github/actions/context/action.yaml b/.github/actions/context/action.yaml new file mode 100644 index 0000000..ea81d45 --- /dev/null +++ b/.github/actions/context/action.yaml @@ -0,0 +1,153 @@ +name: context +description: 'Get the current context' +inputs: + build_registry: + description: "Build registry to be used" + required: false + default: "ghcr.io" + build_repo: + description: "Base build repository to be used (non-protected branches will push to '${build_repo}-test')" + required: false + default: "${{ github.repository }}" +outputs: + chart_version: + description: "Semgr8s Helm chart version" + value: ${{ steps.get_context.outputs.CHART_VERSION }} + original_registry: + description: "Public semgr8s registry" + value: ${{ steps.get_context.outputs.ORIGINAL_REGISTRY }} + original_repo: + description: "Public semgr8s repo" + value: ${{ steps.get_context.outputs.ORIGINAL_REPO }} + original_tag: + description: "Current semgr8s tag, i.e. version" + value: ${{ steps.get_context.outputs.ORIGINAL_TAG }} + original_image: + description: "Full semg8s image reference, i.e. registry + repository + tag" + value: ${{ steps.get_context.outputs.ORIGINAL_IMAGE }} + build_registry: + description: "Workflow build registry used for testing" + value: ${{ steps.get_context.outputs.BUILD_REGISTRY }} + build_repo: + description: "Workflow build repository used for testing" + value: ${{ steps.get_context.outputs.BUILD_REPO }} + build_tag: + description: "Workflow build tag used for testing (unique for each run)" + value: ${{ steps.show_context.outputs.BUILD_TAG }} + build_image: + description: "Workflow build image used for testing, i.e. registry + repository + tag" + value: ${{ steps.show_context.outputs.BUILD_IMAGE }} + ref_tags: + description: "All reference tags used for build" + value: ${{ steps.show_context.outputs.REF_TAGS }} + build_labels: + description: "Repository- and workflow-specific build labels" + value: ${{ steps.meta.outputs.labels }} +runs: + using: "composite" + steps: + - name: Get chart version + id: get_chart_version + uses: mikefarah/yq@47f4f8c7939f887e851b35f14def6741b8f5396e # v4.31.2 + with: + cmd: yq '.version' charts/semgr8s/Chart.yaml + - name: Get app version + id: get_app_version + uses: mikefarah/yq@47f4f8c7939f887e851b35f14def6741b8f5396e # v4.31.2 + with: + cmd: yq '.appVersion' charts/semgr8s/Chart.yaml + - name: Get original image + id: get_original_image_repository + uses: mikefarah/yq@47f4f8c7939f887e851b35f14def6741b8f5396e # v4.31.2 + with: + cmd: yq '.deployment.image.repository' charts/semgr8s/values.yaml + - name: Get context + id: get_context + run: | + GHREF=${{ github.ref }} + echo "github.ref is: ${GHREF}" + CHART_VERSION=${{ steps.get_chart_version.outputs.result }} + CONFIGURED_IMAGE_REPO=${{ steps.get_original_image_repository.outputs.result }} + ORIGINAL_REGISTRY=$(echo "${CONFIGURED_IMAGE_REPO}" | cut -d "/" -f 1) + ORIGINAL_REPO=$(echo "${CONFIGURED_IMAGE_REPO}" | cut -d "/" -f 2- | cut -d ":" -f 1) + ORIGINAL_TAG=v${{ steps.get_app_version.outputs.result }} + BUILD_REGISTRY=${{ inputs.build_registry }} + BUILD_REPO=${{ inputs.build_repo }} + if [[ "${GHREF}" != "refs/heads/master" && + "${GHREF}" != "refs/tags/v"* && + "${GHREF}" != "refs/heads/develop" + ]]; then + BUILD_REPO="${BUILD_REPO}-test" + fi + + echo CHART_VERSION=${CHART_VERSION} >> ${GITHUB_OUTPUT} + echo ORIGINAL_REGISTRY=${ORIGINAL_REGISTRY} >> ${GITHUB_OUTPUT} + echo ORIGINAL_REPO=${ORIGINAL_REPO} >> ${GITHUB_OUTPUT} + echo ORIGINAL_TAG=${ORIGINAL_TAG} >> ${GITHUB_OUTPUT} + echo ORIGINAL_IMAGE=${CONFIGURED_IMAGE_REPO}:${ORIGINAL_TAG} >> ${GITHUB_OUTPUT} + echo BUILD_REGISTRY=${BUILD_REGISTRY} >> ${GITHUB_OUTPUT} + echo BUILD_REPO=${BUILD_REPO} >> ${GITHUB_OUTPUT} + shell: bash + - name: Generate metadata + id: meta + uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96 # v4.3.0 + with: + images: ${{ steps.get_context.outputs.BUILD_REGISTRY }}/${{ steps.get_context.outputs.BUILD_REPO }} + flavor: | + latest=true + tags: | + type=schedule + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=sha + - name: Show context + id: show_context + run: | + PREFIX=$(echo "${{ steps.get_context.outputs.BUILD_REGISTRY }}/${{ steps.get_context.outputs.BUILD_REPO }}:" | sed 's%/%\/%g') + TAGS="${{ steps.meta.outputs.tags }}" + REF_TAGS="${TAGS//${PREFIX}/}" + BUILD_IMAGE=$(echo "${TAGS}" | tail -2 | head -1) + BUILD_TAG="${BUILD_IMAGE//${PREFIX}/}" + [[ ${BUILD_TAG} == "sha-"* ]] || exit 1 # check as parsing of the BUILD_TAG maybe fragile and dependent on docker/metadata-action priorities + REF_TAGS="${REF_TAGS//${BUILD_TAG}/}" + echo BUILD_TAG=${BUILD_TAG} >> ${GITHUB_OUTPUT} + echo BUILD_IMAGE=${BUILD_IMAGE} >> ${GITHUB_OUTPUT} + echo REF_TAGS=${REF_TAGS} >> ${GITHUB_OUTPUT} + echo "# :clipboard: Context" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "
Build ContextValue
Helm chart version${{ steps.get_context.outputs.CHART_VERSION }}
Original registry${{ steps.get_context.outputs.ORIGINAL_REGISTRY }}
Original repository${{ steps.get_context.outputs.ORIGINAL_REPO }}
Original tag${{ steps.get_context.outputs.ORIGINAL_TAG }}
Original image${{ steps.get_context.outputs.ORIGINAL_IMAGE }}
Build registry${{ steps.get_context.outputs.BUILD_REGISTRY }}
Build repository${{ steps.get_context.outputs.BUILD_REPO }}
Build tag${BUILD_TAG}
Build image${BUILD_IMAGE}
Ref tags${REF_TAGS}
All build images${{ steps.meta.outputs.tags }}
Build labels${{ steps.meta.outputs.labels }}
" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "
:pushpin: Context Variables References" >> ${GITHUB_STEP_SUMMARY} + echo "( job must run in workflow and needs: [context] mut be set for job)" >> ${GITHUB_STEP_SUMMARY} + echo "
    " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Helm chart version:
    ${{ needs.context.outputs.chart_version }}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Original registry:
    ${{ needs.context.outputs.original_registry }}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Original repository:
    ${{ needs.context.outputs.original_repo }}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Original tag:
    ${{ needs.context.outputs.original_tag }}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Original image:
    ${{ needs.context.outputs.original_image }}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Build registry:
    ${{ needs.context.outputs.build_registry }}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Build repository:
    ${{ needs.context.outputs.build_repo }}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Build tag (workflow):
    ${{ needs.context.outputs.build_tag }}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Ref tags:
    ${{ needs.context.outputs.ref_tags }}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Build image:
    ${{ needs.context.outputs.build_image }}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Build labels:
    ${{ needs.context.outputs.build_labels }}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
" >> ${GITHUB_STEP_SUMMARY} + echo "
" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "Let's start building :rocket:" >> ${GITHUB_STEP_SUMMARY} + shell: bash + diff --git a/.github/actions/grype/action.yaml b/.github/actions/grype/action.yaml new file mode 100644 index 0000000..058f4e5 --- /dev/null +++ b/.github/actions/grype/action.yaml @@ -0,0 +1,49 @@ +name: grype +description: 'Run Grype on image' +inputs: + image: + description: 'Image name' + required: true + registry: + description: 'Registry to login to pull image, e.g. "ghcr.io" for GHCR, leave empty if image is public' + required: false + default: '' + repo_owner: + description: 'Name of repository owner, e.g. "github.repository_owner" for ghcr.io' + required: false + repo_token: + description: 'Access token for repository owner, e.g. "secrets.GITHUB_TOKEN" for ghcr.io' + required: false + output: + description: 'Grype output either "sarif" (GITHUB_TOKEN with security-events:write) or print results as "table" and fail on error' + required: false +runs: + using: "composite" + steps: + - name: Login with registry + if: inputs.registry != '' + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0 + with: + registry: ${{ inputs.registry }} + username: ${{ inputs.repo_owner }} + password: ${{ inputs.repo_token }} + - name: Scan + if: inputs.output == 'table' + uses: anchore/scan-action@dafbc97d7259af88b61bd260f2fde565d0668a72 # v3.3.4 + with: + image: ${{ inputs.image }} + fail-build: true + output-format: table + - name: Scan + id: scan + if: inputs.output == 'sarif' + uses: anchore/scan-action@dafbc97d7259af88b61bd260f2fde565d0668a72 # v3.3.4 + with: + image: ${{ inputs.image }} + fail-build: false + output-format: sarif + - name: Upload + if: inputs.output == 'sarif' + uses: github/codeql-action/upload-sarif@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5 + with: + sarif_file: ${{ steps.scan.outputs.sarif }} diff --git a/.github/actions/k3s-cluster/action.yaml b/.github/actions/k3s-cluster/action.yaml new file mode 100644 index 0000000..8fca6a3 --- /dev/null +++ b/.github/actions/k3s-cluster/action.yaml @@ -0,0 +1,74 @@ +# Adjusted from https://github.com/jupyterhub/action-k3s-helm +--- +name: K3S with Helm +description: | + Install Kubernetes (K3S) and Helm. + +inputs: + k3s-channel: + description: K3S channel (https://update.k3s.io/v1-release/channels) + required: false + default: "" + +outputs: + kubeconfig: + description: Path to kubeconfig file + value: ${{ steps.set-versions.outputs.kubeconfig }} + k3s-version: + description: "Installed k3s version, such as v1.20.0+k3s2" + value: "${{ steps.set-versions.outputs.k3s-version }}" + k8s-version: + description: "Installed k8s version, such as v1.20.0" + value: "${{ steps.set-versions.outputs.k8s-version }}" + helm-version: + description: "Installed helm version, such as v3.4.2" + value: "${{ steps.set-versions.outputs.helm-version }}" + +runs: + using: "composite" + steps: + - name: Setup k3s ${{ inputs.k3s-channel }} + run: | + curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL="${{ inputs.k3s-channel }}" sh -s - + shell: bash + + # By providing a kubeconfig owned by the current user with 600 permissions, + # kubectl becomes usable without sudo, and helm won't emit warnings about + # bloated access to group/world. + - name: Prepare a kubeconfig in ~/.kube/config + run: | + mkdir -p ~/.kube + sudo cat /etc/rancher/k3s/k3s.yaml > "$HOME/.kube/config" + chmod 600 "$HOME/.kube/config" + echo "KUBECONFIG=$HOME/.kube/config" >> $GITHUB_ENV + shell: bash + + - name: Setup Helm + run: | + curl -sf https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash + shell: bash + + - name: Set version output + id: set-versions + run: | + echo "::group::Set version output" + echo "kubeconfig=$HOME/.kube/config" >> $GITHUB_OUTPUT + echo "k3s-version=$(k3s --version | grep 'k3s' | sed 's/.*\(v[0-9][^ ]*\).*/\1/')" >> $GITHUB_OUTPUT + echo "k8s-version=$(k3s --version | grep 'k3s' | sed 's/.*\(v[0-9][^+]*\).*/\1/')" >> $GITHUB_OUTPUT + echo "::endgroup::" + shell: bash + + - name: Wait for coredns, metrics server, traefik + run: | + # Wait for a few seconds to allow deployments spin up + sleep 10 + + kubectl rollout status --watch --timeout 300s deployment/coredns -n kube-system + + kubectl rollout status --watch --timeout 300s deployment/metrics-server -n kube-system + + kubectl wait --for=condition=complete --timeout=300s job/helm-install-traefik-crd -n kube-system || true + kubectl wait --for=condition=complete --timeout=300s job/helm-install-traefik -n kube-system || true + kubectl rollout status --watch --timeout 300s deployment/traefik -n kube-system + shell: bash + diff --git a/.github/actions/k8s-version-config/action.yaml b/.github/actions/k8s-version-config/action.yaml new file mode 100644 index 0000000..353475c --- /dev/null +++ b/.github/actions/k8s-version-config/action.yaml @@ -0,0 +1,25 @@ +name: k8s-version-config +description: 'action to prepare testing different k8s versions' +inputs: + k8s-version: + description: 'k8s version to be tested' + required: true +runs: + using: "composite" + steps: + - name: Install yq and bash + run: | + sudo snap install yq + sudo apt update + sudo apt install bash -y + shell: bash + - uses: ./.github/actions/k3s-cluster + with: + k3s-channel: ${{ inputs.k8s-version }} + - name: Adjust Configuration + run: | + if [[ $(echo "${{ inputs.k8s-version }}" | tail -c 3) -lt "19" ]]; then + yq e 'del(.kubernetes.deployment.securityContext.seccompProfile)' -i charts/semgr8s/values.yaml + yq e '.kubernetes.deployment.annotations."seccomp.security.alpha.kubernetes.io/pod" = "runtime/default"' -i charts/semgr8s/values.yaml + fi + shell: bash diff --git a/.github/actions/trivy-config/action.yaml b/.github/actions/trivy-config/action.yaml new file mode 100644 index 0000000..4e23b58 --- /dev/null +++ b/.github/actions/trivy-config/action.yaml @@ -0,0 +1,53 @@ +name: trivy-config +description: 'Run Trivy on config' +inputs: + output: + description: 'Trivy output either "sarif" (GITHUB_TOKEN with security-events:write) or print results as "table" and fail on error' + required: false +runs: + using: "composite" + steps: + - name: Create reports folder + run: | + mkdir reports + shell: bash + - name: Render Helm charts + run: | + mkdir deployment + helm template charts/connaisseur > deployment/deployment.yaml + shell: bash + - name: Scan deployment.yaml + uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f # v0.12.0 + if: inputs.output == 'table' + with: + scan-type: "config" + scan-ref: "deployment" + format: 'table' + - name: Scan Dockerfiles + uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f # v0.12.0 + if: inputs.output == 'table' + with: + scan-type: "config" + scan-ref: "build" + format: 'table' + - name: Scan deployment.yaml + uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f # v0.12.0 + if: inputs.output == 'sarif' + with: + scan-type: "config" + scan-ref: "deployment" + format: 'sarif' + output: 'reports/trivy-k8s-results.sarif' + - name: Scan Dockerfiles + uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f # v0.12.0 + if: inputs.output == 'sarif' + with: + scan-type: "config" + scan-ref: "build" + format: 'sarif' + output: 'reports/trivy-docker-results.sarif' + - name: Upload + uses: github/codeql-action/upload-sarif@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5 + if: inputs.output == 'sarif' + with: + sarif_file: 'reports' diff --git a/.github/actions/trivy-image/action.yaml b/.github/actions/trivy-image/action.yaml new file mode 100644 index 0000000..4fd2fd7 --- /dev/null +++ b/.github/actions/trivy-image/action.yaml @@ -0,0 +1,54 @@ +name: trivy-image +description: 'Run Trivy on image' +inputs: + image: + description: 'Image name' + required: true + registry: + description: 'Registry to login to pull image, e.g. "ghcr.io" for GHCR, leave empty if image is public' + required: false + default: '' + repo_owner: + description: 'Name of repository owner, e.g. "github.repository_owner" for ghcr.io' + required: false + repo_token: + description: 'Access token for repository owner, e.g. "secrets.GITHUB_TOKEN" for ghcr.io' + required: false + output: + description: 'Trivy output either "sarif" (GITHUB_TOKEN with security-events:write) or print results as "table" and fail on error' + required: false +runs: + using: "composite" + steps: + - name: Login with registry + if: inputs.registry != '' + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0 + with: + registry: ${{ inputs.registry }} + username: ${{ inputs.repo_owner }} + password: ${{ inputs.repo_token }} + - name: Create reports folder + run: | + mkdir reports + shell: sh + - name: Run Trivy on image + if: inputs.output == 'sarif' + uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f # v0.12.0 + with: + image-ref: ${{ inputs.image }} + scan-type: "image" + format: 'sarif' + output: 'reports/trivy-vuln-results.sarif' + - name: Run Trivy on image + if: inputs.output == 'table' + uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f # v0.12.0 + with: + image-ref: ${{ inputs.image }} + scan-type: "image" + exit-code: 1 + format: 'table' + - name: Upload + if: inputs.output == 'sarif' + uses: github/codeql-action/upload-sarif@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5 + with: + sarif_file: 'reports' diff --git a/.github/workflows/.reusable-build.yml b/.github/workflows/.reusable-build.yml new file mode 100644 index 0000000..75ec9c5 --- /dev/null +++ b/.github/workflows/.reusable-build.yml @@ -0,0 +1,99 @@ +name: build + +#permissions: {} #TODO: reactivate for non-private + +on: + workflow_call: + inputs: + skip: + description: "Want to skip running certain jobs 'none', 'non-required', 'all'?" + type: string + default: "none" + outputs: + cosign_public_key: + description: "Cosign public key used for signing Connaisseur image" + value: ${{ jobs.build.outputs.cosign_public_key }} + chart_version: + description: "Connaisseur Helm chart version" + value: ${{ jobs.context.outputs.chart_version }} + original_registry: + description: "Public Connaisseur registry" + value: ${{ jobs.context.outputs.original_registry }} + original_repo: + description: "Public Connaisseur repo" + value: ${{ jobs.context.outputs.original_repo }} + original_tag: + description: "Current Connaisseur tag, i.e. version" + value: ${{ jobs.context.outputs.original_tag }} + original_image: + description: "Full Connaisseur image reference, i.e. registry + repository + tag" + value: ${{ jobs.context.outputs.original_image }} + build_registry: + description: "Workflow build registry used for testing" + value: ${{ jobs.context.outputs.build_registry }} + build_repo: + description: "Workflow build repository used for testing" + value: ${{ jobs.context.outputs.build_repo }} + build_tag: + description: "Workflow build tag used for testing (unique for each run)" + value: ${{ jobs.context.outputs.build_tag }} + branch_tag: + description: "Branch tag used for all builds on branch" + value: ${{ jobs.context.outputs.branch_tag }} + build_image: + description: "Workflow build image used for testing, i.e. registry + repository + tag" + value: ${{ jobs.context.outputs.build_image }} + build_labels: + description: "Repository- and workflow-specific build labels" + value: ${{ jobs.context.outputs.build_labels }} + +jobs: + context: + runs-on: ubuntu-latest + if: inputs.skip != 'all' + # permissions: {} #TODO: reactivate for non-private + outputs: + chart_version: ${{ steps.get_context.outputs.chart_version }} + original_registry: ${{ steps.get_context.outputs.original_registry }} + original_repo: ${{ steps.get_context.outputs.original_repo }} + original_image: ${{ steps.get_context.outputs.original_image }} + original_tag: ${{ steps.get_context.outputs.original_tag }} + build_registry: ${{ steps.get_context.outputs.build_registry }} + build_repo: ${{ steps.get_context.outputs.build_repo }} + build_tag: ${{ steps.get_context.outputs.build_tag }} + ref_tags: ${{ steps.get_context.outputs.ref_tags }} + build_image: ${{ steps.get_context.outputs.build_image }} + build_labels: ${{ steps.get_context.outputs.build_labels }} + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Get context + id: get_context + uses: ./.github/actions/context + + build: + runs-on: ubuntu-latest + if: | + inputs.skip != 'non-required' && + inputs.skip != 'all' + needs: [context] + # permissions: #TODO: reactivate for non-private + # packages: write + outputs: + cosign_public_key: ${{ steps.build.outputs.cosign_public_key }} + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Build Connaisseur + id: build + uses: ./.github/actions/build + with: + image_registry: ${{ needs.context.outputs.build_registry }} + image_repo: ${{ needs.context.outputs.build_repo }} + image_tag: ${{ needs.context.outputs.build_tag }} + ref_tags: ${{ needs.context.outputs.ref_tags }} + image_labels: ${{ needs.context.outputs.build_labels }} + repo_owner: ${{ github.repository_owner }} + repo_token: ${{ secrets.GITHUB_TOKEN }} + cosign_private_key: ${{ secrets.COSIGN_PRIVATE_KEY }} + cosign_password: ${{ secrets.COSIGN_PASSWORD }} diff --git a/.github/workflows/.reusable-ci.yml b/.github/workflows/.reusable-ci.yml new file mode 100644 index 0000000..6c1393d --- /dev/null +++ b/.github/workflows/.reusable-ci.yml @@ -0,0 +1,177 @@ +name: ci + +#permissions: {} #TODO: reactivate for non-private + +on: + workflow_call: + inputs: + skip_build: + description: "Want to skip running certain build jobs 'none', 'non-required', 'all'?" + type: string + default: "all" + required: false + skip_compliance_checks: + description: "Want to skip running certain compliance jobs 'none', 'non-required', 'all'?" + type: string + default: "all" + required: false + skip_unit_tests: + description: "Want to skip running certain unit test jobs 'none', 'non-required', 'all'?" + type: string + default: "all" + required: false + skip_sast: + description: "Want to skip running certain sast jobs 'none', 'non-required', 'all'?" + type: string + default: "all" + required: false + skip_sca: + description: "Want to skip running certain sca jobs 'none', 'non-required', 'all'?" + type: string + default: "all" + required: false + skip_docs: + description: "Want to skip running certain docs jobs 'none', 'non-required', 'all'?" + type: string + default: "all" + required: false + skip_integration_tests: + description: "Want to skip running certain integration test jobs 'none', 'non-required', 'all'?" + type: string + default: "all" + required: false + output_type: + description: 'Output either "sarif" (GITHUB_TOKEN with security-events:write) or print results as "table" and fail on error' + type: string + default: 'sarif' + required: false + +defaults: + run: + shell: bash + +jobs: + conditionals: + runs-on: ubuntu-latest + outputs: + skip_build: ${{ steps.conditionals.outputs.skip_build }} + skip_compliance_checks: ${{ steps.conditionals.outputs.skip_compliance_checks }} + skip_unit_tests: ${{ steps.conditionals.outputs.skip_unit_tests }} + skip_sast: ${{ steps.conditionals.outputs.skip_sast }} + skip_sca: ${{ steps.conditionals.outputs.skip_sca }} + skip_docs: ${{ steps.conditionals.outputs.skip_docs }} + skip_integration_tests: ${{ steps.conditionals.outputs.skip_integration_tests }} + output_type: ${{ steps.conditionals.outputs.output_type }} + steps: + - name: CI conditionals + id: conditionals + run: | + echo "skip_build=${{ inputs.skip_build }}" >> ${GITHUB_OUTPUT} + echo "skip_compliance_checks=${{ inputs.skip_compliance_checks }}" >> ${GITHUB_OUTPUT} + echo "skip_unit_tests=${{ inputs.skip_unit_tests }}" >> ${GITHUB_OUTPUT} + echo "skip_sast=${{ inputs.skip_sast }}" >> ${GITHUB_OUTPUT} + echo "skip_sca=${{ inputs.skip_sca }}" >> ${GITHUB_OUTPUT} + echo "skip_docs=${{ inputs.skip_docs }}" >> ${GITHUB_OUTPUT} + echo "skip_integration_tests=${{ inputs.skip_integration_tests }}" >> ${GITHUB_OUTPUT} + echo "output_type=${{ inputs.output_type }}" >> ${GITHUB_OUTPUT} + - name: Show conditionals + id: show_conditionals + run: | + get_output() { case "$1" in "none") echo ":white_check_mark:";; "non-required") echo ":information_source:";; "all") echo ":x:";; *) echo "Unknown value";; esac; } + echo "# :pencil: CI Settings" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + echo "
SettingValue
Run Docs$(get_output ${{ steps.conditionals.outputs.skip_docs }})
Run Build$(get_output ${{ steps.conditionals.outputs.skip_build }})
Run Compliance$(get_output ${{ steps.conditionals.outputs.skip_compliance_checks }})
Run Unit Tests$(get_output ${{ steps.conditionals.outputs.skip_unit_tests }})
Run SAST$(get_output ${{ steps.conditionals.outputs.skip_sast }})
Run SCA$(get_output ${{ steps.conditionals.outputs.skip_sca }})
Run Integration Tests$(get_output ${{ steps.conditionals.outputs.skip_integration_tests }})
Report type${{ steps.conditionals.outputs.output_type }}
" >> ${GITHUB_STEP_SUMMARY} + echo "($(get_output 'none') - run all jobs, $(get_output 'non-required') - run important/required jobs only, $(get_output 'all') - skip jobs)" >> ${GITHUB_STEP_SUMMARY} + echo "" >> ${GITHUB_STEP_SUMMARY} + + build: + uses: ./.github/workflows/.reusable-build.yml + needs: [conditionals] + # permissions: #TODO: reactivate for non-private + # packages: write + secrets: inherit + with: + skip: ${{ needs.conditionals.outputs.skip_build }} + + compliance: + uses: ./.github/workflows/.reusable-compliance.yml + needs: [conditionals] + # permissions: #TODO: reactivate for non-private + # contents: write + # id-token: write + # security-events: write + # actions: read + # checks: read + # deployments: read + # issues: read + # discussions: read + # packages: read + # pages: read + # pull-requests: read + # repository-projects: read + # statuses: read + secrets: inherit + with: + skip: ${{ needs.conditionals.outputs.skip_compliance_checks }} + + unit-test: + uses: ./.github/workflows/.reusable-unit-test.yml + needs: [conditionals] + with: + skip: ${{ needs.conditionals.outputs.skip_unit_tests }} + + sast: + uses: ./.github/workflows/.reusable-sast.yml + needs: [conditionals] + # permissions: #TODO: reactivate for non-private + # security-events: write + # pull-requests: read + with: + skip: ${{ needs.conditionals.outputs.skip_sast }} + output: ${{ needs.conditionals.outputs.output_type }} + + sca: + uses: ./.github/workflows/.reusable-sca.yml + needs: [conditionals, build] + # permissions: #TODO: reactivate for non-private + # contents: write + # security-events: write + # packages: read + secrets: inherit + with: + registry: ${{ needs.build.outputs.build_registry }} + repo_owner: ${{ github.repository_owner }} + image: ${{ needs.build.outputs.build_image }} + skip: ${{ needs.conditionals.outputs.skip_sca }} + output: ${{ needs.conditionals.outputs.output_type }} + + docs: + uses: ./.github/workflows/.reusable-docs.yaml + needs: [conditionals] + # permissions: #TODO: reactivate for non-private + # contents: write + with: + skip: ${{ needs.conditionals.outputs.skip_docs }} + + integration-test: + uses: ./.github/workflows/.reusable-integration-test.yml + needs: [conditionals, build] + # permissions: #TODO: reactivate for non-private + # packages: read + secrets: inherit + with: + build_registry: ${{ needs.build.outputs.build_registry }} + repo_owner: ${{ github.repository_owner }} + build_image_repository: ${{ needs.build.outputs.build_registry }}/${{ needs.build.outputs.build_repo }} + build_tag: ${{ needs.build.outputs.build_tag }} + skip: ${{ needs.conditionals.outputs.skip_integration_tests }} + cosign_public_key: ${{ needs.build.outputs.cosign_public_key }} diff --git a/.github/workflows/.reusable-cleanup-registry.yml b/.github/workflows/.reusable-cleanup-registry.yml new file mode 100644 index 0000000..2bef523 --- /dev/null +++ b/.github/workflows/.reusable-cleanup-registry.yml @@ -0,0 +1,40 @@ +name: cleanup registry + +on: + workflow_call: + +permissions: {} + +jobs: + cleanup-registry: + runs-on: ubuntu-latest + steps: + - name: Cleanup test images in 'connaisseur-test' + uses: snok/container-retention-policy@3d27e6a0361deed0b7dc5099a82eadd07924b177 # v2.1.3 + with: + image-names: connaisseur-test + cut-off: three weeks ago UTC+1 + timestamp-to-use: updated_at + account-type: org + org-name: sse-secure-systems + token: ${{ secrets.GHCR_PAT }} + - name: Cleanup dangling images without tag + uses: snok/container-retention-policy@3d27e6a0361deed0b7dc5099a82eadd07924b177 # v2.1.3 + with: + image-names: connaisseur* + untagged-only: true + cut-off: four hours ago UTC+1 + timestamp-to-use: updated_at + account-type: org + org-name: sse-secure-systems + token: ${{ secrets.GHCR_PAT }} + - name: Cleanup all connaisseur images + uses: snok/container-retention-policy@3d27e6a0361deed0b7dc5099a82eadd07924b177 # v2.1.3 + with: + image-names: connaisseur + skip-tags: master, develop, v*, sha256-* + cut-off: four days ago UTC+1 + timestamp-to-use: updated_at + account-type: org + org-name: sse-secure-systems + token: ${{ secrets.GHCR_PAT }} diff --git a/.github/workflows/.reusable-compliance.yml b/.github/workflows/.reusable-compliance.yml new file mode 100644 index 0000000..1f9a8cc --- /dev/null +++ b/.github/workflows/.reusable-compliance.yml @@ -0,0 +1,84 @@ +name: compliance + +on: + workflow_call: + inputs: + skip: + description: "Want to skip running certain jobs 'none', 'non-required', 'all'?" + type: string + default: "none" + +#permissions: read-all + +jobs: + ossf-scorecard: + runs-on: ubuntu-latest + if: | + (github.ref_name == 'master' || github.event_name == 'pull_request') && + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # security-events: write + # id-token: write + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + - name: Analyze + uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + with: + results_file: results.sarif + results_format: sarif + repo_token: ${{ secrets.SCORECARD_TOKEN }} + publish_results: ${{ github.ref_name == 'master' }} + - name: Upload + uses: github/codeql-action/upload-sarif@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7 + with: + sarif_file: results.sarif + + dependency-review: + name: dependency review + runs-on: ubuntu-latest + if: | + github.event_name == 'pull_request' && + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # contents: write + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Review + uses: actions/dependency-review-action@7bbfa034e752445ea40215fff1c3bf9597993d3f # v3.1.3 + + check-commit-message: + runs-on: ubuntu-latest + if: | + github.event_name == 'pull_request' && + inputs.skip != 'all' + # permissions: {} #TODO: reactivate for non-private + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: ${{ github.event.pull_request.head.sha }} # Otherwise will checkout merge commit, which isn't conform + fetch-depth: ${{ github.event.pull_request.commits }} # Fetch all commits of the MR, but only those + - name: Check commit messages for conformity + run: | + echo "Commits between dev branch and current SHA:" + COMMITS=$(git log --pretty=%H) + echo "${COMMITS}" + EXIT=0 + COMMIT_MSGS=$(git log --pretty=%s) # show subject only + for commit in ${COMMITS}; do + MSG=$(git log ${commit} -n1 --pretty=%s) + TYPE=$(echo ${MSG} | awk '{{ print $1 }}') + if ! [[ "${TYPE}" =~ ^(build|ci|docs|feat|fix|refactor|test|update):$ ]]; then + EXIT=1 + echo "Commit message of commit ${commit} doesn't conform to 'type: msg' format:" + echo "${MSG}" + echo "-------------------------" + fi + done + exit ${EXIT} diff --git a/.github/workflows/.reusable-docs.yaml b/.github/workflows/.reusable-docs.yaml new file mode 100644 index 0000000..3e5fc0e --- /dev/null +++ b/.github/workflows/.reusable-docs.yaml @@ -0,0 +1,43 @@ +name: docs + +#permissions: {} #TODO: reactivate for non-private + +on: + workflow_call: + inputs: + skip: + description: "Want to skip running certain jobs 'none', 'non-required', 'all'?" + type: string + default: "none" + +jobs: + deploy: + runs-on: ubuntu-latest + if: inputs.skip != 'all' + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + - name: Set release env + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + - name: Configure the git user + run: | + git config user.name "versioning_user" + git config user.email "connaisseur@securesystems.dev" + - name: Install + run: | + pip install -r docs/requirements_docs.txt + - name: Deploy + if: inputs.skip != 'non-required' + run: | + if [[ "${GITHUB_REF}" == "refs/tags/v"* ]]; + then + mike deploy --push --update-aliases ${RELEASE_VERSION} latest + elif [[ "${GITHUB_REF}" == "refs/heads/develop" ]]; then + mike deploy --push ${RELEASE_VERSION} + else + mkdocs build + fi diff --git a/.github/workflows/.reusable-integration-test.yml b/.github/workflows/.reusable-integration-test.yml new file mode 100644 index 0000000..f98cd13 --- /dev/null +++ b/.github/workflows/.reusable-integration-test.yml @@ -0,0 +1,344 @@ +name: integration-test + +#permissions: {} #TODO: reactivate for non-private + +on: + workflow_call: + inputs: + build_registry: + description: "Workflow build registry used for testing" + type: string + repo_owner: + description: 'Name of repository owner, e.g. "inputs.repo_owner" for ghcr.io' + type: string + build_image_repository: + description: "Workflow build image used for testing, excluding the tag i.e. registry + repository" + type: string + build_tag: + description: "Tag of build image used for testing" + type: string + skip: + description: "Want to skip running certain jobs 'none', 'non-required', 'all'?" + type: string + default: "none" + cosign_public_key: + description: "Cosign public key used for signing the build image" + type: string + +env: + IMAGEPULLSECRET: dockerconfigjson-ghcr + +jobs: + integration-test: + name: functional + runs-on: ubuntu-latest + if: inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # packages: read + env: + IMAGE: ${{ inputs.build_image_repository }} + TAG: ${{ inputs.build_tag }} + COSIGN_PUBLIC_KEY: ${{ inputs.cosign_public_key }} + strategy: + fail-fast: false + matrix: + integration-test-arg: + [ + "regular", + "cosign", + "multi-cosigned", + "rekor-cosigned", + "namespace-val", + "deployment", + "pre-config", + "other-ns", + "configured-cert", + ] + services: + alerting-endpoint: + image: securesystemsengineering/alerting-endpoint + ports: + - 56243:56243 + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Login with registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ inputs.build_registry }} + username: ${{ inputs.repo_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install yq + run: | + sudo snap install yq + - uses: ./.github/actions/k8s-version-config + name: Setup k8s cluster + with: + k8s-version: v1.25 + - name: Get alerting endpoint IP + id: get_ip + uses: ./.github/actions/alerting-endpoint + - name: Run test + run: | + bash tests/integration/integration-test.sh "${{ matrix.integration-test-arg }}" + env: + ALERTING_ENDPOINT_IP: ${{ steps.get_ip.outputs.ip }} + - name: Display Connaisseur configuration + if: always() + run: | + echo "::group::values.yaml" + yq e '... comments=""' charts/connaisseur/values.yaml + echo "::endgroup::" + - name: Display k8s state if integration test failed + if: failure() + run: | + kubectl describe deployments.apps -n connaisseur -lapp.kubernetes.io/name=connaisseur + kubectl describe pods -n connaisseur -lapp.kubernetes.io/name=connaisseur + - name: Display logs if integration test failed + if: failure() + run: | + kubectl logs -n connaisseur -lapp.kubernetes.io/name=connaisseur --prefix=true --tail=-1 + + optional-integration-test: + name: optional + runs-on: ubuntu-latest + if: | + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # packages: read + env: + IMAGE: ${{ inputs.build_image_repository }} + TAG: ${{ inputs.build_tag }} + COSIGN_PUBLIC_KEY: ${{ inputs.cosign_public_key }} + strategy: + fail-fast: false + matrix: + integration-test-arg: + [ + "complexity", + "load", + "upgrade", + ] + services: + alerting-endpoint: + image: securesystemsengineering/alerting-endpoint + ports: + - 56243:56243 + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Login with registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ inputs.build_registry }} + username: ${{ inputs.repo_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install yq + run: | + sudo snap install yq + - uses: ./.github/actions/k8s-version-config + name: Setup k8s cluster + with: + k8s-version: v1.25 + - name: Get alerting endpoint IP + id: get_ip + uses: ./.github/actions/alerting-endpoint + - name: Run test + run: | + bash tests/integration/integration-test.sh "${{ matrix.integration-test-arg }}" + env: + ALERTING_ENDPOINT_IP: ${{ steps.get_ip.outputs.ip }} + - name: Display Connaisseur configuration + if: always() + run: | + echo "::group::values.yaml" + yq e '... comments=""' charts/connaisseur/values.yaml + echo "::endgroup::" + - name: Display k8s state if integration test failed + if: failure() + run: | + kubectl describe deployments.apps -n connaisseur -lapp.kubernetes.io/name=connaisseur + kubectl describe pods -n connaisseur -lapp.kubernetes.io/name=connaisseur + - name: Display logs if integration test failed + if: failure() + run: | + kubectl logs -n connaisseur -lapp.kubernetes.io/name=connaisseur --prefix=true --tail=-1 + + k8s-versions: + name: k8s versions + runs-on: ubuntu-latest + if: inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # packages: read + env: + IMAGE: ${{ inputs.build_image_repository }} + TAG: ${{ inputs.build_tag }} + COSIGN_PUBLIC_KEY: ${{ inputs.cosign_public_key }} + strategy: + fail-fast: false + matrix: + k8s-version: [ + "v1.25", + "v1.26", + "v1.27", + "v1.28", + ] + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Login with registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ inputs.build_registry }} + username: ${{ inputs.repo_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install yq + run: | + sudo snap install yq + - uses: ./.github/actions/k8s-version-config + name: Setup k8s cluster + with: + k8s-version: ${{ matrix.k8s-version }} + - name: Run pre-config and workload integration tests + run: | + bash tests/integration/integration-test.sh "pre-and-workload" + - name: Display k8s state and logs if integration test failed + if: failure() + run: | + kubectl describe deployments.apps -n connaisseur -lapp.kubernetes.io/name=connaisseur + kubectl describe pods -n connaisseur -lapp.kubernetes.io/name=connaisseur + kubectl logs -n connaisseur -lapp.kubernetes.io/name=connaisseur --prefix=true --tail=-1 + - name: Display Connaisseur configuration + if: always() + run: | + echo "::group::values.yaml" + yq e '... comments=""' charts/connaisseur/values.yaml + echo "::endgroup::" + + optional-k8s-versions: + name: optional k8s versions + runs-on: ubuntu-latest + if: | + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # packages: read + env: + IMAGE: ${{ inputs.build_image_repository }} + TAG: ${{ inputs.build_tag }} + COSIGN_PUBLIC_KEY: ${{ inputs.cosign_public_key }} + strategy: + fail-fast: false + matrix: + k8s-version: [ + "v1.20", + "v1.21", + "v1.22", + "v1.23", + "v1.24", + ] + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Login with registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ inputs.build_registry }} + username: ${{ inputs.repo_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install yq + run: | + sudo snap install yq + - uses: ./.github/actions/k8s-version-config + name: Setup k8s cluster + with: + k8s-version: ${{ matrix.k8s-version }} + - name: Run pre-config and workload integration tests + run: | + bash tests/integration/integration-test.sh "pre-and-workload" + - name: Display k8s state and logs if integration test failed + if: failure() + run: | + kubectl describe deployments.apps -n connaisseur -lapp.kubernetes.io/name=connaisseur + kubectl describe pods -n connaisseur -lapp.kubernetes.io/name=connaisseur + kubectl logs -n connaisseur -lapp.kubernetes.io/name=connaisseur --prefix=true --tail=-1 + - name: Display Connaisseur configuration + if: always() + run: | + echo "::group::values.yaml" + yq e '... comments=""' charts/connaisseur/values.yaml + echo "::endgroup::" + + self-hosted-notary: + name: self-hosted-notary + runs-on: ubuntu-latest + if: | + inputs.skip != 'non-required' && + inputs.skip != 'all' + permissions: + packages: read + env: + IMAGE: ${{ inputs.build_image_repository }} + TAG: ${{ inputs.build_tag }} + COSIGN_PUBLIC_KEY: ${{ inputs.cosign_public_key }} + strategy: + fail-fast: false + matrix: + integration-test-arg: + [ + "self-hosted-notary" + ] + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Login with registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ inputs.build_registry }} + username: ${{ inputs.repo_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install yq + run: | + sudo snap install yq + - name: Setup notary signer instance + run: | + docker run -d -p 7899:7899 -v ./tests/data/notary_service_container/signer:/etc/docker/notary-signer/ notary:signer -config=/etc/docker/notary-signer/config.json + - name: Get notary signer instance IP + id: get_notary_signer_ip + uses: ./.github/actions/notary-signer-ip + - name: Setup notary server instance + run: | + docker run -d -p 4443:4443 --add-host notary.signer:${{ steps.get_notary_signer_ip.outputs.notary_signer_ip }} -v ./tests/data/notary_service_container/server:/etc/docker/notary-server notary:server -config=/etc/docker/notary-server/config.json -logf=json + - name: Get container IPs + id: get_notary_server_ip + uses: ./.github/actions/notary-server-ip + - name: Populate notary instance with trust data + uses: ./.github/actions/setup-notary + id: setup_notary + env: + NOTARY_IP: ${{ steps.get_notary_server_ip.outputs.notary_ip }} + - uses: ./.github/actions/k8s-version-config + name: Setup k8s cluster + with: + k8s-version: v1.28 + - name: Run test + run: | + bash tests/integration/integration-test.sh "${{ matrix.integration-test-arg }}" + env: + NOTARY_IP: ${{ steps.get_notary_server_ip.outputs.notary_ip }} + - name: Display Connaisseur configuration + if: always() + run: | + echo "::group::values.yaml" + yq e '... comments=""' charts/semgr8s/values.yaml + echo "::endgroup::" + - name: Display k8s state if integration test failed + if: failure() + run: | + kubectl describe deployments.apps -n connaisseur -lapp.kubernetes.io/name=connaisseur + kubectl describe pods -n connaisseur -lapp.kubernetes.io/name=connaisseur + - name: Display logs if integration test failed + if: failure() + run: | + kubectl logs -n connaisseur -lapp.kubernetes.io/name=connaisseur --prefix=true --tail=-1 diff --git a/.github/workflows/.reusable-sast.yml b/.github/workflows/.reusable-sast.yml new file mode 100644 index 0000000..e673831 --- /dev/null +++ b/.github/workflows/.reusable-sast.yml @@ -0,0 +1,227 @@ +name: sast + +on: + workflow_call: + inputs: + skip: + description: "Want to skip running certain jobs 'none', 'non-required', 'all'?" + type: string + default: "none" + output: + description: 'Output either "sarif" (GITHUB_TOKEN with security-events:write) or print results as "table" and fail on error' + type: string + required: false + default: 'sarif' + +#permissions: {} #TODO: reactivate for non-private + +jobs: + checkov: + runs-on: ubuntu-latest + if: | + (github.actor != 'dependabot[bot]') && + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # security-events: write + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Render Helm charts + run: | + rm -rf test # remove 'test' folder from scan #TODO: fix once final + rm -rf tests # remove 'tests' folder from scan + mkdir deployment + helm template charts/connaisseur > deployment/deployment.yaml + shell: bash + - name: Scan + if: inputs.output == 'table' + uses: bridgecrewio/checkov-action@558f721c4bd65a6fc59b02448ffc792eb721cb9b # v12.2580.0 + with: + output_format: cli + soft_fail: false + - name: Scan + if: inputs.output == 'sarif' + uses: bridgecrewio/checkov-action@558f721c4bd65a6fc59b02448ffc792eb721cb9b # v12.2580.0 + with: + output_file_path: console,checkov-results.sarif + output_format: cli,sarif + soft_fail: true + - name: Upload + if: inputs.output == 'sarif' + uses: github/codeql-action/upload-sarif@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7 + with: + sarif_file: checkov-results.sarif + + codeql: + runs-on: ubuntu-latest + if: | + (github.actor != 'dependabot[bot]') && + inputs.skip != 'non-required' && + inputs.skip != 'all' && + inputs.output == 'sarif' + # permissions: #TODO: reactivate for non-private + # pull-requests: read + # security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Initialize CodeQL + uses: github/codeql-action/init@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7 + with: + languages: 'go' + - name: Analyze + uses: github/codeql-action/analyze@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7 + + golangci-lint: + runs-on: ubuntu-latest + if: | + (github.actor != 'dependabot[bot]') && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # security-events: write + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + cache: false + go-version: '1.21' + - name: Analyze + uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 + with: + version: latest + args: '--timeout=10m --skip-dirs="test" --tests=false' + + gosec: + runs-on: ubuntu-latest + if: | + (github.actor != 'dependabot[bot]') && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # security-events: write + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Analyze + uses: securego/gosec@55d79496019a560e16e73e1948dee20a1fad631a # v2.18.2 + if: inputs.output == 'table' + with: + args: '-fmt text -exclude-dir=test -exclude-dir=tools ./...' + - name: Analyze + uses: securego/gosec@55d79496019a560e16e73e1948dee20a1fad631a # v2.18.2 + if: inputs.output == 'sarif' + with: + args: '-exclude-dir=test -exclude-dir=tools -no-fail -fmt sarif -out gosec-results.sarif ./...' + - name: Upload + uses: github/codeql-action/upload-sarif@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7 + if: inputs.output == 'sarif' + with: + sarif_file: 'gosec-results.sarif' + + hadolint: + runs-on: ubuntu-latest + if: | + (github.actor != 'dependabot[bot]') && + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # security-events: write + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Scan + uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0 + if: inputs.output == 'table' + with: + dockerfile: build/Dockerfile + format: tty + no-fail: false + - name: Scan + uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0 + if: inputs.output == 'sarif' + with: + dockerfile: build/Dockerfile + format: sarif + no-fail: true + output-file: hadolint-results.sarif + - name: Upload + uses: github/codeql-action/upload-sarif@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7 + if: inputs.output == 'sarif' + with: + sarif_file: 'hadolint-results.sarif' + + kubelinter: + runs-on: ubuntu-latest + if: | + (github.actor != 'dependabot[bot]') && + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # security-events: write + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Scan + uses: stackrox/kube-linter-action@ca0d55b925470deb5b04b556e6c4276ea94d03c3 # v1.0.4 + if: inputs.output == 'table' + with: + config: .kube-linter/config.yaml + directory: charts/connaisseur + format: plain + - name: Scan + uses: stackrox/kube-linter-action@ca0d55b925470deb5b04b556e6c4276ea94d03c3 # v1.0.4 + if: inputs.output == 'sarif' + with: + config: .kube-linter/config.yaml + directory: charts/connaisseur + format: sarif + output-file: kubelinter-results.sarif + - name: Upload + uses: github/codeql-action/upload-sarif@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7 + if: inputs.output == 'sarif' + with: + sarif_file: 'kubelinter-results.sarif' + + semgrep: + runs-on: ubuntu-latest + if: | + (github.actor != 'dependabot[bot]') && + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # security-events: write + container: + image: returntocorp/semgrep + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Scan + if: inputs.output == 'table' + run: semgrep ci --config=auto --suppress-errors --text + - name: Scan + if: inputs.output == 'sarif' + run: semgrep ci --config=auto --suppress-errors --sarif --output=semgrep-results.sarif || exit 0 + - name: Upload + uses: github/codeql-action/upload-sarif@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7 + if: inputs.output == 'sarif' + with: + sarif_file: semgrep-results.sarif + + trivy-config-scan: + name: trivy config + runs-on: ubuntu-latest + if: | + (github.actor != 'dependabot[bot]') && + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # security-events: write + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Run Trivy + uses: ./.github/actions/trivy-config + with: + output: ${{ inputs.output }} + diff --git a/.github/workflows/.reusable-sca.yml b/.github/workflows/.reusable-sca.yml new file mode 100644 index 0000000..66c5d76 --- /dev/null +++ b/.github/workflows/.reusable-sca.yml @@ -0,0 +1,100 @@ +name: sca + +#permissions: {} #TODO: reactivate for non-private + +on: + workflow_call: + inputs: + image: + description: "Image used for testing, i.e. registry + repository + tag" + type: string + required: true + registry: + description: 'Registry to login to pull image, e.g. "ghcr.io" for GHCR, leave empty if image is public' + type: string + required: false + default: '' + repo_owner: + description: 'Name of repository owner, e.g. "github.repository_owner" for ghcr.io' + type: string + required: false + default: '' + skip: + description: "Want to skip running certain jobs 'none', 'non-required', 'all'?" + type: string + default: "none" + output: + description: 'Output either "sarif" (GITHUB_TOKEN with security-events:write) or print results as "table" and fail on error' + type: string + required: false + default: 'sarif' + +jobs: + trivy-image-scan: + name: trivy image + runs-on: ubuntu-latest + if: inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # packages: read + # security-events: write + container: + image: docker:stable + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Run + uses: ./.github/actions/trivy-image + with: + image: ${{ inputs.image }} + registry: ${{ inputs.registry }} + repo_owner: ${{ inputs.repo_owner }} + repo_token: ${{ secrets.GITHUB_TOKEN }} + output: ${{ inputs.output }} + + grype: + name: grype + runs-on: ubuntu-latest + if: | + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # packages: read + # security-events: write + container: + image: docker:stable + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Run + uses: ./.github/actions/grype + with: + image: ${{ inputs.image }} + registry: ${{ inputs.registry }} + repo_owner: ${{ inputs.repo_owner }} + repo_token: ${{ secrets.GITHUB_TOKEN }} + output: ${{ inputs.output }} + +# WIP: Syft issue seems to cause error (https://github.com/anchore/syft/issues/1622) + dependency-submission: + name: syft / dependency review + runs-on: ubuntu-latest + if: | + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # packages: read + # contents: write + steps: + - name: Login with registry + if: inputs.registry != '' + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ inputs.registry }} + username: ${{ inputs.repo_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Run + uses: anchore/sbom-action@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1 # v0.14.3 + with: + image: ${{ inputs.image }} + format: cyclonedx-json + dependency-snapshot: ${{ inputs.output == 'sarif' }} diff --git a/.github/workflows/.reusable-unit-test.yml b/.github/workflows/.reusable-unit-test.yml new file mode 100644 index 0000000..b33fb28 --- /dev/null +++ b/.github/workflows/.reusable-unit-test.yml @@ -0,0 +1,32 @@ +name: unit-test + +#permissions: {} #TODO: reactivate for non-private + +on: + workflow_call: + inputs: + skip: + description: "Want to skip running certain jobs 'none', 'non-required', 'all'?" + type: string + default: "none" + +jobs: + gotest: + name: unit tests + runs-on: ubuntu-latest + if: inputs.skip != 'all' + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Setup + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version: '1.21' + - name: Test + if: inputs.skip == 'non-required' + run: go test ./cmd/... -coverprofile=coverage.out -covermode=atomic + - name: Test + if: inputs.skip != 'non-required' + run: go test ./... -race -coverprofile=coverage.out -covermode=atomic + - name: Upload + uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..413459d --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,29 @@ +name: pr + +#permissions: {} #TODO: reactivate for non-private + +on: + pull_request: + branches: + - main + - dev + +defaults: + run: + shell: bash + +jobs: + ci: + uses: ./.github/workflows/.reusable-ci.yml + # permissions: #TODO: adjust for non-private + secrets: inherit + with: + #TODO: adjust for non private + skip_build: 'none' + skip_compliance_checks: 'all' + skip_unit_tests: 'all' + skip_sast: 'all' + skip_sca: 'all' + skip_docs: 'all' + skip_integration_tests: 'all' + output_type: 'sarif' diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 0000000..ebd4cb8 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,29 @@ +name: push + +#permissions: {} #TODO: reactivate for non-private + +on: + push: + branches: + - main + - dev + +defaults: + run: + shell: bash + +jobs: + ci: + uses: ./.github/workflows/.reusable-ci.yml + # permissions: #TODO: adjust for non-private + secrets: inherit + with: + #TODO: adjust for non private + skip_build: 'none' + skip_compliance_checks: 'none' + skip_unit_tests: 'all' + skip_sast: 'all' + skip_sca: 'all' + skip_docs: 'all' + skip_integration_tests: 'all' + output_type: 'sarif' diff --git a/Makefile b/Makefile index 22aa7d4..c078174 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ webhookName := semgr8s -image := $(shell yq e '.deployment.image.repository' helm/values.yaml) -version := $(shell yq e '.appVersion' helm/Chart.yaml) +image := $(shell yq e '.deployment.image.repository' charts/semgr8s/values.yaml) +version := $(shell yq e '.appVersion' charts/semgr8s/Chart.yaml) tag := $(image):$(version) ns := semgr8ns @@ -12,7 +12,7 @@ build: @echo "####################" @echo "## $(@)" @echo "####################" - docker buildx build --platform=linux/amd64 -t $(tag) -f docker/Dockerfile . + docker buildx build --platform=linux/amd64 -t $(tag) -f build/Dockerfile . .PHONY:push push: diff --git a/README.md b/README.md index 79fe996..e6cb536 100644 --- a/README.md +++ b/README.md @@ -65,10 +65,10 @@ cd semgr8s Semgr8s comes preconfigured with some basic rules. However, configuration can be adjusted to your needs: -- Central configuration is maintained in `helm/values.yaml`. +- Central configuration is maintained in `charts/semgr8s/values.yaml`. - Configuration aims to provide the most native integration of Semgrep's functionality into Kubernetes. Working knowledge of Kubernetes and the [Semgrep documentation](https://semgrep.dev/docs/) should be sufficient to understand the concepts and options being used here. -- [Remote Semgrep](https://registry.semgrep.dev/rule) rules, rulesets, [repository rules](https://github.com/returntocorp/semgrep-rules) are configured via `.application.remoteRules` in `helm/values.yaml`, e.g. set to `"r/yaml.kubernetes.security.allow-privilege-escalation.allow-privilege-escalation"` or `"p/kubernetes"`, or `"r/yaml.kubernetes"` respectively. -- [Custom Semgrep rules](https://semgrep.dev/docs/writing-rules/overview/) can placed in `helm/rules/` and will be auto-mounted into the admission controller. +- [Remote Semgrep](https://registry.semgrep.dev/rule) rules, rulesets, [repository rules](https://github.com/returntocorp/semgrep-rules) are configured via `.application.remoteRules` in `charts/semgr8s/values.yaml`, e.g. set to `"r/yaml.kubernetes.security.allow-privilege-escalation.allow-privilege-escalation"` or `"p/kubernetes"`, or `"r/yaml.kubernetes"` respectively. +- [Custom Semgrep rules](https://semgrep.dev/docs/writing-rules/overview/) can placed in `charts/semgr8s/rules/` and will be auto-mounted into the admission controller. - Semgrep provides online tools to [learn](https://semgrep.dev/learn) and [create](https://semgrep.dev/playground/new) custom rules. To deploy the preconfigured admission controller simply run: @@ -119,7 +119,7 @@ Once all resources are in `READY` state, you have successfully installed semgr8s ### Testing Several test resources are provided under `tests/`. -Semgr8s denies creating pods with insecure configuration according to the rules in `helm/rules`: +Semgr8s denies creating pods with insecure configuration according to the rules in `charts/semgr8s/rules`: ```bash kubectl create -f tests/failing_deployment.yaml diff --git a/docker/Dockerfile b/build/Dockerfile similarity index 100% rename from docker/Dockerfile rename to build/Dockerfile diff --git a/helm/Chart.yaml b/charts/semgr8s/Chart.yaml similarity index 100% rename from helm/Chart.yaml rename to charts/semgr8s/Chart.yaml diff --git a/helm/rules/allow-privilege-escalation-no-securitycontext.yaml b/charts/semgr8s/rules/allow-privilege-escalation-no-securitycontext.yaml similarity index 100% rename from helm/rules/allow-privilege-escalation-no-securitycontext.yaml rename to charts/semgr8s/rules/allow-privilege-escalation-no-securitycontext.yaml diff --git a/helm/rules/hostnetwork-pod.yaml b/charts/semgr8s/rules/hostnetwork-pod.yaml similarity index 100% rename from helm/rules/hostnetwork-pod.yaml rename to charts/semgr8s/rules/hostnetwork-pod.yaml diff --git a/helm/rules/privileged-container.yaml b/charts/semgr8s/rules/privileged-container.yaml similarity index 100% rename from helm/rules/privileged-container.yaml rename to charts/semgr8s/rules/privileged-container.yaml diff --git a/helm/rules/run-as-non-root.yaml b/charts/semgr8s/rules/run-as-non-root.yaml similarity index 100% rename from helm/rules/run-as-non-root.yaml rename to charts/semgr8s/rules/run-as-non-root.yaml diff --git a/helm/templates/NOTES.txt b/charts/semgr8s/templates/NOTES.txt similarity index 100% rename from helm/templates/NOTES.txt rename to charts/semgr8s/templates/NOTES.txt diff --git a/helm/templates/_helpers.tpl b/charts/semgr8s/templates/_helpers.tpl similarity index 100% rename from helm/templates/_helpers.tpl rename to charts/semgr8s/templates/_helpers.tpl diff --git a/helm/templates/deployment.yaml b/charts/semgr8s/templates/deployment.yaml similarity index 100% rename from helm/templates/deployment.yaml rename to charts/semgr8s/templates/deployment.yaml diff --git a/helm/templates/env.yaml b/charts/semgr8s/templates/env.yaml similarity index 100% rename from helm/templates/env.yaml rename to charts/semgr8s/templates/env.yaml diff --git a/helm/templates/role.yaml b/charts/semgr8s/templates/role.yaml similarity index 100% rename from helm/templates/role.yaml rename to charts/semgr8s/templates/role.yaml diff --git a/helm/templates/rolebinding.yaml b/charts/semgr8s/templates/rolebinding.yaml similarity index 100% rename from helm/templates/rolebinding.yaml rename to charts/semgr8s/templates/rolebinding.yaml diff --git a/helm/templates/rules.yaml b/charts/semgr8s/templates/rules.yaml similarity index 100% rename from helm/templates/rules.yaml rename to charts/semgr8s/templates/rules.yaml diff --git a/helm/templates/service.yaml b/charts/semgr8s/templates/service.yaml similarity index 100% rename from helm/templates/service.yaml rename to charts/semgr8s/templates/service.yaml diff --git a/helm/templates/serviceaccount.yaml b/charts/semgr8s/templates/serviceaccount.yaml similarity index 100% rename from helm/templates/serviceaccount.yaml rename to charts/semgr8s/templates/serviceaccount.yaml diff --git a/helm/templates/webhook.yaml b/charts/semgr8s/templates/webhook.yaml similarity index 100% rename from helm/templates/webhook.yaml rename to charts/semgr8s/templates/webhook.yaml diff --git a/helm/values.yaml b/charts/semgr8s/values.yaml similarity index 100% rename from helm/values.yaml rename to charts/semgr8s/values.yaml diff --git a/helm/.helmignore b/helm/.helmignore deleted file mode 100644 index 0e8a0eb..0000000 --- a/helm/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ From 26484748f90cd58e24a662ea5fd5d74199af773a Mon Sep 17 00:00:00 2001 From: Christoph Hamsen Date: Thu, 1 Feb 2024 14:32:38 +0100 Subject: [PATCH 2/9] ci: compliance checks --- .github/workflows/.reusable-compliance.yml | 3 +++ .github/workflows/pr.yml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/.reusable-compliance.yml b/.github/workflows/.reusable-compliance.yml index 1f9a8cc..af38ccf 100644 --- a/.github/workflows/.reusable-compliance.yml +++ b/.github/workflows/.reusable-compliance.yml @@ -46,11 +46,14 @@ jobs: inputs.skip != 'all' # permissions: #TODO: reactivate for non-private # contents: write + # pull-requests: write steps: - name: Checkout code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Review uses: actions/dependency-review-action@7bbfa034e752445ea40215fff1c3bf9597993d3f # v3.1.3 + with: + comment-summary-in-pr: always check-commit-message: runs-on: ubuntu-latest diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 413459d..c3b99cf 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -20,7 +20,7 @@ jobs: with: #TODO: adjust for non private skip_build: 'none' - skip_compliance_checks: 'all' + skip_compliance_checks: 'none' skip_unit_tests: 'all' skip_sast: 'all' skip_sca: 'all' From 6636f29caefa9fd7d4692a3c65217e8fbf1000d8 Mon Sep 17 00:00:00 2001 From: Christoph Hamsen Date: Thu, 1 Feb 2024 15:19:21 +0100 Subject: [PATCH 3/9] ci: sast --- .github/actions/trivy-config/action.yaml | 2 +- .github/dependabot.yml | 24 ++++ .github/workflows/.reusable-sast.yml | 132 ++++++++++++---------- .github/workflows/.reusable-unit-test.yml | 15 +-- .github/workflows/pr.yml | 2 +- .github/workflows/push.yml | 2 +- .kube-linter/config.yaml | 2 + docs/requirements.txt | 2 + semgr8s/app.py | 6 +- tests/requirements.txt | 15 +++ 10 files changed, 128 insertions(+), 74 deletions(-) create mode 100644 .kube-linter/config.yaml create mode 100644 docs/requirements.txt create mode 100644 tests/requirements.txt diff --git a/.github/actions/trivy-config/action.yaml b/.github/actions/trivy-config/action.yaml index 4e23b58..3152579 100644 --- a/.github/actions/trivy-config/action.yaml +++ b/.github/actions/trivy-config/action.yaml @@ -14,7 +14,7 @@ runs: - name: Render Helm charts run: | mkdir deployment - helm template charts/connaisseur > deployment/deployment.yaml + helm template charts/semgr8s > deployment/deployment.yaml shell: bash - name: Scan deployment.yaml uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f # v0.12.0 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5b4aaef..a53094d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,6 +12,30 @@ updates: pip-packages: patterns: - "*" + - package-ecosystem: "pip" + directory: "/docs" + schedule: + interval: "weekly" + commit-message: + prefix: "update" + insecure-external-code-execution: "deny" + target-branch: "dev" + groups: + pip-packages: + patterns: + - "*" + - package-ecosystem: "pip" + directory: "/tests" + schedule: + interval: "monthly" + commit-message: + prefix: "update" + insecure-external-code-execution: "deny" + target-branch: "dev" + groups: + pip-packages: + patterns: + - "*" - package-ecosystem: "docker" directory: "/docker" schedule: diff --git a/.github/workflows/.reusable-sast.yml b/.github/workflows/.reusable-sast.yml index e673831..647457a 100644 --- a/.github/workflows/.reusable-sast.yml +++ b/.github/workflows/.reusable-sast.yml @@ -16,6 +16,47 @@ on: #permissions: {} #TODO: reactivate for non-private jobs: + bandit: + runs-on: ubuntu-latest + if: | + (github.actor != 'dependabot[bot]') && + inputs.skip != 'all' + permissions: + security-events: write + container: + image: python:slim + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Install Packages + run: pip3 install -r tests/requirements.txt + - name: Run Bandit + if: inputs.output == 'table' + run: bandit -r -f screen semgr8s/ + - name: Run Bandit + if: inputs.output == 'sarif' + run: bandit -r -f sarif -o bandit-results.sarif semgr8s/ --exit-zero + - name: Upload + if: inputs.output == 'sarif' + uses: github/codeql-action/upload-sarif@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 + with: + sarif_file: 'bandit-results.sarif' + + black: + runs-on: ubuntu-latest + if: | + (github.actor != 'dependabot[bot]') && + inputs.skip != 'non-required' && + inputs.skip != 'all' + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Install packages + run: pip3 install -r tests/requirements.txt + - name: Test formatting + run: | + python3 -m black . 2>&1 | grep -q "reformatted" && { echo 'Not properly formatted.'; exit 1; } || true + checkov: runs-on: ubuntu-latest if: | @@ -32,7 +73,7 @@ jobs: rm -rf test # remove 'test' folder from scan #TODO: fix once final rm -rf tests # remove 'tests' folder from scan mkdir deployment - helm template charts/connaisseur > deployment/deployment.yaml + helm template charts/semgr8s > deployment/deployment.yaml shell: bash - name: Scan if: inputs.output == 'table' @@ -58,66 +99,19 @@ jobs: if: | (github.actor != 'dependabot[bot]') && inputs.skip != 'non-required' && - inputs.skip != 'all' && - inputs.output == 'sarif' - # permissions: #TODO: reactivate for non-private - # pull-requests: read - # security-events: write - steps: - - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Initialize CodeQL - uses: github/codeql-action/init@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7 - with: - languages: 'go' - - name: Analyze - uses: github/codeql-action/analyze@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7 - - golangci-lint: - runs-on: ubuntu-latest - if: | - (github.actor != 'dependabot[bot]') && - inputs.skip != 'all' - # permissions: #TODO: reactivate for non-private - # security-events: write - steps: - - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 - with: - cache: false - go-version: '1.21' - - name: Analyze - uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 - with: - version: latest - args: '--timeout=10m --skip-dirs="test" --tests=false' - - gosec: - runs-on: ubuntu-latest - if: | - (github.actor != 'dependabot[bot]') && inputs.skip != 'all' - # permissions: #TODO: reactivate for non-private - # security-events: write + permissions: + security-events: write + pull-requests: read steps: - - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Analyze - uses: securego/gosec@55d79496019a560e16e73e1948dee20a1fad631a # v2.18.2 - if: inputs.output == 'table' - with: - args: '-fmt text -exclude-dir=test -exclude-dir=tools ./...' - - name: Analyze - uses: securego/gosec@55d79496019a560e16e73e1948dee20a1fad631a # v2.18.2 - if: inputs.output == 'sarif' - with: - args: '-exclude-dir=test -exclude-dir=tools -no-fail -fmt sarif -out gosec-results.sarif ./...' - - name: Upload - uses: github/codeql-action/upload-sarif@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7 - if: inputs.output == 'sarif' - with: - sarif_file: 'gosec-results.sarif' + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Initialize CodeQL + uses: github/codeql-action/init@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 + with: + languages: 'python' + - name: Analyze + uses: github/codeql-action/analyze@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 hadolint: runs-on: ubuntu-latest @@ -167,14 +161,14 @@ jobs: if: inputs.output == 'table' with: config: .kube-linter/config.yaml - directory: charts/connaisseur + directory: charts/semgr8s format: plain - name: Scan uses: stackrox/kube-linter-action@ca0d55b925470deb5b04b556e6c4276ea94d03c3 # v1.0.4 if: inputs.output == 'sarif' with: config: .kube-linter/config.yaml - directory: charts/connaisseur + directory: charts/semgr8s format: sarif output-file: kubelinter-results.sarif - name: Upload @@ -183,6 +177,22 @@ jobs: with: sarif_file: 'kubelinter-results.sarif' + pylint: + runs-on: ubuntu-latest + if: | + (github.actor != 'dependabot[bot]') && + inputs.skip != 'all' + container: + image: python:3.11-slim + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Install + run: | + pip3 install -r tests/requirements.txt + - name: Lint + run: pylint --ignore-patterns=tests,coverage semgr8s + semgrep: runs-on: ubuntu-latest if: | diff --git a/.github/workflows/.reusable-unit-test.yml b/.github/workflows/.reusable-unit-test.yml index b33fb28..6f7e711 100644 --- a/.github/workflows/.reusable-unit-test.yml +++ b/.github/workflows/.reusable-unit-test.yml @@ -18,15 +18,12 @@ jobs: steps: - name: Checkout code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Setup - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 - with: - go-version: '1.21' - - name: Test - if: inputs.skip == 'non-required' - run: go test ./cmd/... -coverprofile=coverage.out -covermode=atomic + - name: Install + run: | + pip3 install -r tests/requirements.txt && pip3 install . - name: Test - if: inputs.skip != 'non-required' - run: go test ./... -race -coverprofile=coverage.out -covermode=atomic + run: pytest --cov=semgr8s --cov-report=xml tests/ - name: Upload uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 + with: + file: coverage.xml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c3b99cf..fa0753f 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -22,7 +22,7 @@ jobs: skip_build: 'none' skip_compliance_checks: 'none' skip_unit_tests: 'all' - skip_sast: 'all' + skip_sast: 'none' skip_sca: 'all' skip_docs: 'all' skip_integration_tests: 'all' diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index ebd4cb8..cb2d2f4 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -22,7 +22,7 @@ jobs: skip_build: 'none' skip_compliance_checks: 'none' skip_unit_tests: 'all' - skip_sast: 'all' + skip_sast: 'non-required' skip_sca: 'all' skip_docs: 'all' skip_integration_tests: 'all' diff --git a/.kube-linter/config.yaml b/.kube-linter/config.yaml new file mode 100644 index 0000000..6b4d6ea --- /dev/null +++ b/.kube-linter/config.yaml @@ -0,0 +1,2 @@ +checks: + doNotAutoAddDefaults: false diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..df6da05 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,2 @@ +mkdocs-material~=9.4.14 +mike~=2.0.0 diff --git a/semgr8s/app.py b/semgr8s/app.py index 42eb854..03617d3 100644 --- a/semgr8s/app.py +++ b/semgr8s/app.py @@ -79,7 +79,11 @@ def validate(): ["* " + f["check_id"] for f in results["results"]] ) APP.logger.debug("+ %s findings: %s", num_findings, findings) - return send_response(False, uid, f"Found {num_findings} violation(s) of the following policies: {findings}") + return send_response( + False, + uid, + f"Found {num_findings} violation(s) of the following policies: {findings}", + ) except Exception as err: return send_response(False, uid, f"Webhook exception: {err}") finally: diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..82cdf1a --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,15 @@ +-r ../requirements.txt +aioresponses~=0.7.6 +bandit~=1.7.7 +bandit-sarif-formatter~=1.1.1 +black~=24.1.1 +freezegun~=1.2.2 +parsedatetime~=2.6 +pylint~=3.0.2 +pytest-asyncio~=0.21.1 +pytest-cov~=4.1.0 +pytest-mock~=3.12.0 +pytest-subprocess~=1.5.0 +requests-mock~=1.11.0 +setuptools~=69.0.2 +wheel~=0.42.0 From 668a1989bfb883b4f49d248499dd78d909667739 Mon Sep 17 00:00:00 2001 From: Christoph Hamsen Date: Thu, 1 Feb 2024 15:45:07 +0100 Subject: [PATCH 4/9] docs: charts details --- .github/workflows/.reusable-sast.yml | 2 -- charts/semgr8s/Chart.yaml | 13 ++++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/.reusable-sast.yml b/.github/workflows/.reusable-sast.yml index 647457a..68d9451 100644 --- a/.github/workflows/.reusable-sast.yml +++ b/.github/workflows/.reusable-sast.yml @@ -23,8 +23,6 @@ jobs: inputs.skip != 'all' permissions: security-events: write - container: - image: python:slim steps: - name: Checkout code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/charts/semgr8s/Chart.yaml b/charts/semgr8s/Chart.yaml index 88d151b..9c7cbe4 100644 --- a/charts/semgr8s/Chart.yaml +++ b/charts/semgr8s/Chart.yaml @@ -1,6 +1,17 @@ apiVersion: v2 name: semgr8s -description: A Helm chart for Kubernetes +description: Semgrep-based Policy Controller for Kubernetes type: application version: 0.1.1 appVersion: "0.1.1" +keywords: + - kubernetes + - admission controller + - policy management +home: https://sse-secure-systems.github.io/semgr8s/latest +sources: + - https://github.com/sse-secure-systems/semgr8s +icon: https://raw.githubusercontent.com/sse-secure-systems/semgr8s/main/docs/assets/semgr8s-logo.png +maintainers: + - name: Christoph Hamsen + email: christoph.hamsen@securesystems.de From d5f2bf4b53106af08fdfb39aa2af4893d3f447a8 Mon Sep 17 00:00:00 2001 From: Christoph Hamsen Date: Thu, 1 Feb 2024 15:58:49 +0100 Subject: [PATCH 5/9] ci: sca --- .github/actions/build/action.yml | 8 ++++---- .github/workflows/pr.yml | 2 +- .github/workflows/push.yml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 54be081..1c48566 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -1,5 +1,5 @@ name: build -description: 'Build connaisseur image' +description: 'Build semgr8s image' inputs: image_registry: description: 'Image registry to be used' @@ -113,11 +113,11 @@ runs: path: ci/cosign.pub - name: Show build and signature information run: | - CONFIGURE="yq '. *+ load(\"tests/integration/var-img.yaml\")' tests/integration/ghcr-values.yaml > ghcr.yaml &&\n\t IMAGE=\"${{ inputs.image_registry }}/${{ inputs.image_repo }}\" TAG=\"${{ inputs.image_tag }}\" IMAGEPULLSECRET=\"\" envsubst < ghcr.yaml > update &&\n\t yq '. *+ load(\"update\")' -i charts/connaisseur/values.yaml &&\n\t rm ghcr.yaml update" + CONFIGURE="yq '. *+ load(\"tests/integration/var-img.yaml\")' tests/integration/ghcr-values.yaml > ghcr.yaml &&\n\t IMAGE=\"${{ inputs.image_registry }}/${{ inputs.image_repo }}\" TAG=\"${{ inputs.image_tag }}\" IMAGEPULLSECRET=\"\" envsubst < ghcr.yaml > update &&\n\t yq '. *+ load(\"update\")' -i charts/semgr8s/values.yaml &&\n\t rm ghcr.yaml update" CONFIGURE=$(printf -- "${CONFIGURE}") PUBLIC_KEY="${{ steps.verify.outputs.public_key }}" PUBLIC_KEY="$(printf -- "${PUBLIC_KEY//'
'/'\n'}")" - HELM_PATCH="yq e '.kubernetes.deployment.image.repository = \"${{ inputs.image_registry }}/${{ inputs.image_repo }}\"' -i charts/connaisseur/values.yaml\nyq e '.kubernetes.deployment.image.tag = \"${{ inputs.image_tag }}\"' -i charts/connaisseur/values.yaml" + HELM_PATCH="yq e '.kubernetes.deployment.image.repository = \"${{ inputs.image_registry }}/${{ inputs.image_repo }}\"' -i charts/semgr8s/values.yaml\nyq e '.kubernetes.deployment.image.tag = \"${{ inputs.image_tag }}\"' -i charts/semgr8s/values.yaml" HELM_PATCH=$(printf -- "${HELM_PATCH}") echo "# :building_construction: Build Information" >> ${GITHUB_STEP_SUMMARY} echo "" >> ${GITHUB_STEP_SUMMARY} @@ -147,7 +147,7 @@ runs: echo "
  • Syft SBOM (cyclonedx-json):
    ${{ steps.verify.outputs.sbom }}
  • " >> ${GITHUB_STEP_SUMMARY} echo "
  • Pull via Docker:
    docker pull ${{ inputs.image_registry }}/${{ inputs.image_repo }}:${{ inputs.image_tag }}
  • " >> ${GITHUB_STEP_SUMMARY} echo "
  • Use in Helm chart (needs imagePullSecret via dockerconfigjson of PAT with package:read permission):
    ${HELM_PATCH}
  • " >> ${GITHUB_STEP_SUMMARY} - echo "
  • Configure Helm chart for successful validation build image (RUN ONLY ONCE, insert name of imagePullSecret in connaisseur namespace):
    ${CONFIGURE}
  • " >> ${GITHUB_STEP_SUMMARY} + echo "
  • Configure Helm chart for successful validation build image (RUN ONLY ONCE, insert name of imagePullSecret in semgr8s namespace):
    ${CONFIGURE}
  • " >> ${GITHUB_STEP_SUMMARY} echo "" >> ${GITHUB_STEP_SUMMARY} echo "" >> ${GITHUB_STEP_SUMMARY} echo "" >> ${GITHUB_STEP_SUMMARY} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index fa0753f..97de8bc 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -23,7 +23,7 @@ jobs: skip_compliance_checks: 'none' skip_unit_tests: 'all' skip_sast: 'none' - skip_sca: 'all' + skip_sca: 'none' skip_docs: 'all' skip_integration_tests: 'all' output_type: 'sarif' diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index cb2d2f4..4d720f0 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -23,7 +23,7 @@ jobs: skip_compliance_checks: 'none' skip_unit_tests: 'all' skip_sast: 'non-required' - skip_sca: 'all' + skip_sca: 'non-required' skip_docs: 'all' skip_integration_tests: 'all' output_type: 'sarif' From 559e007f4bbf75b3b28c1fa7612f92086001853e Mon Sep 17 00:00:00 2001 From: Christoph Hamsen Date: Thu, 1 Feb 2024 16:41:38 +0100 Subject: [PATCH 6/9] ci: docs fix fix --- ....reusable-docs.yaml => .reusable-docs.yml} | 4 +- docs/README.md | 1 + mkdocs.yml | 88 +++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) rename .github/workflows/{.reusable-docs.yaml => .reusable-docs.yml} (90%) create mode 120000 docs/README.md create mode 100644 mkdocs.yml diff --git a/.github/workflows/.reusable-docs.yaml b/.github/workflows/.reusable-docs.yml similarity index 90% rename from .github/workflows/.reusable-docs.yaml rename to .github/workflows/.reusable-docs.yml index 3e5fc0e..fce6553 100644 --- a/.github/workflows/.reusable-docs.yaml +++ b/.github/workflows/.reusable-docs.yml @@ -29,14 +29,14 @@ jobs: git config user.email "connaisseur@securesystems.dev" - name: Install run: | - pip install -r docs/requirements_docs.txt + pip install -r docs/requirements.txt - name: Deploy if: inputs.skip != 'non-required' run: | if [[ "${GITHUB_REF}" == "refs/tags/v"* ]]; then mike deploy --push --update-aliases ${RELEASE_VERSION} latest - elif [[ "${GITHUB_REF}" == "refs/heads/develop" ]]; then + elif [[ "${GITHUB_REF}" == "refs/heads/dev" ]]; then mike deploy --push ${RELEASE_VERSION} else mkdocs build diff --git a/docs/README.md b/docs/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/docs/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..dc294a6 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,88 @@ +# Project information +site_name: semgr8s - Semgrep-based Policy Controller for Kubernetes. + +site_url: https://sse-secure-systems.github.io/semgr8s/ +site_description: >- + Admission controller to use your well-known publicly available or custom Semgrep rules to validate k8s resources before deployment to the cluster. + +# Repository +repo_name: sse-secure-systems/semgr8s/ +repo_url: https://github.com/sse-secure-systems/semgr8s +edit_uri: "" + +# Company +copyright:
    Secure Systems Engineering GmbH + + +# Configuration +theme: + language: en + name: material + palette: + primary: blue + font: + text: Roboto + code: Roboto Mono + logo: 'assets/logo.png' + favicon: 'assets/logo.png' + features: + - content.code.copy + - content.code.select + - navigation.top + +# Extensions +markdown_extensions: + - admonition + - codehilite + - footnotes + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.tabbed + - pymdownx.superfences + - pymdownx.inlinehilite + - pymdownx.snippets + - toc: + permalink: ⚓︎ + +# Plugins + +# Extras +extra_javascript: + - https://unpkg.com/tablesort@5.3.0/dist/tablesort.min.js + - javascripts/tablesort.js + +# Customization +extra: + version: + provider: mike + social: + - icon: fontawesome/brands/github + link: https://github.com/sse-secure-systems + name: SSE on GitHub + - icon: fontawesome/brands/medium + link: https://medium.com/sse-blog + name: SSE on Medium + - icon: fontawesome/brands/youtube + link: https://www.youtube.com/channel/UCReAmr98RzwYZeWG6CAwOhg + name: SSE on YouTube + - icon: fontawesome/brands/twitter + link: https://twitter.com/sse_gmbh + name: SSE on Twitter + - icon: fontawesome/brands/linkedin + link: https://www.linkedin.com/company/sse-secure-systems-engineering + name: SSE on LinkedIn + - icon: fontawesome/solid/link + link: https://www.securesystems.de/ + name: SSE Website + - icon: fontawesome/solid/envelope + link: mailto:christoph.hamsen@securesystems.dev + name: Email contact + +# Page tree +nav: + - Documentation: README.md From 4e480442316d91fe13cad09832abe7fb14d86038 Mon Sep 17 00:00:00 2001 From: Christoph Hamsen Date: Thu, 1 Feb 2024 16:41:56 +0100 Subject: [PATCH 7/9] ci: integration tests --- .../workflows/.reusable-integration-test.yml | 309 +----------------- 1 file changed, 3 insertions(+), 306 deletions(-) diff --git a/.github/workflows/.reusable-integration-test.yml b/.github/workflows/.reusable-integration-test.yml index f98cd13..43a8708 100644 --- a/.github/workflows/.reusable-integration-test.yml +++ b/.github/workflows/.reusable-integration-test.yml @@ -29,316 +29,13 @@ env: IMAGEPULLSECRET: dockerconfigjson-ghcr jobs: - integration-test: + do-nothing: name: functional runs-on: ubuntu-latest if: inputs.skip != 'all' # permissions: #TODO: reactivate for non-private # packages: read - env: - IMAGE: ${{ inputs.build_image_repository }} - TAG: ${{ inputs.build_tag }} - COSIGN_PUBLIC_KEY: ${{ inputs.cosign_public_key }} - strategy: - fail-fast: false - matrix: - integration-test-arg: - [ - "regular", - "cosign", - "multi-cosigned", - "rekor-cosigned", - "namespace-val", - "deployment", - "pre-config", - "other-ns", - "configured-cert", - ] - services: - alerting-endpoint: - image: securesystemsengineering/alerting-endpoint - ports: - - 56243:56243 steps: - - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Login with registry - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 - with: - registry: ${{ inputs.build_registry }} - username: ${{ inputs.repo_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Install yq + - name: Do nothing run: | - sudo snap install yq - - uses: ./.github/actions/k8s-version-config - name: Setup k8s cluster - with: - k8s-version: v1.25 - - name: Get alerting endpoint IP - id: get_ip - uses: ./.github/actions/alerting-endpoint - - name: Run test - run: | - bash tests/integration/integration-test.sh "${{ matrix.integration-test-arg }}" - env: - ALERTING_ENDPOINT_IP: ${{ steps.get_ip.outputs.ip }} - - name: Display Connaisseur configuration - if: always() - run: | - echo "::group::values.yaml" - yq e '... comments=""' charts/connaisseur/values.yaml - echo "::endgroup::" - - name: Display k8s state if integration test failed - if: failure() - run: | - kubectl describe deployments.apps -n connaisseur -lapp.kubernetes.io/name=connaisseur - kubectl describe pods -n connaisseur -lapp.kubernetes.io/name=connaisseur - - name: Display logs if integration test failed - if: failure() - run: | - kubectl logs -n connaisseur -lapp.kubernetes.io/name=connaisseur --prefix=true --tail=-1 - - optional-integration-test: - name: optional - runs-on: ubuntu-latest - if: | - inputs.skip != 'non-required' && - inputs.skip != 'all' - # permissions: #TODO: reactivate for non-private - # packages: read - env: - IMAGE: ${{ inputs.build_image_repository }} - TAG: ${{ inputs.build_tag }} - COSIGN_PUBLIC_KEY: ${{ inputs.cosign_public_key }} - strategy: - fail-fast: false - matrix: - integration-test-arg: - [ - "complexity", - "load", - "upgrade", - ] - services: - alerting-endpoint: - image: securesystemsengineering/alerting-endpoint - ports: - - 56243:56243 - steps: - - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Login with registry - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 - with: - registry: ${{ inputs.build_registry }} - username: ${{ inputs.repo_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Install yq - run: | - sudo snap install yq - - uses: ./.github/actions/k8s-version-config - name: Setup k8s cluster - with: - k8s-version: v1.25 - - name: Get alerting endpoint IP - id: get_ip - uses: ./.github/actions/alerting-endpoint - - name: Run test - run: | - bash tests/integration/integration-test.sh "${{ matrix.integration-test-arg }}" - env: - ALERTING_ENDPOINT_IP: ${{ steps.get_ip.outputs.ip }} - - name: Display Connaisseur configuration - if: always() - run: | - echo "::group::values.yaml" - yq e '... comments=""' charts/connaisseur/values.yaml - echo "::endgroup::" - - name: Display k8s state if integration test failed - if: failure() - run: | - kubectl describe deployments.apps -n connaisseur -lapp.kubernetes.io/name=connaisseur - kubectl describe pods -n connaisseur -lapp.kubernetes.io/name=connaisseur - - name: Display logs if integration test failed - if: failure() - run: | - kubectl logs -n connaisseur -lapp.kubernetes.io/name=connaisseur --prefix=true --tail=-1 - - k8s-versions: - name: k8s versions - runs-on: ubuntu-latest - if: inputs.skip != 'all' - # permissions: #TODO: reactivate for non-private - # packages: read - env: - IMAGE: ${{ inputs.build_image_repository }} - TAG: ${{ inputs.build_tag }} - COSIGN_PUBLIC_KEY: ${{ inputs.cosign_public_key }} - strategy: - fail-fast: false - matrix: - k8s-version: [ - "v1.25", - "v1.26", - "v1.27", - "v1.28", - ] - steps: - - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Login with registry - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 - with: - registry: ${{ inputs.build_registry }} - username: ${{ inputs.repo_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Install yq - run: | - sudo snap install yq - - uses: ./.github/actions/k8s-version-config - name: Setup k8s cluster - with: - k8s-version: ${{ matrix.k8s-version }} - - name: Run pre-config and workload integration tests - run: | - bash tests/integration/integration-test.sh "pre-and-workload" - - name: Display k8s state and logs if integration test failed - if: failure() - run: | - kubectl describe deployments.apps -n connaisseur -lapp.kubernetes.io/name=connaisseur - kubectl describe pods -n connaisseur -lapp.kubernetes.io/name=connaisseur - kubectl logs -n connaisseur -lapp.kubernetes.io/name=connaisseur --prefix=true --tail=-1 - - name: Display Connaisseur configuration - if: always() - run: | - echo "::group::values.yaml" - yq e '... comments=""' charts/connaisseur/values.yaml - echo "::endgroup::" - - optional-k8s-versions: - name: optional k8s versions - runs-on: ubuntu-latest - if: | - inputs.skip != 'non-required' && - inputs.skip != 'all' - # permissions: #TODO: reactivate for non-private - # packages: read - env: - IMAGE: ${{ inputs.build_image_repository }} - TAG: ${{ inputs.build_tag }} - COSIGN_PUBLIC_KEY: ${{ inputs.cosign_public_key }} - strategy: - fail-fast: false - matrix: - k8s-version: [ - "v1.20", - "v1.21", - "v1.22", - "v1.23", - "v1.24", - ] - steps: - - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Login with registry - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 - with: - registry: ${{ inputs.build_registry }} - username: ${{ inputs.repo_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Install yq - run: | - sudo snap install yq - - uses: ./.github/actions/k8s-version-config - name: Setup k8s cluster - with: - k8s-version: ${{ matrix.k8s-version }} - - name: Run pre-config and workload integration tests - run: | - bash tests/integration/integration-test.sh "pre-and-workload" - - name: Display k8s state and logs if integration test failed - if: failure() - run: | - kubectl describe deployments.apps -n connaisseur -lapp.kubernetes.io/name=connaisseur - kubectl describe pods -n connaisseur -lapp.kubernetes.io/name=connaisseur - kubectl logs -n connaisseur -lapp.kubernetes.io/name=connaisseur --prefix=true --tail=-1 - - name: Display Connaisseur configuration - if: always() - run: | - echo "::group::values.yaml" - yq e '... comments=""' charts/connaisseur/values.yaml - echo "::endgroup::" - - self-hosted-notary: - name: self-hosted-notary - runs-on: ubuntu-latest - if: | - inputs.skip != 'non-required' && - inputs.skip != 'all' - permissions: - packages: read - env: - IMAGE: ${{ inputs.build_image_repository }} - TAG: ${{ inputs.build_tag }} - COSIGN_PUBLIC_KEY: ${{ inputs.cosign_public_key }} - strategy: - fail-fast: false - matrix: - integration-test-arg: - [ - "self-hosted-notary" - ] - steps: - - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Login with registry - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 - with: - registry: ${{ inputs.build_registry }} - username: ${{ inputs.repo_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Install yq - run: | - sudo snap install yq - - name: Setup notary signer instance - run: | - docker run -d -p 7899:7899 -v ./tests/data/notary_service_container/signer:/etc/docker/notary-signer/ notary:signer -config=/etc/docker/notary-signer/config.json - - name: Get notary signer instance IP - id: get_notary_signer_ip - uses: ./.github/actions/notary-signer-ip - - name: Setup notary server instance - run: | - docker run -d -p 4443:4443 --add-host notary.signer:${{ steps.get_notary_signer_ip.outputs.notary_signer_ip }} -v ./tests/data/notary_service_container/server:/etc/docker/notary-server notary:server -config=/etc/docker/notary-server/config.json -logf=json - - name: Get container IPs - id: get_notary_server_ip - uses: ./.github/actions/notary-server-ip - - name: Populate notary instance with trust data - uses: ./.github/actions/setup-notary - id: setup_notary - env: - NOTARY_IP: ${{ steps.get_notary_server_ip.outputs.notary_ip }} - - uses: ./.github/actions/k8s-version-config - name: Setup k8s cluster - with: - k8s-version: v1.28 - - name: Run test - run: | - bash tests/integration/integration-test.sh "${{ matrix.integration-test-arg }}" - env: - NOTARY_IP: ${{ steps.get_notary_server_ip.outputs.notary_ip }} - - name: Display Connaisseur configuration - if: always() - run: | - echo "::group::values.yaml" - yq e '... comments=""' charts/semgr8s/values.yaml - echo "::endgroup::" - - name: Display k8s state if integration test failed - if: failure() - run: | - kubectl describe deployments.apps -n connaisseur -lapp.kubernetes.io/name=connaisseur - kubectl describe pods -n connaisseur -lapp.kubernetes.io/name=connaisseur - - name: Display logs if integration test failed - if: failure() - run: | - kubectl logs -n connaisseur -lapp.kubernetes.io/name=connaisseur --prefix=true --tail=-1 + sleep 1 From 09e3466e8e5836c22ab9320b001a2b5f28f01e6c Mon Sep 17 00:00:00 2001 From: Christoph Hamsen Date: Thu, 1 Feb 2024 16:42:23 +0100 Subject: [PATCH 8/9] ci: workflow configurations --- .github/workflows/pr.yml | 4 ++-- .github/workflows/push.yml | 4 ++-- .github/workflows/tag.yml | 28 ++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/tag.yml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 97de8bc..4c39e0c 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -24,6 +24,6 @@ jobs: skip_unit_tests: 'all' skip_sast: 'none' skip_sca: 'none' - skip_docs: 'all' - skip_integration_tests: 'all' + skip_docs: 'non-required' + skip_integration_tests: 'none' output_type: 'sarif' diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 4d720f0..1c5516b 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -24,6 +24,6 @@ jobs: skip_unit_tests: 'all' skip_sast: 'non-required' skip_sca: 'non-required' - skip_docs: 'all' - skip_integration_tests: 'all' + skip_docs: 'none' + skip_integration_tests: 'non-required' output_type: 'sarif' diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml new file mode 100644 index 0000000..138c21c --- /dev/null +++ b/.github/workflows/tag.yml @@ -0,0 +1,28 @@ +name: tag + +#permissions: {} #TODO: reactivate for non-private + +on: + push: + tags: + - "v*" + +defaults: + run: + shell: bash + +jobs: + ci: + uses: ./.github/workflows/.reusable-ci.yml + # permissions: #TODO: adjust for non-private + secrets: inherit + with: + #TODO: adjust for non private + skip_build: 'none' + skip_compliance_checks: 'none' + skip_unit_tests: 'all' + skip_sast: 'none' + skip_sca: 'none' + skip_docs: 'none' + skip_integration_tests: 'non-required' + output_type: 'sarif' From 77994b53e7b34cfd13270d7c984388179c848846 Mon Sep 17 00:00:00 2001 From: Christoph Hamsen Date: Thu, 1 Feb 2024 16:44:03 +0100 Subject: [PATCH 9/9] ci: minor fixes --- .github/workflows/.reusable-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/.reusable-ci.yml b/.github/workflows/.reusable-ci.yml index 6c1393d..eb0818f 100644 --- a/.github/workflows/.reusable-ci.yml +++ b/.github/workflows/.reusable-ci.yml @@ -155,7 +155,7 @@ jobs: output: ${{ needs.conditionals.outputs.output_type }} docs: - uses: ./.github/workflows/.reusable-docs.yaml + uses: ./.github/workflows/.reusable-docs.yml needs: [conditionals] # permissions: #TODO: reactivate for non-private # contents: write