From 0211fa1274d49db656a67c25221b52df585a077c Mon Sep 17 00:00:00 2001 From: Shlomo Heigh Date: Fri, 9 Feb 2024 09:14:44 -0500 Subject: [PATCH 1/4] Add GitHub Action --- action.yml | 43 +++++++++++++++++++++++++++++++++++++++++++ generate.sh | 6 ++++++ install.sh | 5 +++++ setup.sh | 22 ++++++++++++++++++++++ wait.sh | 4 ++++ 5 files changed, 80 insertions(+) create mode 100644 action.yml create mode 100755 generate.sh create mode 100755 install.sh create mode 100755 setup.sh create mode 100755 wait.sh diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..ea95798 --- /dev/null +++ b/action.yml @@ -0,0 +1,43 @@ +name: "Generate VEX with Kubescape" +description: "Generate VEX with Kubescape" +inputs: + deployment-file: + description: "Path / URL to the deployment file" + required: true + ready-condition: + description: "Condition to wait for before collecting VEX info" + required: true + wait-time: + description: "Time to wait (in seconds) before collecting VEX info, after the ready-condition is met" + default: "120" + +runs: + using: "composite" + steps: + - name: "Setup" + run: $GITHUB_ACTION_PATH/setup.sh + shell: bash + + - name: "Install deployment" + env: + DEPLOYMENT_FILE: ${{ inputs.deployment-file }} + READY_CONDITION: ${{ inputs.ready-condition }} + run: $GITHUB_ACTION_PATH/install.sh + shell: bash + + - name: "Wait for deployment to be ready" + env: + WAIT_TIME: ${{ inputs.wait-time }} + run: $GITHUB_ACTION_PATH/wait.sh + shell: bash + + - name: "Generate VEX" + run: $GITHUB_ACTION_PATH/generate.sh + shell: bash + + - name: Upload VEX document + if: always() + uses: actions/upload-artifact@v4 + with: + name: VEX document + path: vex.json diff --git a/generate.sh b/generate.sh new file mode 100755 index 0000000..40b6e07 --- /dev/null +++ b/generate.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -x + +kubectl -n kubescape get openvulnerabilityexchangecontainer \ + "$(kubectl -n kubescape get openvulnerabilityexchangecontainer -o jsonpath='{.items[0].metadata.name}')" \ + -o jsonpath='{.spec}' > vex.json diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..e06585c --- /dev/null +++ b/install.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -x + +kubectl apply -f "$DEPLOYMENT_FILE" +$READY_CONDITION diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..a62e1e9 --- /dev/null +++ b/setup.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -x + +# Install kind and kubectl +curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-$(uname)-amd64 +chmod +x ./kind +./kind create cluster +curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.21.0/bin/linux/amd64/kubectl +chmod +x ./kubectl +sudo mv ./kubectl /usr/local/bin/kubectl +# Install helm +curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 +chmod 700 get_helm.sh +sudo ./get_helm.sh +# Install Kubescape +helm repo add kubescape https://kubescape.github.io/helm-charts/ +helm repo update +helm upgrade --install kubescape kubescape/kubescape-operator -n kubescape --create-namespace --set clusterName="$(kubectl config current-context)" --set capabilities.vexGeneration=enable +# Wait for the pod to be ready +sleep 15 +kubectl get pods -n kubescape +kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=node-agent -n kubescape --timeout=300s diff --git a/wait.sh b/wait.sh new file mode 100755 index 0000000..cbac680 --- /dev/null +++ b/wait.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -x + +sleep "$WAIT_TIME" From af4f855a38f751b67da833d6a4e8235642abf85a Mon Sep 17 00:00:00 2001 From: Shlomo Heigh Date: Tue, 14 May 2024 09:41:50 -0400 Subject: [PATCH 2/4] Add self-test workflow --- .github/workflows/generate-vex.yaml | 58 ----------------------------- .github/workflows/self-test.yaml | 23 ++++++++++++ action.yml | 2 +- 3 files changed, 24 insertions(+), 59 deletions(-) delete mode 100644 .github/workflows/generate-vex.yaml create mode 100644 .github/workflows/self-test.yaml diff --git a/.github/workflows/generate-vex.yaml b/.github/workflows/generate-vex.yaml deleted file mode 100644 index 605bd54..0000000 --- a/.github/workflows/generate-vex.yaml +++ /dev/null @@ -1,58 +0,0 @@ -name: Generate VEX workflow -on: - pull_request: - paths-ignore: - - '**/*.md' - types: [labeled, synchronize, ready_for_review, opened, reopened] - -jobs: - generate-vex: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Kind - run: | - curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-$(uname)-amd64 - chmod +x ./kind - ./kind create cluster - curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.21.0/bin/linux/amd64/kubectl - chmod +x ./kubectl - sudo mv ./kubectl /usr/local/bin/kubectl - - - name: Install Helm and Kubectl - run: | - curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 - chmod 700 get_helm.sh - sudo ./get_helm.sh - - - name: Install Kubescape - run: | - helm repo add kubescape https://kubescape.github.io/helm-charts/ - helm repo update - helm upgrade --install kubescape kubescape/kubescape-operator -n kubescape --create-namespace --set clusterName=`kubectl config current-context` --set capabilities.vexGeneration=enable - # Wait for the pod to be ready - kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=node-agent -n kubescape --timeout=300s - - - name: Install Nginx - run: | - kubectl apply -f https://k8s.io/examples/application/deployment.yaml - kubectl wait --for=condition=ready pod -l app=nginx --timeout=300s - - - name: Wait 2 minutes - run: sleep 120 # Wait for ebpf to collect data - - - name: Generate VEX - run: | - kubectl -n kubescape get openvulnerabilityexchangecontainer $(kubectl -n kubescape get openvulnerabilityexchangecontainer -o jsonpath='{.items[0].metadata.name}') -o jsonpath='{.spec}' > nginx.json - - - name: Upload VEX document - if: always() - uses: actions/upload-artifact@v2 - with: - name: VEX document - path: "nginx.json" - - diff --git a/.github/workflows/self-test.yaml b/.github/workflows/self-test.yaml new file mode 100644 index 0000000..71dd77b --- /dev/null +++ b/.github/workflows/self-test.yaml @@ -0,0 +1,23 @@ +name: Self test +on: + push: + branches: + - main + pull_request: + paths-ignore: + - '**/*.md' + types: [labeled, synchronize, ready_for_review, opened, reopened] + +jobs: + generate_vex: + runs-on: ubuntu-latest + name: Test VEX Generation + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Test VEX Generation + uses: ./ + with: + deployment-file: "https://k8s.io/examples/application/deployment.yaml" + ready-condition: "kubectl wait --for=condition=ready pod -l app=nginx --timeout=300s" diff --git a/action.yml b/action.yml index ea95798..8dd86ff 100644 --- a/action.yml +++ b/action.yml @@ -9,7 +9,7 @@ inputs: required: true wait-time: description: "Time to wait (in seconds) before collecting VEX info, after the ready-condition is met" - default: "120" + default: "300" runs: using: "composite" From f61232f299de970d8ac1880b8d67401a5be26bde Mon Sep 17 00:00:00 2001 From: Shlomo Heigh Date: Tue, 14 May 2024 14:38:57 -0400 Subject: [PATCH 3/4] Use Helm chart and add test command --- .github/workflows/self-test.yaml | 12 ++++++++++-- action.yml | 24 ++++++++++++------------ generate.sh | 25 +++++++++++++++++++++++++ install.sh | 7 +++++-- self-test/test.sh | 17 +++++++++++++++++ setup.sh | 15 +++++++++++---- test.sh | 6 ++++++ wait.sh | 4 ---- 8 files changed, 86 insertions(+), 24 deletions(-) create mode 100755 self-test/test.sh create mode 100755 test.sh delete mode 100755 wait.sh diff --git a/.github/workflows/self-test.yaml b/.github/workflows/self-test.yaml index 71dd77b..87a1ec8 100644 --- a/.github/workflows/self-test.yaml +++ b/.github/workflows/self-test.yaml @@ -15,9 +15,17 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + + - name: Checkout nginx helm chart + uses: actions/checkout@v4 + with: + repository: helm/examples + path: charts/examples + ref: main - name: Test VEX Generation uses: ./ with: - deployment-file: "https://k8s.io/examples/application/deployment.yaml" - ready-condition: "kubectl wait --for=condition=ready pod -l app=nginx --timeout=300s" + helm-chart-path: "charts/examples/charts/hello-world" + ready-condition: "kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=hello-world --timeout=300s" + test-command: "self-test/test.sh" diff --git a/action.yml b/action.yml index 8dd86ff..1dcc643 100644 --- a/action.yml +++ b/action.yml @@ -1,15 +1,15 @@ name: "Generate VEX with Kubescape" description: "Generate VEX with Kubescape" inputs: - deployment-file: - description: "Path / URL to the deployment file" + helm-chart-path: + description: "Path to Helm chart to test" required: true ready-condition: description: "Condition to wait for before collecting VEX info" - required: true - wait-time: - description: "Time to wait (in seconds) before collecting VEX info, after the ready-condition is met" - default: "300" + required: false + test-command: + description: "Command to run to test the deployment" + required: false runs: using: "composite" @@ -18,17 +18,17 @@ runs: run: $GITHUB_ACTION_PATH/setup.sh shell: bash - - name: "Install deployment" + - name: "Install Helm Chart" env: - DEPLOYMENT_FILE: ${{ inputs.deployment-file }} + HELM_CHART_PATH: ${{ inputs.helm-chart-path }} READY_CONDITION: ${{ inputs.ready-condition }} run: $GITHUB_ACTION_PATH/install.sh shell: bash - - name: "Wait for deployment to be ready" - env: - WAIT_TIME: ${{ inputs.wait-time }} - run: $GITHUB_ACTION_PATH/wait.sh + - name: "Run tests" + env: + TEST_COMMAND: ${{ inputs.test-command }} + run: $GITHUB_ACTION_PATH/test.sh shell: bash - name: "Generate VEX" diff --git a/generate.sh b/generate.sh index 40b6e07..724cbff 100755 --- a/generate.sh +++ b/generate.sh @@ -1,6 +1,31 @@ #!/usr/bin/env bash set -x +timeout=300 +start_time=$SECONDS +while [[ -z $(kubectl -n kubescape get openvulnerabilityexchangecontainers.spdx.softwarecomposition.kubescape.io) ]]; do + echo "Waiting for VEX generation..." + sleep 10 + if [[ $((SECONDS - start_time)) -gt $timeout ]]; then + echo "Timeout reached. Exiting..." + + # Loop through all pods in the kubescape namespace and print the logs + for pod in $(kubectl -n kubescape get pods -o jsonpath='{.items[*].metadata.name}'); do + echo "Logs for $pod:" + kubectl -n kubescape logs "$pod" + done + + break + fi +done + +echo "Saving VEX results..." kubectl -n kubescape get openvulnerabilityexchangecontainer \ "$(kubectl -n kubescape get openvulnerabilityexchangecontainer -o jsonpath='{.items[0].metadata.name}')" \ -o jsonpath='{.spec}' > vex.json + +echo "Affected:" +jq "." vex.json | grep -c "\"affected\"" + +echo "Not affected:" +jq "." vex.json | grep -c "\"not_affected\"" diff --git a/install.sh b/install.sh index e06585c..75ae09e 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash set -x -kubectl apply -f "$DEPLOYMENT_FILE" -$READY_CONDITION +helm install "$HELM_CHART_PATH" --wait --timeout 300s --generate-name + +if [[ -n "$READY_CONDITION" ]]; then + $READY_CONDITION +fi diff --git a/self-test/test.sh b/self-test/test.sh new file mode 100755 index 0000000..338d065 --- /dev/null +++ b/self-test/test.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -x + +# Get the pod name and container port of the test application +POD_NAME=$(kubectl get pods -l "app.kubernetes.io/name=hello-world" -o jsonpath="{.items[0].metadata.name}") +CONTAINER_PORT=$(kubectl get pod "$POD_NAME" -o jsonpath="{.spec.containers[0].ports[0].containerPort}") +# Expose the test app on localhost +kubectl port-forward "$POD_NAME" 8080:"$CONTAINER_PORT" & +sleep 5 +# Test the application by sending a request to it a number of times +for _ in {1..10}; do + # Prints just the status code + curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080 + echo +done +# Stop the port-forwarding +kill %1 diff --git a/setup.sh b/setup.sh index a62e1e9..ee96518 100755 --- a/setup.sh +++ b/setup.sh @@ -15,8 +15,15 @@ sudo ./get_helm.sh # Install Kubescape helm repo add kubescape https://kubescape.github.io/helm-charts/ helm repo update -helm upgrade --install kubescape kubescape/kubescape-operator -n kubescape --create-namespace --set clusterName="$(kubectl config current-context)" --set capabilities.vexGeneration=enable +helm upgrade --install kubescape kubescape/kubescape-operator -n kubescape --create-namespace \ + --set clusterName="$(kubectl config current-context)" \ + --set capabilities.vexGeneration=enable \ + --set nodeAgent.config.learningPeriod=1m \ + --set nodeAgent.config.updatePeriod=1m \ + --set logger.level=debug \ + --wait # Wait for the pod to be ready -sleep 15 -kubectl get pods -n kubescape -kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=node-agent -n kubescape --timeout=300s +sleep 5 +kubectl -n kubescape wait --for=condition=ready pod -l app.kubernetes.io/name=node-agent --timeout=300s +kubectl -n kubescape wait --for=condition=ready pod -l app.kubernetes.io/name=storage --timeout=300s +echo "Kubescape is ready" \ No newline at end of file diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..49fa45c --- /dev/null +++ b/test.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -x + +if [[ -n "$TEST_COMMAND" ]]; then + $TEST_COMMAND +fi diff --git a/wait.sh b/wait.sh deleted file mode 100755 index cbac680..0000000 --- a/wait.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -x - -sleep "$WAIT_TIME" From 25843bd7156b28f50088549bcf5ee6bd91cafeb8 Mon Sep 17 00:00:00 2001 From: Shlomo Heigh Date: Tue, 21 May 2024 13:50:54 -0400 Subject: [PATCH 4/4] Add README.md --- .../{self-test.yaml => self-test.yml} | 0 README.md | 38 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) rename .github/workflows/{self-test.yaml => self-test.yml} (100%) diff --git a/.github/workflows/self-test.yaml b/.github/workflows/self-test.yml similarity index 100% rename from .github/workflows/self-test.yaml rename to .github/workflows/self-test.yml diff --git a/README.md b/README.md index 21929a4..1225bef 100644 --- a/README.md +++ b/README.md @@ -1 +1,37 @@ -# generate-vex-action +# Generate VEX with Kubescape GitHub Action + +This GitHub Action is designed to generate a VEX (Vulnerability Exploitability +eXchange) document using Kubescape. It does this by deploying a Helm chart to a +KinD cluster and running a test command, which should run your suite of tests +and thereby generate usage information for Kubescape. The Action will then +produce a VEX document based on the information collected and save it as an +artifact. + +## Inputs + +- `helm-chart-path`: (Required) The path to the Helm chart that you want to + test. +- `ready-condition`: (Optional) A condition that the action should wait for + before collecting VEX information. +- `test-command`: (Optional) The command that should be run to test the + deployment. This should be a command that runs your suite of tests. If + omitted, no tests will be run and the VEX document will likely be unreliable + as there will be no real usage information. + +## Usage + +To use this action in your workflow, include it in your workflow file with the required inputs: + +```yaml +- uses: slashben/generate-vex-action@v1 + with: + helm-chart-path: './path/to/your/chart' # Ex. './charts/hello-world' + ready-condition: 'your-condition' # Ex. `kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=hello-world --timeout=300s` + test-command: 'your-test-command' # Ex. `./ci/test_integration.sh` +``` + +Replace `your-org/generate-vex-with-kubescape@v1` with the actual repository and version of the action. Replace the values of `helm-chart-path`, `ready-condition`, and `test-command` with your actual values. + +## Example + +To see an example of this Action in use, check out the [self test](./.github/workflows/self-test.yml) workflow in this repository.