From e06ae7a8b0a122187ca60645ab5c19d6000fa849 Mon Sep 17 00:00:00 2001 From: James Petersen Date: Wed, 11 Dec 2024 10:19:01 -0700 Subject: [PATCH] feat: add code ci (#3) * feat: add code ci Signed-off-by: James Petersen * chore: fix cargo test Signed-off-by: James Petersen * chore: fix test and check for existence of certs Signed-off-by: James Petersen --------- Signed-off-by: James Petersen --- .../{helm-test.yaml => ci-chart.yaml} | 2 +- .github/workflows/ci-code.yaml | 165 ++++++++++++++++++ .../{release-artifacts.yaml => release.yaml} | 23 ++- README.md | 10 +- hack/code/shellcheck.sh | 7 + hack/code/shfmt.sh | 22 +++ src/server/mod.rs | 51 +++++- src/server/mutate.rs | 6 +- 8 files changed, 265 insertions(+), 21 deletions(-) rename .github/workflows/{helm-test.yaml => ci-chart.yaml} (98%) create mode 100644 .github/workflows/ci-code.yaml rename .github/workflows/{release-artifacts.yaml => release.yaml} (89%) create mode 100755 hack/code/shellcheck.sh create mode 100755 hack/code/shfmt.sh diff --git a/.github/workflows/helm-test.yaml b/.github/workflows/ci-chart.yaml similarity index 98% rename from .github/workflows/helm-test.yaml rename to .github/workflows/ci-chart.yaml index cae0afa..7243177 100644 --- a/.github/workflows/helm-test.yaml +++ b/.github/workflows/ci-chart.yaml @@ -6,7 +6,7 @@ on: - main paths: - charts/** - - .github/workflows/helm-test.yaml + - .github/workflows/ci-chart.yaml jobs: lint-test: diff --git a/.github/workflows/ci-code.yaml b/.github/workflows/ci-code.yaml new file mode 100644 index 0000000..9f9ed9a --- /dev/null +++ b/.github/workflows/ci-code.yaml @@ -0,0 +1,165 @@ +name: Lint and Test Code + +on: + pull_request: + branches: + - main + paths: + - src/** + - .github/workflows/ci-code.yaml + +jobs: + rustfmt: + name: rustfmt + runs-on: ubuntu-latest + steps: + - name: harden runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + + - name: checkout repository + uses: actions/checkout@6b42224f41ee5dfe5395e27c8b2746f1f9955030 # v4.2.0 + with: + submodules: recursive + persist-credentials: false + + - name: 'Install stable rust toolchain with rustfmt' + run: | + rustup update --no-self-update stable + rustup default stable + rustup component add rustfmt + + - name: 'cargo fmt' + run: cargo fmt --all -- --check + + shfmt: + name: shfmt + runs-on: ubuntu-latest + steps: + - name: harden runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + + - name: checkout repository + uses: actions/checkout@6b42224f41ee5dfe5395e27c8b2746f1f9955030 # v4.2.0 + with: + submodules: recursive + persist-credentials: false + + - name: shfmt + run: | + GOBIN=/usr/local/bin go install mvdan.cc/sh/v3/cmd/shfmt@latest + if ! ./hack/code/shfmt.sh; then + echo "" + echo "Please run \`PROTECT_SHFMT_WRITE=true ./hack/code/shfmt.sh\`" + fi + + shellcheck: + name: shellcheck + runs-on: ubuntu-latest + steps: + - name: harden runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + + - name: checkout repository + uses: actions/checkout@6b42224f41ee5dfe5395e27c8b2746f1f9955030 # v4.2.0 + with: + submodules: recursive + persist-credentials: false + + - name: shellcheck + run: ./hack/code/shellcheck.sh + + full-build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + arch: + - x86_64 + env: + TARGET_ARCH: "${{ matrix.arch }}" + name: 'Full build linux-${{ matrix.arch }}' + steps: + - name: harden runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + + - name: checkout repository + uses: actions/checkout@6b42224f41ee5dfe5395e27c8b2746f1f9955030 # v4.2.0 + with: + submodules: recursive + persist-credentials: false + + - name: 'Install stable rust toolchain' + run: | + rustup update --no-self-update stable + rustup default stable + + - name: cargo build + run: cargo build + + full-test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + arch: + - x86_64 + env: + TARGET_ARCH: "${{ matrix.arch }}" + name: 'Full test linux-${{ matrix.arch }}' + steps: + - name: harden runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + + - name: checkout repository + uses: actions/checkout@6b42224f41ee5dfe5395e27c8b2746f1f9955030 # v4.2.0 + with: + submodules: recursive + persist-credentials: false + + - name: 'Install stable rust toolchain' + run: | + rustup update --no-self-update stable + rustup default stable + + - name: 'cargo test' + run: cargo test + + full-clippy: + runs-on: ubuntu-latest + strategy: + matrix: + arch: + - x86_64 + env: + TARGET_ARCH: "${{ matrix.arch }}" + name: 'Full clippy linux-${{ matrix.arch }}' + steps: + - name: harden runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + + - name: checkout repository + uses: actions/checkout@6b42224f41ee5dfe5395e27c8b2746f1f9955030 # v4.2.0 + with: + submodules: recursive + persist-credentials: false + + - name: 'Install stable rust toolchain with clippy' + run: | + rustup update --no-self-update stable + rustup default stable + rustup component add clippy + + - name: 'cargo clippy' + run: cargo clippy diff --git a/.github/workflows/release-artifacts.yaml b/.github/workflows/release.yaml similarity index 89% rename from .github/workflows/release-artifacts.yaml rename to .github/workflows/release.yaml index 64800b2..b4a3671 100644 --- a/.github/workflows/release-artifacts.yaml +++ b/.github/workflows/release.yaml @@ -2,14 +2,6 @@ name: Release Artifacts run-name: 'Release run by ${{ github.actor }}' on: - # Release unstable from HEAD on every merge - push: - branches: - - main - - # Run manually to release unstable from HEAD - workflow_dispatch: - # Official stable versioned release release: types: @@ -124,22 +116,27 @@ jobs: - name: Resolve parameters id: resolve_parameters run: | - resolved_ref="${{ github.ref }}" - echo "INFO: Resolving short SHA for $resolved_ref" - echo "short_sha=$(git rev-parse --short $resolved_ref)" >> $GITHUB_OUTPUT + echo "INFO: Resolving short SHA for ${GITHUB_REF}" + echo "short_sha=$(git rev-parse --short ${GITHUB_REF})" >> $GITHUB_OUTPUT echo "INFO: Normalizing repository name (lowercase)" - echo "repository_owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT + echo "repository_owner=$(echo ${GITHUB_REPOSITORY_OWNER} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT + env: + GITHUB_REF: '${{ github.ref }}' + GITHUB_REPOSITORY_OWNER: '${{ github.repository_owner }}' - name: Set up Helm uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2 - name: Publish new helm chart for protect-webhook run: | - echo ${{ secrets.GITHUB_TOKEN }} | helm registry login ghcr.io --username ${{ github.actor }} --password-stdin + echo ${{ secrets.GITHUB_TOKEN }} | helm registry login ghcr.io --username ${GITHUB_ACTOR} --password-stdin PROTECT_WEBHOOK_CHART_VERSION_TAG=$(cat charts/protect-webhook/Chart.yaml | grep version: | cut -d " " -f 2) echo "PROTECT_WEBHOOK_CHART_VERSION_TAG=${PROTECT_WEBHOOK_CHART_VERSION_TAG}" >> $GITHUB_ENV helm package charts/protect-webhook/ --version="${PROTECT_WEBHOOK_CHART_VERSION_TAG}" helm push protect-webhook-"${PROTECT_WEBHOOK_CHART_VERSION_TAG}".tgz oci://ghcr.io/${{ steps.resolve_parameters.outputs.repository_owner }}/charts + env: + GITHUB_ACTOR: '${{ github.actor }}' + GITHUB_REPOSITORY_OWNER: '${{ github.repository_owner }}' - name: Job summary run: | diff --git a/README.md b/README.md index eb2d4d1..caf0737 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Edera Protect Runtime Class Injector -This repo contains a mutating webhook to inject the `edera` runtime class into a kubernetes manifest. +protect-webhook is a mutating webhook to inject the `edera` runtime class into a kubernetes manifest. -### Why would I want this? +### Uses Edera Protect provides strong isolation for kubernetes workloads (see [edera.dev](https://edera.dev) for more details). It does so by utilizing a runtime class name to specify whichworkloads should be @@ -11,7 +11,7 @@ use of this webhook to inject it into the manifest upon creation. You also might every manifest you've ever created. Instead you can just use this mutating webhook to set a runtime class name for manifests without ever touching their yaml. -### Quickstart +### Installation You can get started quickly by setting up self-signed certificates and then deploying the helm chart. It deploys a mutating webhook which needs certs to communicate with the API Server. You can find an @@ -24,3 +24,7 @@ helm upgrade --install mutate oci://ghcr.io/edera-dev/charts/protect-webhook \ --create-namespace \ --values ./examples/self-signed-certs/values.yaml ``` + +### Troubleshooting + +If you're running into issues, please file an issue! diff --git a/hack/code/shellcheck.sh b/hack/code/shellcheck.sh new file mode 100755 index 0000000..559448e --- /dev/null +++ b/hack/code/shellcheck.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -e + +REAL_SCRIPT="$(realpath "${0}")" +cd "$(dirname "${REAL_SCRIPT}")/../.." + +find hack -type f -name '*.sh' -print0 | xargs -0 shellcheck -x diff --git a/hack/code/shfmt.sh b/hack/code/shfmt.sh new file mode 100755 index 0000000..f0b066f --- /dev/null +++ b/hack/code/shfmt.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -e + +REAL_SCRIPT="$(realpath "${0}")" +cd "$(dirname "${REAL_SCRIPT}")/../.." + +# Pulled from the flags resembling the google style guide here +# https://github.com/mvdan/sh/blob/master/cmd/shfmt/shfmt.1.scd#examples +FLAGS="--indent 2 --case-indent --binary-next-line --list" + +if [ -z "${PROTECT_SHFMT_WRITE}" ]; then + echo "Running shfmt in diff mode..." + FLAGS="${FLAGS} --diff" +else + echo "Running shfmt in write mode..." + FLAGS="${FLAGS} --write" +fi + +echo "shfmt $FLAGS" + +# shellcheck disable=SC2086 +find . -not -path '*/.*' -type f -name '*.sh' -print0 | xargs -0 shfmt $FLAGS diff --git a/src/server/mod.rs b/src/server/mod.rs index 594acef..a795958 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,6 +1,6 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use log::info; -use std::env; +use std::{env, fs}; use warp::Filter; mod healthz; @@ -13,10 +13,55 @@ fn routes() -> impl Filter .or(healthz::handler()) } -pub async fn start() -> Result<()> { +fn set_certs_dir() -> Result { let certs_dir = env::var("WEBHOOK_CERTS_DIR").unwrap_or("/certs".to_string()); + + let meta = match fs::metadata(&certs_dir) { + Err(e) => return Err(anyhow!("Error reading metadata for {}: {}", certs_dir, e)), + Ok(meta) => meta, + }; + + if !meta.is_dir() { + return Err(anyhow!("{} is not a directory", certs_dir)); + } + + Ok(certs_dir) +} + +fn set_crt_file() -> Result { let crt_file = env::var("WEBHOOK_CRT_FILE").unwrap_or("tls.crt".to_string()); + + let meta = match fs::metadata(&crt_file) { + Err(e) => return Err(anyhow!("Error reading metadata for {}: {}", crt_file, e)), + Ok(meta) => meta, + }; + + if !meta.is_file() { + return Err(anyhow!("{} is not a file", crt_file)); + } + + Ok(crt_file) +} + +fn set_key_file() -> Result { let key_file = env::var("WEBHOOK_KEY_FILE").unwrap_or("tls.key".to_string()); + + let meta = match fs::metadata(&key_file) { + Err(e) => return Err(anyhow!("Error reading metadata for {}: {}", key_file, e)), + Ok(meta) => meta, + }; + + if !meta.is_file() { + return Err(anyhow!("{} is not a file", key_file)); + } + + Ok(key_file) +} + +pub async fn start() -> Result<()> { + let certs_dir = set_certs_dir()?; + let crt_file = set_crt_file()?; + let key_file = set_key_file()?; info!("configured certs directory to: {}", certs_dir); // TODO: Make healthz and livez listen on http rather than https if they need to do more diff --git a/src/server/mutate.rs b/src/server/mutate.rs index b298e80..f238ef9 100644 --- a/src/server/mutate.rs +++ b/src/server/mutate.rs @@ -94,6 +94,8 @@ mod tests { let admission_review = AdmissionReview { request: Some(AdmissionRequest { uid: "test-uid".to_string(), + name: "test-name".to_string(), + namespace: "test-namespace".to_string(), }), }; @@ -138,7 +140,9 @@ mod tests { let admission_review = json!({ "request": { - "uid": "test-uid" + "uid": "test-uid", + "name": "test-name", + "namespace": "test-namespace", } });