diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml new file mode 100644 index 0000000..1c48566 --- /dev/null +++ b/.github/actions/build/action.yml @@ -0,0 +1,173 @@ +name: build +description: 'Build semgr8s 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/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/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} + 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 "
" >> ${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..3152579 --- /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/semgr8s > 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/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-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..eb0818f --- /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.yml + 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..af38ccf --- /dev/null +++ b/.github/workflows/.reusable-compliance.yml @@ -0,0 +1,87 @@ +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 + # 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 + 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.yml b/.github/workflows/.reusable-docs.yml new file mode 100644 index 0000000..fce6553 --- /dev/null +++ b/.github/workflows/.reusable-docs.yml @@ -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.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/dev" ]]; 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..43a8708 --- /dev/null +++ b/.github/workflows/.reusable-integration-test.yml @@ -0,0 +1,41 @@ +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: + do-nothing: + name: functional + runs-on: ubuntu-latest + if: inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # packages: read + steps: + - name: Do nothing + run: | + sleep 1 diff --git a/.github/workflows/.reusable-sast.yml b/.github/workflows/.reusable-sast.yml new file mode 100644 index 0000000..68d9451 --- /dev/null +++ b/.github/workflows/.reusable-sast.yml @@ -0,0 +1,235 @@ +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: + bandit: + runs-on: ubuntu-latest + if: | + (github.actor != 'dependabot[bot]') && + inputs.skip != 'all' + permissions: + security-events: write + 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: | + (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/semgr8s > 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' + permissions: + security-events: write + pull-requests: read + steps: + - 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 + 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/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/semgr8s + 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' + + 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: | + (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..6f7e711 --- /dev/null +++ b/.github/workflows/.reusable-unit-test.yml @@ -0,0 +1,29 @@ +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: Install + run: | + pip3 install -r tests/requirements.txt && pip3 install . + - name: Test + 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 new file mode 100644 index 0000000..4c39e0c --- /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: 'none' + skip_unit_tests: 'all' + skip_sast: 'none' + skip_sca: 'none' + skip_docs: 'non-required' + skip_integration_tests: 'none' + output_type: 'sarif' diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 0000000..1c5516b --- /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: 'non-required' + skip_sca: 'non-required' + 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' 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/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/charts/semgr8s/Chart.yaml b/charts/semgr8s/Chart.yaml new file mode 100644 index 0000000..9c7cbe4 --- /dev/null +++ b/charts/semgr8s/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: semgr8s +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 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/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/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/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/ diff --git a/helm/Chart.yaml b/helm/Chart.yaml deleted file mode 100644 index 88d151b..0000000 --- a/helm/Chart.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v2 -name: semgr8s -description: A Helm chart for Kubernetes -type: application -version: 0.1.1 -appVersion: "0.1.1" 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 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