├── .cargo
└── audit.toml
├── .chglog
├── CHANGELOG.tpl.md
└── config.yml
├── .clomonitor.yml
├── .github
├── release-drafter.yml
└── workflows
│ ├── build.yml
│ ├── ci.yml
│ ├── fossa.yml
│ ├── openssf.yml
│ ├── release-drafter.yml
│ ├── release.yml
│ ├── security-audit-cron.yml
│ ├── security-audit-reactive.yml
│ └── update-rust-toolchain.yaml
├── .gitignore
├── .taplo.toml
├── CODEOWNERS
├── CONTRIBUTING.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── Makefile
├── README.md
├── SECURITY-INSIGHTS.yml
├── cli-docs.md
├── config.toml
├── coverage
├── integration-tests
│ └── .gitignore
└── unit-tests
│ └── .gitignore
├── renovate.json
├── rust-toolchain.toml
├── scripts
├── kubewarden-load-policies.sh
└── kubewarden-save-policies.sh
├── src
├── annotate.rs
├── backend.rs
├── bench.rs
├── callback_handler
│ ├── mod.rs
│ └── proxy.rs
├── cli.rs
├── completions.rs
├── info.rs
├── inspect.rs
├── load.rs
├── main.rs
├── policies.rs
├── pull.rs
├── push.rs
├── rm.rs
├── run.rs
├── save.rs
├── scaffold.rs
├── scaffold
│ ├── admission_request.rs
│ ├── artifacthub.rs
│ ├── kubewarden_crds.rs
│ ├── manifest.rs
│ ├── vap.rs
│ └── verification_config.rs
├── utils.rs
└── verify.rs
├── tests
├── airgap.rs
├── common
│ └── mod.rs
├── data
│ ├── airgap
│ │ └── policies.txt
│ ├── artifacthub
│ │ └── metadata.yml
│ ├── context-aware-policy-request-pod-creation-all-labels.json
│ ├── host-capabilities-sessions
│ │ ├── context-aware-demo-namespace-found.yml
│ │ ├── context-aware-demo-namespace-not-found.yml
│ │ ├── context-aware-unique-ingress-duplicate.yml
│ │ └── context-aware-unique-ingress-no-duplicate.yml
│ ├── ingress.json
│ ├── privileged-pod-admission-review.json
│ ├── privileged-pod.json
│ ├── raw.json
│ ├── rego-annotate
│ │ ├── metadata-correct.yml
│ │ ├── metadata-wrong.yml
│ │ └── no-default-namespace-rego.wasm
│ ├── sigstore
│ │ ├── README.md
│ │ ├── cosign1.key
│ │ ├── cosign1.pub
│ │ ├── cosign2.key
│ │ ├── cosign2.pub
│ │ ├── cosign3.key
│ │ ├── cosign3.pub
│ │ ├── verification-config-keyless.yml
│ │ └── verification-config.yml
│ ├── unprivileged-pod-admission-review.json
│ ├── unprivileged-pod.json
│ └── vap
│ │ ├── vap-binding.yml
│ │ ├── vap-with-variables.yml
│ │ └── vap-without-variables.yml
├── e2e.rs
└── secure_supply_chain_e2e.rs
└── updatecli
├── DEVELOP.md
├── updatecli.d
└── update-rust-toolchain.yaml
└── values.yaml
/.cargo/audit.toml:
--------------------------------------------------------------------------------
1 | [advisories]
2 | ignore = [
3 | "RUSTSEC-2020-0071", # `time` localtime_r segfault -- https://rustsec.org/advisories/RUSTSEC-2020-0071
4 | # Ignored because there are not known workarounds or dependency version bump
5 | # at this time. The call to localtime_r is not protected by any lock and can
6 | # cause unsoundness. Read the previous link for more information.
7 | "RUSTSEC-2020-0168", # This is about "mach" being unmaintained.
8 | # This is a transitive dependency of wasmtime. This is
9 | # being tracked upstream via https://github.com/bytecodealliance/wasmtime/issues/6000
10 | # This is a transitive depependency of sigstore
11 | "RUSTSEC-2023-0071", # "Classic" RSA timing sidechannel attack from non-constant-time implementation.
12 | # Okay for local use.
13 | # https://rustsec.org/advisories/RUSTSEC-2023-0071.html
14 | "RUSTSEC-2023-0081", # This is about `safemem` being unmaintained.
15 | # This is a transitive dependency of syntect. This bug is tracked upstream inside of
16 | # https://github.com/trishume/syntect/issues/521
17 | "RUSTSEC-2024-0370", # This is a warning about `proc-macro-errors` being unmaintained. It's a transitive dependency of `sigstore` and `oci-spec`.
18 | "RUSTSEC-2023-0055", # This is a warning about `lexical` having multiple soundness issues. It's a transitive dependency of `sigstore`.
19 | ]
20 |
--------------------------------------------------------------------------------
/.chglog/CHANGELOG.tpl.md:
--------------------------------------------------------------------------------
1 | {{ if .Versions -}}
2 |
3 | ## [Unreleased]
4 |
5 | {{ if .Unreleased.CommitGroups -}}
6 | {{ range .Unreleased.CommitGroups -}}
7 | ### {{ .Title }}
8 | {{ range .Commits -}}
9 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
10 | {{ end }}
11 | {{ end -}}
12 | {{ end -}}
13 | {{ end -}}
14 |
15 | {{ range .Versions }}
16 |
17 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }}
18 | {{ range .CommitGroups -}}
19 | ### {{ .Title }}
20 | {{ range .Commits -}}
21 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
22 | {{ end }}
23 | {{ end -}}
24 |
25 | {{- if .RevertCommits -}}
26 | ### Reverts
27 | {{ range .RevertCommits -}}
28 | - {{ .Revert.Header }}
29 | {{ end }}
30 | {{ end -}}
31 |
32 | {{- if .MergeCommits -}}
33 | ### Pull Requests
34 | {{ range .MergeCommits -}}
35 | - {{ .Header }}
36 | {{ end }}
37 | {{ end -}}
38 |
39 | {{- if .NoteGroups -}}
40 | {{ range .NoteGroups -}}
41 | ### {{ .Title }}
42 | {{ range .Notes }}
43 | {{ .Body }}
44 | {{ end }}
45 | {{ end -}}
46 | {{ end -}}
47 | {{ end -}}
48 |
49 | {{- if .Versions }}
50 | [Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
51 | {{ range .Versions -}}
52 | {{ if .Tag.Previous -}}
53 | [{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
54 | {{ end -}}
55 | {{ end -}}
56 | {{ end -}}
--------------------------------------------------------------------------------
/.chglog/config.yml:
--------------------------------------------------------------------------------
1 | style: github
2 | template: CHANGELOG.tpl.md
3 | info:
4 | title: CHANGELOG
5 | repository_url: https://github.com/kubewarden/kwctl
6 | options:
7 | commits:
8 | filters:
9 | Type:
10 | - feat
11 | - fix
12 | - perf
13 | - refactor
14 | commit_groups:
15 | title_maps:
16 | feat: Features
17 | fix: Bug Fixes
18 | perf: Performance Improvements
19 | refactor: Code Refactoring
20 | header:
21 | pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
22 | pattern_maps:
23 | - Type
24 | - Scope
25 | - Subject
26 | notes:
27 | keywords:
28 | - BREAKING CHANGE
29 |
--------------------------------------------------------------------------------
/.clomonitor.yml:
--------------------------------------------------------------------------------
1 | # CLOMonitor metadata file
2 | # This file must be located at the root of the repository
3 |
4 | # Checks exemptions
5 | exemptions:
6 | - check: artifacthub_badge # Check identifier (see https://github.com/cncf/clomonitor/blob/main/docs/checks.md#exemptions)
7 | reason: "kwctl is a cli binary, can't be published in ArtifactHub" # Justification of this exemption (mandatory, it will be displayed on the UI)
8 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | categories:
2 | - title: '⚠️ Breaking changes'
3 | labels:
4 | - 'kind/major'
5 | - 'kind/breaking-change'
6 | - title: '🚀 Features'
7 | labels:
8 | - 'kind/enhancement'
9 | - 'kind/feature'
10 | - title: '🐛 Bug Fixes'
11 | labels:
12 | - 'kind/bug'
13 | - title: '🧰 Maintenance'
14 | labels:
15 | - 'kind/chore'
16 | - 'area/dependencies'
17 |
18 | exclude-labels:
19 | - duplicate
20 | - invalid
21 | - later
22 | - wontfix
23 | - kind/question
24 | - release/skip-changelog
25 |
26 | change-template: '- $TITLE (#$NUMBER)'
27 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
28 | name-template: 'v$RESOLVED_VERSION'
29 | template: |
30 | $CHANGES
31 |
32 | autolabeler:
33 | # Tag any PR with "!" in the subject as major update. In other words, breaking change
34 | - label: 'kind/breaking-change'
35 | title: '/.*!:.*/'
36 | - label: 'area/dependencies'
37 | title: 'chore(deps)'
38 | - label: 'area/dependencies'
39 | title: 'fix(deps)'
40 | - label: 'area/dependencies'
41 | title: 'build(deps)'
42 | - label: 'kind/feature'
43 | title: 'feat'
44 | - label: 'kind/bug'
45 | title: 'fix'
46 | - label: 'kind/chore'
47 | title: 'chore'
48 |
49 | version-resolver:
50 | major:
51 | labels:
52 | - 'kind/major'
53 | - 'kind/breaking-change'
54 | minor:
55 | labels:
56 | - 'kind/minor'
57 | - 'kind/feature'
58 | - 'kind/enhancement'
59 | patch:
60 | labels:
61 | - 'kind/patch'
62 | - 'kind/fix'
63 | - 'kind/bug'
64 | - 'kind/chore'
65 | - 'area/dependencies'
66 | default: patch
67 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: kwctl build
2 | on:
3 | workflow_call:
4 | push:
5 | branches:
6 | - "main"
7 | - "feat-**"
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 |
12 | jobs:
13 | build-linux-binaries:
14 | name: Build linux binaries
15 | runs-on: ubuntu-latest
16 | strategy:
17 | matrix:
18 | targetarch:
19 | - aarch64
20 | - x86_64
21 | permissions:
22 | id-token: write
23 | attestations: write
24 | steps:
25 | - uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
26 |
27 | - name: checkout code
28 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
29 |
30 | - name: Install cross-rs
31 | run: |
32 | set -e
33 |
34 | echo "$CROSS_CHECKSUM cross-x86_64-unknown-linux-musl.tar.gz" > checksum
35 | curl -L -O https://github.com/cross-rs/cross/releases/download/$CROSS_VERSION/cross-x86_64-unknown-linux-musl.tar.gz
36 | sha512sum -c checksum
37 | tar -xvf cross-x86_64-unknown-linux-musl.tar.gz
38 | env:
39 | CROSS_CHECKSUM: "70b31b207e981aa31925a7519a0ad125c5d97b84afe0e8e81b0664df5c3a7978558d83f9fcd0c36dc2176fc2a4d0caed67f8cf9fd689f9935f84449cd4922ceb"
40 | CROSS_VERSION: "v0.2.5"
41 |
42 | - name: Build kwctl
43 | shell: bash
44 | run: |
45 | ./cross build --release --target ${{matrix.targetarch}}-unknown-linux-musl
46 |
47 | - run: mv target/${{ matrix.targetarch }}-unknown-linux-musl/release/kwctl kwctl-linux-${{ matrix.targetarch }}
48 |
49 | - name: Smoke test build
50 | if: matrix.targetarch == 'x86_64'
51 | run: ./kwctl-linux-x86_64 --help
52 |
53 | - name: Generate attestations
54 | uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
55 | id: attestations
56 | with:
57 | subject-path: kwctl-linux-${{ matrix.targetarch }}
58 |
59 | - name: Sign kwctl
60 | run: |
61 | cosign sign-blob --yes kwctl-linux-${{ matrix.targetarch }} --output-certificate kwctl-linux-${{ matrix.targetarch}}.pem --output-signature kwctl-linux-${{ matrix.targetarch }}.sig
62 |
63 | - run: zip -j9 kwctl-linux-${{ matrix.targetarch }}.zip kwctl-linux-${{ matrix.targetarch }} kwctl-linux-${{ matrix.targetarch }}.sig kwctl-linux-${{ matrix.targetarch }}.pem
64 |
65 | - name: Upload binary
66 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
67 | with:
68 | name: kwctl-linux-${{ matrix.targetarch }}
69 | path: kwctl-linux-${{ matrix.targetarch }}.zip
70 |
71 | - name: Install the syft command
72 | uses: kubewarden/github-actions/syft-installer@7195340a122321bf547fda2ffc07eed6f6ae43f6 # v4.5.1
73 |
74 | - name: Create SBOM file
75 | shell: bash
76 | run: |
77 | syft \
78 | --file kwctl-linux-${{ matrix.targetarch }}-sbom.spdx \
79 | --output spdx-json \
80 | --source-name kwctl-linux-${{ matrix.targetarch }} \
81 | --source-version ${{ github.sha }} \
82 | -vv \
83 | dir:. # use dir default catalogers, which includes Cargo.toml
84 |
85 | - name: Sign SBOM file
86 | run: |
87 | cosign sign-blob --yes \
88 | --output-certificate kwctl-linux-${{ matrix.targetarch }}-sbom.spdx.cert \
89 | --output-signature kwctl-linux-${{ matrix.targetarch }}-sbom.spdx.sig \
90 | kwctl-linux-${{ matrix.targetarch }}-sbom.spdx
91 |
92 | - name: Upload kwctl SBOM files
93 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
94 | with:
95 | name: kwctl-linux-${{ matrix.targetarch }}-sbom
96 | path: |
97 | kwctl-linux-${{ matrix.targetarch }}-sbom.spdx
98 | kwctl-linux-${{ matrix.targetarch }}-sbom.spdx.cert
99 | kwctl-linux-${{ matrix.targetarch }}-sbom.spdx.sig
100 |
101 | - name: Upload kwctl air gap scripts
102 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
103 | if: matrix.targetarch == 'x86_64' # only upload the scripts once
104 | with:
105 | name: kwctl-airgap-scripts
106 | path: |
107 | scripts/kubewarden-load-policies.sh
108 | scripts/kubewarden-save-policies.sh
109 |
110 | build-darwin-binaries:
111 | name: Build darwin binary
112 | strategy:
113 | matrix:
114 | targetarch: ["aarch64", "x86_64"]
115 | runs-on: macos-latest
116 | permissions:
117 | id-token: write
118 | attestations: write
119 | steps:
120 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
121 |
122 | - uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
123 |
124 | - run: rustup target add ${{ matrix.targetarch }}-apple-darwin
125 |
126 | - name: Build kwctl
127 | run: cargo build --target=${{ matrix.targetarch }}-apple-darwin --release
128 |
129 | - run: mv target/${{ matrix.targetarch }}-apple-darwin/release/kwctl kwctl-darwin-${{ matrix.targetarch }}
130 |
131 | - name: Smoke test build
132 | if: matrix.targetarch == 'x86_64'
133 | run: ./kwctl-darwin-x86_64 --help
134 |
135 | - name: Generate attestations
136 | uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
137 | id: attestations
138 | with:
139 | subject-path: kwctl-darwin-${{ matrix.targetarch }}
140 |
141 | - name: Sign kwctl
142 | run: cosign sign-blob --yes kwctl-darwin-${{ matrix.targetarch }} --output-certificate kwctl-darwin-${{ matrix.targetarch }}.pem --output-signature kwctl-darwin-${{ matrix.targetarch }}.sig
143 |
144 | - run: zip -j9 kwctl-darwin-${{ matrix.targetarch }}.zip kwctl-darwin-${{ matrix.targetarch }} kwctl-darwin-${{ matrix.targetarch }}.sig kwctl-darwin-${{ matrix.targetarch }}.pem
145 |
146 | - name: Upload binary
147 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
148 | with:
149 | name: kwctl-darwin-${{ matrix.targetarch }}
150 | path: kwctl-darwin-${{ matrix.targetarch }}.zip
151 |
152 | - name: Install the syft command
153 | uses: kubewarden/github-actions/syft-installer@7195340a122321bf547fda2ffc07eed6f6ae43f6 # v4.5.1
154 | with:
155 | arch: darwin_amd64
156 |
157 | - name: Create SBOM file
158 | shell: bash
159 | run: |
160 | syft \
161 | --file kwctl-darwin-${{ matrix.targetarch }}-sbom.spdx \
162 | --output spdx-json \
163 | --source-name kwctl-darwin-${{ matrix.targetarch }} \
164 | --source-version ${{ github.sha }} \
165 | -vv \
166 | dir:. # use dir default catalogers, which includes Cargo.toml
167 |
168 | - name: Sign SBOM file
169 | run: |
170 | cosign sign-blob --yes \
171 | --output-certificate kwctl-darwin-${{ matrix.targetarch }}-sbom.spdx.cert \
172 | --output-signature kwctl-darwin-${{ matrix.targetarch }}-sbom.spdx.sig \
173 | kwctl-darwin-${{ matrix.targetarch }}-sbom.spdx
174 |
175 | - name: Upload kwctl SBOM files
176 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
177 | with:
178 | name: kwctl-darwin-${{ matrix.targetarch }}-sbom
179 | path: |
180 | kwctl-darwin-${{ matrix.targetarch }}-sbom.spdx
181 | kwctl-darwin-${{ matrix.targetarch }}-sbom.spdx.cert
182 | kwctl-darwin-${{ matrix.targetarch }}-sbom.spdx.sig
183 |
184 | build-windows-x86_64:
185 | name: Build windows (x86_64) binary
186 | strategy:
187 | matrix:
188 | # workaround to have the same GH UI for all jobs
189 | targetarch: ["x86_64"]
190 | os: ["windows-latest"]
191 | runs-on: ${{ matrix.os }}
192 | permissions:
193 | id-token: write
194 | attestations: write
195 | steps:
196 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
197 |
198 | - uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
199 |
200 | - name: enable git long paths on Windows
201 | if: matrix.os == 'windows-latest'
202 | run: |
203 | echo 'CMAKE_POLICY_VERSION_MINIMUM="3.5"' >> $GITHUB_ENV
204 |
205 | # aws-lc-sys CMakefile contains a directive that has been removed from
206 | # cmake v4 that has just been released (march 2025). The build failure
207 | # can be fixed by setting an environment variable
208 | - name: fix aws-lc-sys building with cmake 4.0.0
209 | run: set CMAKE_POLICY_VERSION_MINIMUM="3.5"
210 |
211 | - name: Build kwctl
212 | run: cargo build --target=x86_64-pc-windows-msvc --release
213 |
214 | - run: mv target/x86_64-pc-windows-msvc/release/kwctl.exe kwctl-windows-x86_64.exe
215 |
216 | - name: Smoke test build
217 | run: .\kwctl-windows-x86_64.exe --help
218 |
219 | - name: Generate attestations
220 | uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
221 | id: attestations
222 | with:
223 | subject-path: kwctl-windows-${{ matrix.targetarch }}.exe
224 |
225 | - name: Sign kwctl
226 | run: cosign sign-blob --yes kwctl-windows-x86_64.exe --output-certificate kwctl-windows-x86_64.pem --output-signature kwctl-windows-x86_64.sig
227 |
228 | - run: |
229 | "/c/Program Files/7-Zip/7z.exe" a kwctl-windows-x86_64.exe.zip kwctl-windows-x86_64.exe kwctl-windows-x86_64.sig kwctl-windows-x86_64.pem
230 | shell: bash
231 |
232 | - name: Upload binary
233 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
234 | with:
235 | name: kwctl-windows-x86_64
236 | path: kwctl-windows-x86_64.exe.zip
237 |
238 | - name: Install the syft command
239 | uses: kubewarden/github-actions/syft-installer@7195340a122321bf547fda2ffc07eed6f6ae43f6 # v4.5.1
240 | with:
241 | arch: windows_amd64
242 |
243 | - name: Create SBOM file
244 | shell: bash
245 | run: |
246 | syft \
247 | --file kwctl-windows-x86_64-sbom.spdx \
248 | --output spdx-json \
249 | --source-name kwctl-windows-x86_64 \
250 | --source-version ${{ github.sha }} \
251 | -vv \
252 | dir:. # use dir default catalogers, which includes Cargo.toml
253 |
254 | - name: Sign SBOM file
255 | shell: bash
256 | run: |
257 | cosign sign-blob --yes \
258 | --output-certificate kwctl-windows-x86_64-sbom.spdx.cert \
259 | --output-signature kwctl-windows-x86_64-sbom.spdx.sig \
260 | kwctl-windows-x86_64-sbom.spdx
261 |
262 | - name: Upload kwctl SBOM files
263 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
264 | with:
265 | name: kwctl-windows-x86_64-sbom
266 | path: |
267 | kwctl-windows-x86_64-sbom.spdx
268 | kwctl-windows-x86_64-sbom.spdx.cert
269 | kwctl-windows-x86_64-sbom.spdx.sig
270 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | on:
2 | - push
3 | - pull_request
4 | - workflow_call
5 |
6 | name: Continuous integration
7 |
8 | # Declare default permissions as read only.
9 | permissions: read-all
10 |
11 | env:
12 | CARGO_TERM_COLOR: always
13 |
14 | jobs:
15 | check:
16 | name: Cargo check
17 | runs-on: ${{ matrix.os }}
18 | strategy:
19 | matrix:
20 | os: [ubuntu-latest, macos-latest, windows-latest]
21 | steps:
22 | - name: enable git long paths on Windows
23 | if: matrix.os == 'windows-latest'
24 | run: git config --global core.longpaths true
25 |
26 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
27 |
28 | # aws-lc-sys CMakefile contains a directive that has been removed from
29 | # cmake v4 that has just been released (march 2025). The build failure
30 | # can be fixed by setting an environment variable
31 | - name: fix aws-lc-sys building with cmake 4.0.0
32 | if: matrix.os == 'windows-latest'
33 | run: |
34 | echo 'CMAKE_POLICY_VERSION_MINIMUM="3.5"' >> $GITHUB_ENV
35 |
36 | - name: Run cargo check
37 | run: cargo check
38 |
39 | version-check:
40 | name: Check Cargo.toml version
41 | if: github.ref_type == 'tag'
42 | runs-on: ubuntu-latest
43 | steps:
44 | - name: Download source code
45 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
46 | - name: Check cargo file version
47 | run: |
48 | CARGO_VERSION=$(sed -n 's,^version\s*= \"\(.*\)\",\1,p' Cargo.toml)
49 | TAG_VERSION=$(echo ${{ github.ref_name }} | sed 's/v//')
50 |
51 | if [ "$CARGO_VERSION" != "$TAG_VERSION" ];then
52 | echo "::error title=Invalid Cargo.toml version::Cargo.toml version does not match the tag version"
53 | exit 1
54 | fi
55 |
56 | test:
57 | name: Unit tests
58 | runs-on: ubuntu-latest
59 | steps:
60 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
61 | - name: Run cargo test
62 | run: cargo test --workspace --bins
63 |
64 | e2e-tests:
65 | name: E2E tests
66 | runs-on: ubuntu-latest
67 | steps:
68 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
69 | - uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
70 | - name: run e2e tests
71 | run: make e2e-tests
72 |
73 | coverage:
74 | name: coverage
75 | runs-on: ubuntu-latest
76 | continue-on-error: true
77 | steps:
78 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
79 |
80 | - name: Install cargo-llvm-cov
81 | uses: taiki-e/install-action@7bf3bbf3104a2e9a77906ccbdf6d4aa6a87b0210 # v2.52.5
82 | with:
83 | tool: cargo-llvm-cov
84 |
85 | - name: Install cosign # this is needed by some of the e2e tests
86 | uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
87 |
88 | - name: Generate tests coverage
89 | run: cargo llvm-cov --lcov --output-path lcov.info
90 |
91 | - name: Upload unit-tests coverage to Codecov
92 | uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
93 | with:
94 | files: lcov.info
95 | fail_ci_if_error: true
96 | name: unit-tests and e2e-tests
97 | verbose: true
98 | token: ${{ secrets.CODECOV_ORG_TOKEN }}
99 |
100 | fmt:
101 | name: Rustfmt
102 | runs-on: ubuntu-latest
103 | steps:
104 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
105 | - run: rustup component add rustfmt
106 | - name: Run cargo fmt
107 | run: cargo fmt --all -- --check
108 |
109 | clippy:
110 | name: Clippy
111 | runs-on: ubuntu-latest
112 | steps:
113 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
114 | - run: rustup component add clippy
115 | - name: Run cargo clippy
116 | run: cargo clippy -- -D warnings
117 |
118 | shellcheck:
119 | name: Shellcheck
120 | runs-on: ubuntu-latest
121 | steps:
122 | - name: Checkout
123 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
124 |
125 | - run: shellcheck $(find scripts/ -name '*.sh')
126 |
127 | docs:
128 | name: Update documentation
129 | runs-on: ubuntu-latest
130 | steps:
131 | - name: Checkout
132 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
133 |
134 | - run: |
135 | make build-docs
136 | if ! git diff --quiet cli-docs.md; then
137 | echo "Changes detected in cli-docs.md. Please run `make build-docs` and commit the changes."
138 | gh run cancel ${{ github.run_id }}
139 | fi
140 |
141 | spelling:
142 | name: Spell Check with Typos
143 | runs-on: ubuntu-latest
144 | steps:
145 | - name: Checkout Actions Repository
146 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
147 | - name: Spell Check Repo
148 | uses: crate-ci/typos@b1ae8d918b6e85bd611117d3d9a3be4f903ee5e4 # v1.33.1
149 |
--------------------------------------------------------------------------------
/.github/workflows/fossa.yml:
--------------------------------------------------------------------------------
1 | name: fossa scanning
2 | on:
3 | push:
4 | tags:
5 | - "v*"
6 | branches:
7 | - "main"
8 |
9 | # Declare default permissions as read only.
10 | permissions: read-all
11 |
12 | jobs:
13 | fossa-scan:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
17 | - uses: fossas/fossa-action@3ebcea1862c6ffbd5cf1b4d0bd6b3fe7bd6f2cac # v1.7.0
18 | with:
19 | api-key: ${{secrets.FOSSA_API_TOKEN}}
20 |
--------------------------------------------------------------------------------
/.github/workflows/openssf.yml:
--------------------------------------------------------------------------------
1 | name: Scorecards supply-chain security
2 | on:
3 | push:
4 | branches: [main]
5 |
6 | # Declare default permissions as read only.
7 | permissions: read-all
8 |
9 | jobs:
10 | analysis:
11 | name: Scorecards analysis
12 | runs-on: ubuntu-latest
13 | permissions:
14 | # Needed to upload the results to code-scanning dashboard.
15 | security-events: write
16 | # Used to receive a badge. (Upcoming feature)
17 | id-token: write
18 |
19 | steps:
20 | - name: "Checkout code"
21 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
22 | with:
23 | persist-credentials: false
24 |
25 | - name: "Run analysis"
26 | uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
27 | with:
28 | results_file: results.sarif
29 | results_format: sarif
30 | # Publish the results for public repositories to enable scorecard badges. For more details, see
31 | # https://github.com/ossf/scorecard-action#publishing-results.
32 | publish_results: true
33 |
--------------------------------------------------------------------------------
/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name: Release Drafter
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | # branches to consider in the event; optional, defaults to all
7 | branches:
8 | - main
9 | # pull_request event is required only for autolabeler
10 | pull_request:
11 | # Only following types are handled by the action, but one can default to all as well
12 | types: [opened, reopened, synchronize, edited]
13 | # pull_request_target event is required for autolabeler to support PRs from forks
14 | pull_request_target:
15 | types: [opened, reopened, synchronize, edited]
16 |
17 | permissions:
18 | contents: read
19 |
20 | jobs:
21 | update_release_draft:
22 | permissions:
23 | # write permission is required to create a github release
24 | contents: write
25 | # write permission is required for autolabeler
26 | # otherwise, read permission is required at least
27 | pull-requests: write
28 | runs-on: ubuntu-latest
29 | steps:
30 | # Drafts your next Release notes as Pull Requests are merged into "master"
31 | - uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0
32 | # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
33 | # with:
34 | # config-name: my-config.yml
35 | # disable-autolabeler: true
36 | env:
37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: kwctl release
2 | on:
3 | push:
4 | tags:
5 | - "v*"
6 |
7 | # Declare default permissions as read only.
8 | permissions: read-all
9 |
10 | env:
11 | CARGO_TERM_COLOR: always
12 |
13 | jobs:
14 | ci:
15 | uses: ./.github/workflows/ci.yml
16 | permissions: read-all
17 |
18 | build:
19 | name: Build kwctl, sign it, and generate SBOMs
20 | uses: ./.github/workflows/build.yml
21 | permissions:
22 | id-token: write
23 | packages: write
24 | actions: read
25 | contents: write
26 | attestations: write
27 |
28 | release:
29 | name: Create release
30 |
31 | needs:
32 | - ci
33 | - build
34 |
35 | permissions:
36 | contents: write
37 |
38 | runs-on: ubuntu-latest
39 |
40 | steps:
41 | - name: Retrieve tag name
42 | if: ${{ startsWith(github.ref, 'refs/tags/') }}
43 | run: |
44 | echo TAG_NAME=$(echo ${{ github.ref_name }}) >> $GITHUB_ENV
45 |
46 | - name: Get latest release tag
47 | id: get_last_release_tag
48 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
49 | with:
50 | script: |
51 | let release = await github.rest.repos.getLatestRelease({
52 | owner: context.repo.owner,
53 | repo: context.repo.repo,
54 | });
55 |
56 | if (release.status === 200 ) {
57 | core.setOutput('old_release_tag', release.data.tag_name)
58 | return
59 | }
60 | core.setFailed("Cannot find latest release")
61 |
62 | - name: Get release ID from the release created by release drafter
63 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
64 | with:
65 | script: |
66 | let releases = await github.rest.repos.listReleases({
67 | owner: context.repo.owner,
68 | repo: context.repo.repo,
69 | });
70 | for (const release of releases.data) {
71 | if (release.draft) {
72 | core.info(release)
73 | core.exportVariable('RELEASE_ID', release.id)
74 | return
75 | }
76 | }
77 | core.setFailed(`Draft release not found`)
78 |
79 | - name: Download all artifacts
80 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
81 | # no name provided, download all artifacts. Puts them in folders.
82 |
83 | - name: Display structure of downloaded files
84 | run: ls -R
85 |
86 | - name: Upload release assets
87 | id: upload_release_assets
88 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
89 | with:
90 | script: |
91 | let fs = require('fs');
92 | let path = require('path');
93 |
94 | let files = [
95 | './kwctl-airgap-scripts/kubewarden-load-policies.sh',
96 | './kwctl-airgap-scripts/kubewarden-save-policies.sh',
97 | './kwctl-darwin-aarch64/kwctl-darwin-aarch64.zip',
98 | './kwctl-darwin-aarch64-sbom/kwctl-darwin-aarch64-sbom.spdx',
99 | './kwctl-darwin-aarch64-sbom/kwctl-darwin-aarch64-sbom.spdx.cert',
100 | './kwctl-darwin-aarch64-sbom/kwctl-darwin-aarch64-sbom.spdx.sig',
101 | './kwctl-darwin-x86_64/kwctl-darwin-x86_64.zip',
102 | './kwctl-darwin-x86_64-sbom/kwctl-darwin-x86_64-sbom.spdx',
103 | './kwctl-darwin-x86_64-sbom/kwctl-darwin-x86_64-sbom.spdx.cert',
104 | './kwctl-darwin-x86_64-sbom/kwctl-darwin-x86_64-sbom.spdx.sig',
105 | './kwctl-linux-aarch64/kwctl-linux-aarch64.zip',
106 | './kwctl-linux-aarch64-sbom/kwctl-linux-aarch64-sbom.spdx',
107 | './kwctl-linux-aarch64-sbom/kwctl-linux-aarch64-sbom.spdx.cert',
108 | './kwctl-linux-aarch64-sbom/kwctl-linux-aarch64-sbom.spdx.sig',
109 | './kwctl-linux-x86_64/kwctl-linux-x86_64.zip',
110 | './kwctl-linux-x86_64-sbom/kwctl-linux-x86_64-sbom.spdx',
111 | './kwctl-linux-x86_64-sbom/kwctl-linux-x86_64-sbom.spdx.cert',
112 | './kwctl-linux-x86_64-sbom/kwctl-linux-x86_64-sbom.spdx.sig',
113 | './kwctl-windows-x86_64/kwctl-windows-x86_64.exe.zip',
114 | './kwctl-windows-x86_64-sbom/kwctl-windows-x86_64-sbom.spdx',
115 | './kwctl-windows-x86_64-sbom/kwctl-windows-x86_64-sbom.spdx.cert',
116 | './kwctl-windows-x86_64-sbom/kwctl-windows-x86_64-sbom.spdx.sig',
117 | ]
118 | const {RELEASE_ID} = process.env
119 |
120 | for (const file of files) {
121 | let file_data = fs.readFileSync(file);
122 |
123 | let response = await github.rest.repos.uploadReleaseAsset({
124 | owner: context.repo.owner,
125 | repo: context.repo.repo,
126 | release_id: `${RELEASE_ID}`,
127 | name: path.basename(file),
128 | data: file_data,
129 | });
130 | }
131 |
132 | - name: Publish release
133 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
134 | with:
135 | script: |
136 | const {RELEASE_ID} = process.env
137 | const {TAG_NAME} = process.env
138 | isPreRelease = ${{ contains(github.ref_name, '-alpha') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-rc') }}
139 | github.rest.repos.updateRelease({
140 | owner: context.repo.owner,
141 | repo: context.repo.repo,
142 | release_id: `${RELEASE_ID}`,
143 | draft: false,
144 | tag_name: `${TAG_NAME}`,
145 | name: `${TAG_NAME}`,
146 | prerelease: isPreRelease,
147 | make_latest: !isPreRelease
148 | });
149 |
150 | - name: Trigger chart update
151 | env:
152 | GH_TOKEN: ${{ secrets.WORKFLOW_PAT }}
153 | run: |
154 | echo '{
155 | "event_type": "update-chart",
156 | "client_payload": {
157 | "version": "${{ github.ref_name }}",
158 | "oldVersion": "${{ steps.get_last_release_tag.outputs.old_release_tag }}",
159 | "repository": "${{ github.repository }}"
160 | }
161 | }' > payload.json
162 | gh api repos/${{ github.repository_owner }}/helm-charts/dispatches \
163 | -X POST \
164 | --input payload.json
165 |
--------------------------------------------------------------------------------
/.github/workflows/security-audit-cron.yml:
--------------------------------------------------------------------------------
1 | name: Security audit cron job
2 | on:
3 | schedule:
4 | - cron: "0 0 * * *"
5 |
6 | # Declare default permissions as read only.
7 | permissions: read-all
8 |
9 | jobs:
10 | audit:
11 | permissions:
12 | checks: write # for rustsec/audit-check to create check
13 | contents: read # for actions/checkout to fetch code
14 | issues: write # for rustsec/audit-check to create issues
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
18 | - uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0
19 | with:
20 | token: ${{ secrets.GITHUB_TOKEN }}
21 |
--------------------------------------------------------------------------------
/.github/workflows/security-audit-reactive.yml:
--------------------------------------------------------------------------------
1 | name: Security audit
2 | on:
3 | push:
4 | paths:
5 | - "**/Cargo.toml"
6 | - "**/Cargo.lock"
7 |
8 | # Declare default permissions as read only.
9 | permissions: read-all
10 |
11 | jobs:
12 | security_audit:
13 | runs-on: ubuntu-latest
14 | permissions:
15 | checks: write # for rustsec/audit-check to create check
16 | contents: read # for actions/checkout to fetch code
17 | issues: write # for rustsec/audit-check to create issues
18 | steps:
19 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
20 | - uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0
21 | with:
22 | token: ${{ secrets.GITHUB_TOKEN }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/update-rust-toolchain.yaml:
--------------------------------------------------------------------------------
1 | name: Update rust-toolchain
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: "30 3 * * 1" # 3:30 on Monday
7 |
8 | jobs:
9 | update-rust-toolchain:
10 | name: Update Rust toolchain
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
15 |
16 | - name: Install Updatecli in the runner
17 | uses: updatecli/updatecli-action@307ce72e224b82157cc31c78828f168b8e55d47d # v2.84.0
18 |
19 | - name: Update rust version inside of rust-toolchain file
20 | id: update_rust_toolchain
21 | env:
22 | UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23 | UPDATECLI_GITHUB_OWNER: ${{ github.repository_owner }}
24 | run: |-
25 | updatecli apply --config ./updatecli/updatecli.d/update-rust-toolchain.yaml \
26 | --values updatecli/values.yaml
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /bin
3 | bom-cargo.json
4 |
5 | # coverage instrumentation:
6 | *.profraw
7 |
--------------------------------------------------------------------------------
/.taplo.toml:
--------------------------------------------------------------------------------
1 | [formatting]
2 | align_entries = true
3 | reorder_arrays = true
4 | reorder_keys = true
5 | sort_keys = true
6 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @kubewarden/kubewarden-developers
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Making a new release
4 |
5 | 1. Bump to `version = "X.Y.Z"` on `cargo.toml`.
6 | 2. Format if needed, commit and open PR (as `main` branch is protected).
7 | 3. Wait for PR to be merged.
8 | 4. Once the PR is in `main`, create an annotated signed tag on the merge commit
9 | of the PR in `main`:
10 | `git tag -s -a -m "vX.Y.Z" vX.Y.Z`. This will trigger the GH Action for
11 | release. Wait for it to complete and check that it is created.
12 | 5. If needed, edit the GH release description.
13 |
14 | ## GitHub Actions
15 |
16 | For some workflows, GITHUB_TOKEN needs read and write permissions (e.g: to
17 | perform cosign signatures); if you have forked the repository, you may need to
18 | change "settings -> actions -> general -> workflow permissions" to "Read and
19 | write permissions".
20 |
21 | Also, given how the release and release-drafter workflows work, they need git
22 | tags present; push the tags from origin to your fork.
23 |
24 | ## Code conventions
25 |
26 |
27 | Check out our global [CONTRIBUTING guidelines](https://github.com/kubewarden/.github/blob/main/CONTRIBUTING.md) for Rust code conventions
28 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | authors = ["Kubewarden Developers "]
3 | description = "Tool to manage Kubewarden policies"
4 | edition = "2021"
5 | name = "kwctl"
6 | version = "1.25.0"
7 |
8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9 |
10 | [dependencies]
11 | anyhow = "1.0"
12 | clap = { version = "4.5", features = ["cargo", "env"] }
13 | clap-markdown = "0.1.4"
14 | clap_complete = "4.5"
15 | directories = "6.0.0"
16 | flate2 = "1.1"
17 | humansize = "2.1"
18 | indicatif = "0.17"
19 | is-terminal = "0.4.16"
20 | itertools = "0.14.0"
21 | k8s-openapi = { version = "0.25.0", default-features = false, features = [
22 | "v1_30",
23 | ] }
24 | lazy_static = "1.4.0"
25 | pem = "3"
26 | policy-evaluator = { git = "https://github.com/kubewarden/policy-evaluator", tag = "v0.25.2" }
27 | prettytable-rs = "^0.10"
28 | regex = "1"
29 | rustls-pki-types = { version = "1", features = ["alloc"] }
30 | semver = { version = "1.0.22", features = ["serde"] }
31 | serde = { version = "1.0", features = ["derive"] }
32 | serde_json = "1.0"
33 | serde_yaml = "0.9.34"
34 | tar = "0.4.40"
35 | termimad = "0.33.0"
36 | thiserror = "2.0"
37 | time = "0.3.36"
38 | tiny-bench = "0.4"
39 | tokio = { version = "^1.42.0", features = ["full"] }
40 | tracing = "0.1"
41 | tracing-subscriber = { version = "0.3", features = ["fmt"] }
42 | url = "2.5.0"
43 | walrus = "0.23.0"
44 | wasmparser = "0.232"
45 |
46 | hostname-validator = "1.1.1"
47 | # This is required to have reqwest built using the `rustls-tls-native-roots`
48 | # feature across all the transitive dependencies of kwctl
49 | # This is required to have kwctl use the system certificates instead of the
50 | # ones bundled inside of rustls
51 | reqwest = { version = "0", default-features = false, features = [
52 | "rustls-tls-native-roots",
53 | ] }
54 |
55 | [dev-dependencies]
56 | assert_cmd = "2.0.14"
57 | hyper = { version = "1.5.0" }
58 | predicates = "3.1"
59 | rstest = "0.25"
60 | tempfile = "3.17"
61 | testcontainers = { version = "0.24", features = ["blocking"] }
62 | tower-test = "0.4"
63 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: build
2 | build: build-release build-docs
3 |
4 | .PHONY: build-release
5 | build-release:
6 | cargo build --release
7 |
8 | .PHONY:build-docs
9 | build-docs:
10 | cargo run --release -- docs --output cli-docs.md
11 |
12 | .PHONY: fmt
13 | fmt:
14 | cargo fmt --all -- --check
15 |
16 | .PHONY: lint
17 | lint:
18 | cargo clippy -- -D warnings
19 |
20 | .PHONY: typos
21 | typos:
22 | typos # run typo checker from crate-ci/typos
23 |
24 | .PHONY: test
25 | test: fmt lint
26 | cargo test --workspace --bins
27 |
28 | .PHONY: e2e-tests
29 | e2e-tests:
30 | cargo test --test '*'
31 |
32 | .PHONY: coverage
33 | coverage:
34 | cargo llvm-cov --html
35 |
36 | .PHONY: clean
37 | clean:
38 | cargo clean
39 |
40 | .PHONY: tag
41 | tag:
42 | @git tag "${TAG}" || (echo "Tag ${TAG} already exists. If you want to retag, delete it manually and re-run this command" && exit 1)
43 | @git-chglog --output CHANGELOG.md
44 | @git commit -m 'Update CHANGELOG.md' -- CHANGELOG.md
45 | @git tag -f "${TAG}"
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/kubewarden/community/blob/main/REPOSITORIES.md#core-scope)
2 | [](https://github.com/kubewarden/community/blob/main/REPOSITORIES.md#stable)
3 | [](https://www.bestpractices.dev/projects/9180)
4 | [](https://app.fossa.com/projects/cjustom%2B25850%2Fgithub.com%2Fkubewarden%2Fkwctl?ref=badge_shield)
5 | [](https://scorecard.dev/viewer/?uri=github.com/kubewarden/kwctl)
6 |
7 | # `kwctl`
8 |
9 | `kwctl` is the go-to CLI tool for [Kubewarden](https://kubewarden.io)
10 | users.
11 |
12 | Think of it as the `docker` CLI tool if you were working with
13 | containers.
14 |
15 | ## How does `kwctl` help me?
16 |
17 | ### As a policy author
18 |
19 | - e2e testing of your policy. Test your policy against crafted
20 | Kubernetes requests, and ensure your policy behaves as you
21 | expect. You can even test context-aware policies, that require
22 | access to a running cluster.
23 |
24 | - Embed metadata in your Wasm module, so the binary is annotated with
25 | the permissions it needs to execute.
26 |
27 | - Publish policies to OCI registries.
28 |
29 | - Generate initial `ClusterAdmissionPolicy` scaffolding for your
30 | policy.
31 |
32 | ### As a cluster administrator
33 |
34 | - Inspect remote policies. Given a policy in an OCI registry, or in an
35 | HTTP server, show all static information about the policy.
36 |
37 | - Dry-run of a policy in your cluster. Test the policy against crafted
38 | Kubernetes requests, and ensure the policy behaves as you expect
39 | given the input data you provide. You can even test context-aware
40 | policies, that require access to a running cluster, also in a
41 | dry-run mode.
42 |
43 | - Generate `ClusterAdmissionPolicy` scaffolding for a given policy.
44 |
45 | ### Everyone
46 |
47 | - The UX of this tool is intended to be as easy and intuitive as
48 | possible.
49 |
50 | ## Install
51 |
52 | Built binaries for `Linux x86_64`, `Windows x86_64`, `MacOS x86_64` and `MacOS
53 | aarch64 (M1)` are available in [GH Releases](https://github.com/kubewarden/kwctl/releases).
54 |
55 | There is also:
56 |
57 | - Community-created [Homebrew 🍺 formula for kwctl](https://formulae.brew.sh/formula/kwctl)
58 | - Community-created [AUR 🐧 package](https://aur.archlinux.org/packages/kwctl-bin)
59 |
60 | ## Usage
61 |
62 | These are the commands currently supported by kwctl.
63 |
64 | If you want a complete list of the available commands, you can read the
65 | [cli-docs.md](./cli-docs.md) file.
66 |
67 | ### List policies
68 |
69 | The list of policies downloaded on the local machine can be
70 | obtained by doing:
71 |
72 | ```console
73 | kwctl policies
74 | ```
75 |
76 | ### Download policies
77 |
78 | Policies can be downloaded using the `pull` command.
79 |
80 | The name of the policy must be expressed as a url with one of the
81 | following protocols:
82 |
83 | - `http://`: pull from a HTTP server
84 | - `https://`: pull from a HTTPS server
85 | - `registry://`: pull from an OCI registry
86 |
87 | Pulling from a registry, by tag:
88 |
89 | ```console
90 | kwctl pull registry://ghcr.io/kubewarden/policies/psp-capabilities:latest
91 | ```
92 |
93 | It's possible to pull from a registry using an immutable reference (in the
94 | same way as with regular container images):
95 |
96 | ```console
97 | kwctl pull registry://ghcr.io/kubewarden/policies/psp-capabilities@sha256:61ef63621fa5be8e422881d96d05edfef810992fbf9468e35d1fa5ae815bd97c
98 | ```
99 |
100 | Note well, the shasum is the digest of the OCI artifact containing the policy.
101 | This value can be obtained using a tool like [crane](https://github.com/google/go-containerregistry/blob/main/cmd/crane/README.md):
102 |
103 | ```console
104 | crane digest ghcr.io/kubewarden/policies/psp-capabilities:v0.1.6
105 | ```
106 |
107 | ### Run a policy locally
108 |
109 | `kwctl` can be used to run a policy locally, outside of Kubernetes. This can be used
110 | to quickly evaluate a policy and find the right settings for it.
111 |
112 | The evaluation is done against a pre-recorded [`AdmissionReview`](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#request).
113 |
114 | Running a policy locally:
115 |
116 | ```console
117 | kwctl run \
118 | --settings-json '{"constrained_labels": {"owner": ".*"}}' \
119 | -r test_data/ingress.json \
120 | registry://ghcr.io/kubewarden/policies/safe-labels:v0.1.5
121 | ```
122 |
123 | Policy configuration can be passed on the CLI via the `--settings-json` flag
124 | or can be loaded from the disk via the `--settings-path` flag.
125 |
126 | #### Scaffold AdmissionReview from a Kubernetes resource
127 |
128 | It's possible to scaffold an `AdmissionReview` object from a Kubernetes resource:
129 |
130 | ```console
131 | kwctl scaffold \
132 | admission-request \
133 | --operation CREATE \
134 | --object ingress.yaml
135 | ```
136 |
137 | The output of the above command can be used by the `run` command.
138 |
139 | ### Annotate a policy
140 |
141 | Kubewarden policies are WebAssembly module, which must contain some
142 | Kubewarden-specific metadata.
143 |
144 | The act of adding metadata to the policy is done by the policy author, right
145 | before policy distribution.
146 |
147 | The `kwctl annotate` command can be used to perform this operation.
148 |
149 | ### Inspect a policy
150 |
151 | The metadata attached to a policy, plus other details can be seen via the
152 | `kwctl inspect` command.
153 |
154 | This command works against a policy that has been previously downloaded.
155 |
156 | ### Publish a policy
157 |
158 | `kwctl` can be used to publish a local policy into an OCI registry. This is done
159 | via the `push` sub-command.
160 |
161 | The `push` sub-command can also be used to copy a policy into another registry:
162 |
163 | ```console
164 | kwctl push registry://ghcr.io/kubewarden/policies/safe-labels:v0.1.5 \
165 | registry://registry.local.lan/kubewarden/safe-labels:v0.1.5
166 | ```
167 |
168 | The above command copies a local policy that was downloaded from the GitHub
169 | Container Registry, into a local registry.
170 |
171 | > **Note well:** the policy must be previously downloaded locally via `kwctl pull`
172 |
173 | ### Remove a local policy
174 |
175 | Local policies can be removed via the `rm` sub-command:
176 |
177 | ```console
178 | kwctl rm
179 | ```
180 |
181 | ### Scaffold Kubernetes Custom Resources
182 |
183 | Kubewarden policies are enforced on Kubernetes clusters by using
184 | special Custom Resources provided by our [Kubernetes integration](https://docs.kubewarden.io/quick-start.html#kubewarden-policies).
185 |
186 | The `manifest` sub-command can be used to quickly scaffold the definition of
187 | Kubewarden Custom Resources.
188 |
189 | The manifest command shares some of the arguments of the `run` command, it's
190 | typical to test a policy locally via the `kwctl run` command and then, once
191 | satisfied about the policy settings, create a deployment manifest for it via
192 | the `manifest` command.
193 |
194 | Step #1, find the right policy settings:
195 |
196 | ```console
197 | kwctl run \
198 | --settings-json '{"constrained_labels": {"owner": ".*"}}' \
199 | -r test_data/ingress.json \
200 | registry://ghcr.io/kubewarden/policies/safe-labels:v0.1.5
201 | ```
202 |
203 | Step #2, generate a manifest to enforce the policy inside of a
204 | Kubernetes cluster:
205 |
206 | ```console
207 | kwctl manifest\
208 | --settings-json '{"constrained_labels": {"owner": ".*"}}' \
209 | -t ClusterAdmissionPolicy \
210 | registry://ghcr.io/kubewarden/policies/safe-labels:v0.1.5
211 | ```
212 |
213 | This will produce the following output:
214 |
215 | ```yaml
216 | ---
217 | apiVersion: policies.kubewarden.io/v1
218 | kind: ClusterAdmissionPolicy
219 | metadata:
220 | name: generated-policy
221 | spec:
222 | module: "registry://ghcr.io/kubewarden/policies/safe-labels:v0.1.5"
223 | settings:
224 | constrained_labels:
225 | owner: ".*"
226 | rules:
227 | - apiGroups:
228 | - "*"
229 | apiVersions:
230 | - "*"
231 | resources:
232 | - "*"
233 | operations:
234 | - CREATE
235 | - UPDATE
236 | mutating: false
237 | ```
238 |
239 | Which can then be customized by hand, and then applied into a Kubernetes cluster.
240 |
241 | ### Shell completion
242 |
243 | `kwctl` can generate autocompletion scripts for the following shells:
244 |
245 | - bash
246 | - elvish
247 | - fish
248 | - powershell
249 | - zsh
250 |
251 | The completion script can be generated with the following command:
252 |
253 | ```console
254 | $ kwctl completions -s
255 | ```
256 |
257 | The command will print to the stdout the completion script.
258 |
259 | #### Bash
260 |
261 | To load completions in your current shell session:
262 |
263 | ```console
264 | $ source <(kwctl completions -s bash)
265 | ```
266 |
267 | To load completions for every new session, execute once:
268 |
269 | - Linux: `$ kwctl completions -s bash > /etc/bash_completion.d/kwctl`
270 | - MacOS: `$ kwctl completions -s bash > /usr/local/etc/bash_completion.d/kwctl`
271 |
272 | You will need to start a new shell for this setup to take effect.
273 |
274 | #### Fish
275 |
276 | To load completions in your current shell session:
277 |
278 | ```console
279 | $ kwctl completions -s fish | source
280 | ```
281 |
282 | To load completions for every new session, execute once:
283 |
284 | ```console
285 | $ kwctl completions -s fish > ~/.config/fish/completions/kwctl.fish
286 | ```
287 |
288 | You will need to start a new shell for this setup to take effect.
289 |
290 | #### Zsh
291 |
292 | To load completions in your current shell session:
293 |
294 | ```console
295 | $ source <(kwctl completions -s zsh)
296 | ```
297 |
298 | To load completions for every new session, execute once:
299 |
300 | ```console
301 | $ kwctl completions -s zsh > "${fpath[1]}/_kwctl"
302 | ```
303 |
304 | ##### Oh My Zsh users
305 |
306 | These steps are required by [oh-my-zsh](https://ohmyz.sh/) users:
307 |
308 | ```console
309 | $ print -l $fpath | grep '.oh-my-zsh/completions'
310 | $ mkdir ~/.oh-my-zsh/completions
311 | $ kwctl completions -s zsh > ~/.oh-my-zsh/completions/_kwctl
312 | rm ~/.zcompdump*
313 | ```
314 |
315 | Then start a new shell or run `source ~/.zshrc` once.
316 |
317 | ## Verify kwctl binaries
318 |
319 | kwctl binaries are signed using [Sigstore's blog signing](https://docs.sigstore.dev/signing/signing_with_blobs/).
320 | When you download a [kwctl release](https://github.com/kubewarden/kwctl/releases/) each zip file contains two
321 | files that can be used for verification: `kwctl.sig` and `kwctl.pem`.
322 |
323 | In order to verify kwctl you need cosign installed, and then execute the following command:
324 |
325 | ```
326 | cosign verify-blob \
327 | --signature kwctl-linux-x86_64.sig \
328 | --cert kwctl-linux-x86_64.pem kwctl-linux-x86_64 \
329 | --certificate-identity-regexp 'https://github.com/kubewarden/*' \
330 | --certificate-oidc-issuer https://token.actions.githubusercontent.com
331 | ```
332 |
333 | The output should be:
334 |
335 | ```
336 | Verified OK
337 | ```
338 |
339 | # Software bill of materials & provenance
340 |
341 | Kwctl has its software bill of materials (SBOM) published every release. They
342 | follow the [SPDX](https://spdx.dev/) format, you can find them together with
343 | the signature and certificate used to sign it in the [releases
344 | assets](https://github.com/kubewarden/kwctl/releases).
345 |
346 | The build [Provenance](https://slsa.dev/spec/v1.0/provenance) files are
347 | following the [SLSA](https://slsa.dev/provenance/v0.2#schema) provenance schema
348 | and are accessible at the GitHub Actions'
349 | [provenance](https://github.com/kubewarden/kwctl/attestations) tab. For
350 | information on their format and how to verify them, see the [GitHub
351 | documentation](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/verifying-attestations-offline).
352 |
353 | ## Security disclosure
354 |
355 | See [SECURITY.md](https://github.com/kubewarden/community/blob/main/SECURITY.md) on the kubewarden/community repo.
356 |
357 | ## Changelog
358 |
359 | See [GitHub Releases content](https://github.com/kubewarden/kwctl/releases).
360 |
--------------------------------------------------------------------------------
/SECURITY-INSIGHTS.yml:
--------------------------------------------------------------------------------
1 | header:
2 | schema-version: 1.0.0
3 | last-updated: "2024-08-12"
4 | last-reviewed: "2023-08-12"
5 | expiration-date: "2025-10-01T01:00:00.000Z"
6 | project-url: https://github.com/kubewarden/kwctl/
7 | changelog: https://github.com/kubewarden/kwctl/releases/latest
8 | license: https://github.com/kubewarden/kwctl/blob/main/LICENSE
9 | project-lifecycle:
10 | bug-fixes-only: false
11 | core-maintainers:
12 | - https://github.com/kubewarden/community?tab=readme-ov-file#maintainers
13 | roadmap: https://github.com/kubewarden/community?tab=readme-ov-file#roadmap
14 | status: active
15 | contribution-policy:
16 | accepts-pull-requests: true
17 | accepts-automated-pull-requests: true
18 | contributing-policy: https://github.com/kubewarden/kwctl/blob/main/CONTRIBUTING.md
19 | code-of-conduct: https://github.com/kubewarden/community/blob/main/CODE_OF_CONDUCT.md
20 | documentation:
21 | - https://docs.kubewarden.io
22 | distribution-points:
23 | - https://github.com/kubewarden/kwctl/
24 | security-artifacts:
25 | threat-model:
26 | threat-model-created: true
27 | evidence-url:
28 | - https://docs.kubewarden.io/reference/threat-model
29 | security-testing:
30 | - tool-type: sca
31 | tool-name: Dependabot
32 | tool-version: latest
33 | integration:
34 | ad-hoc: false
35 | ci: true
36 | before-release: true
37 | comment: |
38 | Dependabot is enabled for this repo.
39 | security-contacts:
40 | - type: website
41 | value: https://docs.kubewarden.io/disclosure
42 | vulnerability-reporting:
43 | accepts-vulnerability-reports: true
44 | security-policy: https://github.com/kubewarden/community/blob/main/SECURITY.md
45 | email-contact: cncf-kubewarden-maintainers@lists.cncf.io
46 | comment: |
47 | The first and best way to report a vulnerability is by using private security issues in GitHub or opening an issue on Github. We are also available on the Kubernetes Slack in the #kubewaden-dev channel.
48 | dependencies:
49 | third-party-packages: true
50 | dependencies-lists:
51 | - https://github.com/kubewarden/kwctl/blob/main/Cargo.lock
52 | sbom:
53 | - sbom-file: https://github.com/kubewarden/kwctl/releases/latest/download/kwctl-linux-x86_64-sbom.spdx
54 | sbom-format: SPDX
55 | sbom-url: https://github.com/anchore/sbom-action
56 | dependencies-lifecycle:
57 | policy-url: https://github.com/kubewarden/community/blob/main/SECURITY.md#security-patch-policy
58 | env-dependencies-policy:
59 | policy-url: https://github.com/kubewarden/community/blob/main/SECURITY.md#dependency-policy
60 |
--------------------------------------------------------------------------------
/config.toml:
--------------------------------------------------------------------------------
1 | # Due to an issue with linking when cross-compiling, specify the
2 | # linker and archiver for cross-compiled targets.
3 | #
4 | # More information: https://github.com/rust-lang/cargo/issues/4133
5 |
6 | [target.x86_64-unknown-linux-musl]
7 | linker = "x86_64-linux-musl-gcc"
8 |
9 | [target.aarch64-unknown-linux-musl]
10 | linker = "aarch64-linux-musl-gcc"
11 |
--------------------------------------------------------------------------------
/coverage/integration-tests/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/coverage/unit-tests/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "github>kubewarden/github-actions//renovate-config/default"
4 | ],
5 | "packageRules": [
6 | {
7 | "description": "Update GitHub Actions",
8 | "matchManagers": ["github-actions"],
9 | "groupName": "github-actions",
10 | "groupSlug": "github-actions"
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 |
2 | [toolchain]
3 | channel = "1.87.0"
4 | components = ["clippy", "rust-analyzer", "rustfmt"]
5 | profile = "minimal"
6 | targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-musl", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", "x86_64-unknown-linux-musl"]
7 |
--------------------------------------------------------------------------------
/scripts/kubewarden-load-policies.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | kwctl="${KWCTL_CMD:-kwctl}"
5 | policies="kubewarden-policies.tar.gz"
6 | list="kubewarden-policies.txt"
7 |
8 | usage () {
9 | echo "USAGE: $0 [--policies kubewarden-policies.tar.gz] --registry my.registry.com:5000"
10 | echo " [-l|--policies-list path] text file with list of policies; one image per line."
11 | echo " [-p|--policies path] tar.gz generated by kwctl save."
12 | echo " [-r|--registry registry:port] target private registry:port."
13 | echo " [-s|--sources-path path] kwctl sources path."
14 | echo " [-h|--help] Usage message"
15 | }
16 |
17 | pushPolicy() {
18 | newPolicyUrl=$1
19 | if [[ -n $sourcesPath ]]; then
20 | $kwctl push "$policy" "$newPolicyUrl" --sources-path "$sourcesPath"
21 | else
22 | $kwctl push "$policy" "$newPolicyUrl"
23 | fi
24 | }
25 |
26 | while [[ $# -gt 0 ]]; do
27 | key="$1"
28 | shift
29 | case $key in
30 | -r|--registry)
31 | registry="$1"
32 | shift # past value
33 | ;;
34 | -l|--policies-list)
35 | list="$1"
36 | shift # past value
37 | ;;
38 | -p|--policies)
39 | policies="$1"
40 | shift # past value
41 | ;;
42 | -s|--sources-path)
43 | sourcesPath="$1"
44 | shift # past value
45 | ;;
46 | -h|--help)
47 | help="true"
48 | ;;
49 | *)
50 | usage
51 | exit 1
52 | ;;
53 | esac
54 | done
55 | if [[ -v help ]]; then
56 | usage
57 | exit 0
58 | fi
59 | if [[ -z ${registry:-} ]]; then
60 | usage
61 | exit 1
62 | fi
63 |
64 | $kwctl load --input "${policies}"
65 |
66 | policies=()
67 | while read -r policy; do
68 | policies+=("${policy}");
69 | done < "${list}"
70 |
71 | for policy in "${policies[@]}"; do
72 | if [[ $policy == registry://* ]]; then
73 | # replace registry with the one provided as parameter.
74 | # e.g. registry://ghcr.io/kubewarden/policies/capabilities-psp:v0.1.9 -> registry://localhost:5000/kubewarden/policies/capabilities-psp:v0.1.9
75 | oldPolicyUrl=$(awk -Fregistry:// '{print $2}' <<< "$policy")
76 | oldRegistry=$(echo "$oldPolicyUrl" | cut -f1 -d"/")
77 | newPolicyUrl="registry://${oldPolicyUrl/$oldRegistry/$registry}"
78 | pushPolicy "$newPolicyUrl"
79 | fi
80 | if [[ $policy == https://* ]]; then
81 | # replace registry with the one provided as parameter.
82 | # e.g. https://github.com/kubewarden/pod-privileged-policy/releases/download/v0.1.6/policy.wasm -> registry://localhost:5000/kubewarden/pod-privileged-policy/releases/download/v0.1.6/policy.wasm
83 | oldPolicyUrl=$(awk -Fhttps:// '{print $2}' <<< "$policy")
84 | newPolicyUrl="registry://$registry/${oldPolicyUrl#*/}"
85 | pushPolicy "$newPolicyUrl"
86 | fi
87 | done
88 |
--------------------------------------------------------------------------------
/scripts/kubewarden-save-policies.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | kwctl="${KWCTL_CMD:-kwctl}"
5 | policies="kubewarden-policies.tar.gz"
6 | list="kubewarden-policies.txt"
7 |
8 | usage () {
9 | echo "USAGE: $0 [--policies-list kubewarden-policies.txt] [--policies kubewarden-policies.tar.gz]"
10 | echo " [-l|--policies-list path] text file with list of policies; one police per line."
11 | echo " [-p|--policies path] tar.gz generated by kwctl save."
12 | echo " [-h|--help] Usage message"
13 | }
14 |
15 | while [[ $# -gt 0 ]]; do
16 | key="$1"
17 | shift
18 | case $key in
19 | -p|--policies)
20 | policies="$1"
21 | shift # past value
22 | ;;
23 | -l|--policies-list)
24 | list="$1"
25 | shift # past value
26 | ;;
27 | -h|--help)
28 | help="true"
29 | ;;
30 | *)
31 | usage
32 | exit 1
33 | ;;
34 | esac
35 | done
36 |
37 | if [[ -v help ]]; then
38 | usage
39 | exit 0
40 | fi
41 |
42 | pulled=()
43 | while IFS= read -r i; do
44 | [ -z "${i}" ] && continue
45 | if $kwctl pull "${i}" > /dev/null 2>&1; then
46 | echo "Policy pull success: ${i}"
47 | pulled+=("${i}")
48 | else
49 | echo "Policy pull failed: ${i}"
50 | fi
51 | done < "${list}"
52 |
53 | echo "Creating ${policies} with ${#pulled[@]} policies"
54 | $kwctl save "${pulled[@]}" --output "${policies}"
55 |
56 |
--------------------------------------------------------------------------------
/src/annotate.rs:
--------------------------------------------------------------------------------
1 | use crate::backend::{Backend, BackendDetector};
2 | use anyhow::{anyhow, Result};
3 | use policy_evaluator::validator::Validate;
4 | use policy_evaluator::{constants::*, policy_metadata::Metadata, ProtocolVersion};
5 | use std::fs::{self, File};
6 | use std::path::PathBuf;
7 |
8 | pub(crate) fn write_annotation(
9 | wasm_path: PathBuf,
10 | metadata_path: PathBuf,
11 | destination: PathBuf,
12 | usage_path: Option,
13 | ) -> Result<()> {
14 | let usage = usage_path
15 | .map(|path| {
16 | fs::read_to_string(path).map_err(|e| anyhow!("Error reading usage file: {}", e))
17 | })
18 | .transpose()?;
19 | let backend_detector = BackendDetector::default();
20 | let metadata = prepare_metadata(
21 | wasm_path.clone(),
22 | metadata_path,
23 | backend_detector,
24 | usage.as_deref(),
25 | )?;
26 | write_annotated_wasm_file(wasm_path, destination, metadata)
27 | }
28 |
29 | fn prepare_metadata(
30 | wasm_path: PathBuf,
31 | metadata_path: PathBuf,
32 | backend_detector: BackendDetector,
33 | usage: Option<&str>,
34 | ) -> Result {
35 | let metadata_file =
36 | File::open(metadata_path).map_err(|e| anyhow!("Error opening metadata file: {}", e))?;
37 | let mut metadata: Metadata = serde_yaml::from_reader(&metadata_file)
38 | .map_err(|e| anyhow!("Error unmarshalling metadata {}", e))?;
39 |
40 | let backend = backend_detector.detect(wasm_path, &metadata)?;
41 |
42 | match backend {
43 | Backend::Opa | Backend::OpaGatekeeper | Backend::Wasi => {
44 | metadata.protocol_version = Some(ProtocolVersion::Unknown)
45 | }
46 | Backend::KubewardenWapc(protocol_version) => {
47 | metadata.protocol_version = Some(protocol_version)
48 | }
49 | };
50 |
51 | let mut annotations = metadata.annotations.unwrap_or_default();
52 | annotations.insert(
53 | String::from(KUBEWARDEN_ANNOTATION_KWCTL_VERSION),
54 | String::from(env!("CARGO_PKG_VERSION")),
55 | );
56 | if let Some(s) = usage {
57 | annotations.insert(
58 | String::from(KUBEWARDEN_ANNOTATION_POLICY_USAGE),
59 | String::from(s),
60 | );
61 | }
62 | metadata.annotations = Some(annotations);
63 |
64 | metadata
65 | .validate()
66 | .map_err(|e| anyhow!("Metadata is invalid: {:?}", e))
67 | .and(Ok(metadata))
68 | }
69 |
70 | fn write_annotated_wasm_file(
71 | input_path: PathBuf,
72 | output_path: PathBuf,
73 | metadata: Metadata,
74 | ) -> Result<()> {
75 | let buf: Vec = std::fs::read(input_path)?;
76 | let metadata_json = serde_json::to_vec(&metadata)?;
77 |
78 | let mut module = walrus::Module::from_buffer(buf.as_slice())?;
79 |
80 | let custom_section = walrus::RawCustomSection {
81 | name: String::from(KUBEWARDEN_CUSTOM_SECTION_METADATA),
82 | data: metadata_json,
83 | };
84 | module.customs.add(custom_section);
85 |
86 | module.emit_wasm_file(output_path)?;
87 | Ok(())
88 | }
89 |
90 | #[cfg(test)]
91 | mod tests {
92 | use super::*;
93 | use std::io::Write;
94 | use tempfile::tempdir;
95 |
96 | fn mock_protocol_version_detector_v1(_wasm_path: PathBuf) -> Result {
97 | Ok(ProtocolVersion::V1)
98 | }
99 |
100 | fn mock_rego_policy_detector_true(_wasm_path: PathBuf) -> Result {
101 | Ok(true)
102 | }
103 |
104 | fn mock_rego_policy_detector_false(_wasm_path: PathBuf) -> Result {
105 | Ok(false)
106 | }
107 |
108 | #[test]
109 | fn test_kwctl_version_is_added_to_already_populated_annotations() -> Result<()> {
110 | let dir = tempdir()?;
111 |
112 | let file_path = dir.path().join("metadata.yml");
113 | let mut file = File::create(file_path.clone())?;
114 |
115 | let expected_policy_title = "psp-test";
116 | let raw_metadata = format!(
117 | r#"
118 | rules:
119 | - apiGroups: [""]
120 | apiVersions: ["v1"]
121 | resources: ["pods"]
122 | operations: ["CREATE", "UPDATE"]
123 | mutating: false
124 | backgroundAudit: true
125 | annotations:
126 | io.kubewarden.policy.title: {}
127 | "#,
128 | expected_policy_title
129 | );
130 |
131 | write!(file, "{}", raw_metadata)?;
132 |
133 | let backend_detector = BackendDetector::new(
134 | mock_rego_policy_detector_false,
135 | mock_protocol_version_detector_v1,
136 | );
137 | let metadata = prepare_metadata(
138 | PathBuf::from("irrelevant.wasm"),
139 | file_path,
140 | backend_detector,
141 | None,
142 | )?;
143 | let annotations = metadata.annotations.unwrap();
144 |
145 | assert_eq!(
146 | annotations.get(KUBEWARDEN_ANNOTATION_POLICY_TITLE),
147 | Some(&String::from(expected_policy_title))
148 | );
149 |
150 | assert_eq!(
151 | annotations.get(KUBEWARDEN_ANNOTATION_KWCTL_VERSION),
152 | Some(&String::from(env!("CARGO_PKG_VERSION"))),
153 | );
154 |
155 | Ok(())
156 | }
157 |
158 | #[test]
159 | fn test_kwctl_version_is_overwrote_when_user_accidentally_provides_it() -> Result<()> {
160 | let dir = tempdir()?;
161 |
162 | let file_path = dir.path().join("metadata.yml");
163 | let mut file = File::create(file_path.clone())?;
164 |
165 | let expected_policy_title = "psp-test";
166 | let raw_metadata = format!(
167 | r#"
168 | rules:
169 | - apiGroups: [""]
170 | apiVersions: ["v1"]
171 | resources: ["pods"]
172 | operations: ["CREATE", "UPDATE"]
173 | mutating: false
174 | backgroundAudit: true
175 | annotations:
176 | io.kubewarden.policy.title: {}
177 | {}: NOT_VALID
178 | "#,
179 | expected_policy_title, KUBEWARDEN_ANNOTATION_KWCTL_VERSION,
180 | );
181 |
182 | write!(file, "{}", raw_metadata)?;
183 |
184 | let backend_detector = BackendDetector::new(
185 | mock_rego_policy_detector_false,
186 | mock_protocol_version_detector_v1,
187 | );
188 | let metadata = prepare_metadata(
189 | PathBuf::from("irrelevant.wasm"),
190 | file_path,
191 | backend_detector,
192 | None,
193 | )?;
194 | let annotations = metadata.annotations.unwrap();
195 |
196 | assert_eq!(
197 | annotations.get(KUBEWARDEN_ANNOTATION_POLICY_TITLE),
198 | Some(&String::from(expected_policy_title))
199 | );
200 |
201 | assert_eq!(
202 | annotations.get(KUBEWARDEN_ANNOTATION_KWCTL_VERSION),
203 | Some(&String::from(env!("CARGO_PKG_VERSION"))),
204 | );
205 |
206 | Ok(())
207 | }
208 |
209 | #[test]
210 | fn test_kwctl_version_is_added_when_annotations_is_none() -> Result<()> {
211 | let dir = tempdir()?;
212 |
213 | let file_path = dir.path().join("metadata.yml");
214 | let mut file = File::create(file_path.clone())?;
215 |
216 | let raw_metadata = r#"
217 | rules:
218 | - apiGroups: [""]
219 | apiVersions: ["v1"]
220 | resources: ["pods"]
221 | operations: ["CREATE", "UPDATE"]
222 | mutating: false
223 | backgroundAudit: true
224 | executionMode: kubewarden-wapc
225 | "#;
226 |
227 | write!(file, "{}", raw_metadata)?;
228 |
229 | let backend_detector = BackendDetector::new(
230 | mock_rego_policy_detector_false,
231 | mock_protocol_version_detector_v1,
232 | );
233 | let metadata = prepare_metadata(
234 | PathBuf::from("irrelevant.wasm"),
235 | file_path,
236 | backend_detector,
237 | None,
238 | )?;
239 | let annotations = metadata.annotations.unwrap();
240 |
241 | assert_eq!(
242 | annotations.get(KUBEWARDEN_ANNOTATION_KWCTL_VERSION),
243 | Some(&String::from(env!("CARGO_PKG_VERSION"))),
244 | );
245 |
246 | Ok(())
247 | }
248 |
249 | #[test]
250 | fn test_kwctl_usage_is_added_when_annotations_is_none() -> Result<()> {
251 | let dir = tempdir()?;
252 |
253 | let file_path = dir.path().join("metadata.yml");
254 | let mut file = File::create(file_path.clone())?;
255 |
256 | let raw_metadata = r#"
257 | rules:
258 | - apiGroups: [""]
259 | apiVersions: ["v1"]
260 | resources: ["pods"]
261 | operations: ["CREATE", "UPDATE"]
262 | mutating: false
263 | backgroundAudit: true
264 | executionMode: kubewarden-wapc
265 | "#;
266 |
267 | write!(file, "{}", raw_metadata)?;
268 |
269 | let backend_detector = BackendDetector::new(
270 | mock_rego_policy_detector_false,
271 | mock_protocol_version_detector_v1,
272 | );
273 | let metadata = prepare_metadata(
274 | PathBuf::from("irrelevant.wasm"),
275 | file_path,
276 | backend_detector,
277 | Some("readme contents"),
278 | )?;
279 | let annotations = metadata.annotations.unwrap();
280 |
281 | assert_eq!(
282 | annotations.get(KUBEWARDEN_ANNOTATION_POLICY_USAGE),
283 | Some(&String::from("readme contents")),
284 | );
285 |
286 | Ok(())
287 | }
288 |
289 | #[test]
290 | fn test_final_metadata_for_a_rego_policy() -> Result<()> {
291 | let dir = tempdir()?;
292 |
293 | let file_path = dir.path().join("metadata.yml");
294 | let mut file = File::create(file_path.clone())?;
295 |
296 | let raw_metadata = String::from(
297 | r#"
298 | rules:
299 | - apiGroups: [""]
300 | apiVersions: ["v1"]
301 | resources: ["pods"]
302 | operations: ["CREATE", "UPDATE"]
303 | mutating: false
304 | backgroundAudit: true
305 | executionMode: opa
306 | "#,
307 | );
308 |
309 | write!(file, "{}", raw_metadata)?;
310 |
311 | let backend_detector = BackendDetector::new(
312 | mock_rego_policy_detector_true,
313 | mock_protocol_version_detector_v1,
314 | );
315 | let metadata = prepare_metadata(
316 | PathBuf::from("irrelevant.wasm"),
317 | file_path,
318 | backend_detector,
319 | None,
320 | );
321 | assert!(metadata.is_ok());
322 | assert_eq!(
323 | metadata.unwrap().protocol_version,
324 | Some(ProtocolVersion::Unknown)
325 | );
326 |
327 | Ok(())
328 | }
329 | }
330 |
--------------------------------------------------------------------------------
/src/backend.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{anyhow, Result};
2 | use lazy_static::lazy_static;
3 | use policy_evaluator::{
4 | evaluation_context::EvaluationContext, policy_evaluator::PolicyExecutionMode,
5 | policy_evaluator_builder::PolicyEvaluatorBuilder, policy_metadata::Metadata, ProtocolVersion,
6 | };
7 | use semver::{BuildMetadata, Prerelease, Version};
8 | use std::path::{Path, PathBuf};
9 |
10 | lazy_static! {
11 | static ref KUBEWARDEN_VERSION: Version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
12 | }
13 |
14 | pub(crate) enum Backend {
15 | Opa,
16 | OpaGatekeeper,
17 | Wasi,
18 | KubewardenWapc(ProtocolVersion),
19 | }
20 |
21 | type KubewardenProtocolDetectorFn = fn(PathBuf) -> Result;
22 | type RegoDetectorFn = fn(PathBuf) -> Result;
23 |
24 | // Looks at the Wasm module pointed by `wasm_path` and return whether it was generated by a Rego
25 | // policy
26 | //
27 | // The code looks at the export symbols offered by the Wasm module.
28 | // Having at least one symbol that starts with the `opa_` prefix leads
29 | // the policy to be considered a Rego-based one.
30 | fn rego_policy_detector(wasm_path: PathBuf) -> Result {
31 | let data: Vec = std::fs::read(wasm_path.clone())
32 | .map_err(|e| anyhow!("cannot access file {:?}: {}", wasm_path, e))?;
33 | for payload in wasmparser::Parser::new(0).parse_all(&data) {
34 | if let wasmparser::Payload::ExportSection(s) =
35 | payload.map_err(|e| anyhow!("cannot parse WebAssembly file: {}", e))?
36 | {
37 | for export in s {
38 | if export
39 | .map_err(|e| anyhow!("cannot parse WebAssembly export section: {}", e))?
40 | .name
41 | .starts_with("opa_")
42 | {
43 | return Ok(true);
44 | }
45 | }
46 | }
47 | }
48 |
49 | Ok(false)
50 | }
51 |
52 | fn kubewarden_protocol_detector(wasm_path: PathBuf) -> Result {
53 | let eval_ctx = EvaluationContext::default();
54 | PolicyEvaluatorBuilder::new()
55 | .policy_file(&wasm_path)?
56 | .execution_mode(PolicyExecutionMode::KubewardenWapc)
57 | .build_pre()?
58 | .rehydrate(&eval_ctx)?
59 | .protocol_version()
60 | .map_err(|e| anyhow!("Cannot compute ProtocolVersion used by the policy: {:?}", e))
61 | }
62 |
63 | pub(crate) struct BackendDetector {
64 | kubewarden_protocol_detector_func: KubewardenProtocolDetectorFn,
65 | rego_detector_func: RegoDetectorFn,
66 | }
67 |
68 | impl Default for BackendDetector {
69 | fn default() -> Self {
70 | BackendDetector {
71 | kubewarden_protocol_detector_func: kubewarden_protocol_detector,
72 | rego_detector_func: rego_policy_detector,
73 | }
74 | }
75 | }
76 |
77 | impl BackendDetector {
78 | #[allow(dead_code)]
79 | /// This method is intended to be used by unit tests
80 | pub(crate) fn new(
81 | rego_detector_func: RegoDetectorFn,
82 | kubewarden_protocol_detector_func: KubewardenProtocolDetectorFn,
83 | ) -> Self {
84 | BackendDetector {
85 | kubewarden_protocol_detector_func,
86 | rego_detector_func,
87 | }
88 | }
89 |
90 | pub(crate) fn is_rego_policy(&self, wasm_path: &Path) -> Result {
91 | (self.rego_detector_func)(wasm_path.to_path_buf())
92 | .map_err(|e| anyhow!("Rego policy type check failure: {}", e))
93 | }
94 |
95 | pub(crate) fn detect(&self, wasm_path: PathBuf, metadata: &Metadata) -> Result {
96 | let is_rego_policy = self.is_rego_policy(&wasm_path)?;
97 | match metadata.execution_mode {
98 | PolicyExecutionMode::Wasi => Ok(Backend::Wasi),
99 | PolicyExecutionMode::Opa => {
100 | if is_rego_policy {
101 | Ok(Backend::Opa)
102 | } else {
103 | Err(anyhow!(
104 | "Wrong value inside of policy's metadata for 'executionMode'. The policy has not been created using Rego"
105 | ))
106 | }
107 | }
108 | PolicyExecutionMode::OpaGatekeeper => {
109 | if is_rego_policy {
110 | Ok(Backend::OpaGatekeeper)
111 | } else {
112 | Err(anyhow!(
113 | "Wrong value inside of policy's metadata for 'executionMode'. The policy has not been created using Rego"
114 | ))
115 | }
116 | }
117 | PolicyExecutionMode::KubewardenWapc => {
118 | if is_rego_policy {
119 | Err(anyhow!(
120 | "Wrong value inside of policy's metadata for 'executionMode'. This policy has been created using Rego"
121 | ))
122 | } else {
123 | let protocol_version = (self.kubewarden_protocol_detector_func)(wasm_path)
124 | .map_err(|e| {
125 | anyhow!("Error while detecting Kubewarden protocol version: {:?}", e)
126 | })?;
127 | Ok(Backend::KubewardenWapc(protocol_version))
128 | }
129 | }
130 | }
131 | }
132 | }
133 |
134 | /// Check if policy server version is compatible with minimum kubewarden
135 | /// version required by the policy
136 | pub fn has_minimum_kubewarden_version(opt_metadata: Option<&Metadata>) -> Result<()> {
137 | if let Some(metadata) = opt_metadata {
138 | if let Some(minimum_kubewarden_version) = &metadata.minimum_kubewarden_version {
139 | let sanitized_minimum_kubewarden_version = Version {
140 | major: minimum_kubewarden_version.major,
141 | minor: minimum_kubewarden_version.minor,
142 | // Kubewarden stack version ignore patch version number
143 | patch: 0,
144 | pre: Prerelease::EMPTY,
145 | build: BuildMetadata::EMPTY,
146 | };
147 | if *KUBEWARDEN_VERSION < sanitized_minimum_kubewarden_version {
148 | return Err(anyhow!(
149 | "Policy required Kubewarden version {} or greater. But it's running on {}",
150 | sanitized_minimum_kubewarden_version,
151 | KUBEWARDEN_VERSION.to_string(),
152 | ));
153 | }
154 | }
155 | }
156 | Ok(())
157 | }
158 |
159 | #[cfg(test)]
160 | mod tests {
161 | use super::*;
162 |
163 | fn mock_protocol_version_detector_v1(_wasm_path: PathBuf) -> Result {
164 | Ok(ProtocolVersion::V1)
165 | }
166 |
167 | fn mock_rego_policy_detector_true(_wasm_path: PathBuf) -> Result {
168 | Ok(true)
169 | }
170 |
171 | fn mock_rego_policy_detector_false(_wasm_path: PathBuf) -> Result {
172 | Ok(false)
173 | }
174 |
175 | #[test]
176 | fn test_execution_mode_cannot_be_kubewarden_for_a_rego_policy() {
177 | let metadata = Metadata {
178 | execution_mode: PolicyExecutionMode::KubewardenWapc,
179 | ..Default::default()
180 | };
181 |
182 | let backend_detector = BackendDetector::new(
183 | mock_rego_policy_detector_true,
184 | mock_protocol_version_detector_v1,
185 | );
186 | let backend = backend_detector.detect(PathBuf::from("irrelevant.wasm"), &metadata);
187 | assert!(backend.is_err());
188 | }
189 |
190 | #[test]
191 | fn test_execution_mode_cannot_be_opa_or_gatekeeper_for_a_kubewarden_policy() {
192 | for execution_mode in [PolicyExecutionMode::Opa, PolicyExecutionMode::OpaGatekeeper] {
193 | let metadata = Metadata {
194 | execution_mode,
195 | ..Default::default()
196 | };
197 |
198 | let backend_detector = BackendDetector::new(
199 | mock_rego_policy_detector_false,
200 | mock_protocol_version_detector_v1,
201 | );
202 | let backend = backend_detector.detect(PathBuf::from("irrelevant.wasm"), &metadata);
203 | assert!(backend.is_err());
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/bench.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{anyhow, Result};
2 | use tiny_bench::{bench_with_configuration_labeled, BenchmarkConfig};
3 | use tracing::error;
4 |
5 | use crate::run;
6 |
7 | pub(crate) struct PullAndBenchSettings {
8 | pub pull_and_run_settings: run::PullAndRunSettings,
9 | pub benchmark_cfg: BenchmarkConfig,
10 | }
11 |
12 | pub(crate) async fn pull_and_bench(cfg: &PullAndBenchSettings) -> Result<()> {
13 | let run_env = run::prepare_run_env(&cfg.pull_and_run_settings).await?;
14 | let mut policy_evaluator = run_env.policy_evaluator;
15 | let mut callback_handler = run_env.callback_handler;
16 | let callback_handler_shutdown_channel_tx = run_env.callback_handler_shutdown_channel_tx;
17 | let request = run_env.request;
18 |
19 | // validate the settings given by the user
20 | let settings_validation_response = policy_evaluator.validate_settings(&run_env.policy_settings);
21 | if !settings_validation_response.valid {
22 | println!("{}", serde_json::to_string(&settings_validation_response)?);
23 | return Err(anyhow!(
24 | "Provided settings are not valid: {:?}",
25 | settings_validation_response.message
26 | ));
27 | }
28 |
29 | // Spawn the tokio task used by the CallbackHandler
30 | let callback_handle = tokio::spawn(async move {
31 | callback_handler.loop_eval().await;
32 | });
33 |
34 | // validate the settings given by the user
35 | let settings_validation_response = policy_evaluator.validate_settings(&run_env.policy_settings);
36 | if !settings_validation_response.valid {
37 | println!("{}", serde_json::to_string(&settings_validation_response)?);
38 | return Err(anyhow!(
39 | "Provided settings are not valid: {:?}",
40 | settings_validation_response.message
41 | ));
42 | }
43 |
44 | bench_with_configuration_labeled("validate_settings", &cfg.benchmark_cfg, || {
45 | let _settings_validation_response =
46 | policy_evaluator.validate_settings(&run_env.policy_settings);
47 | });
48 |
49 | tokio::task::block_in_place(|| {
50 | bench_with_configuration_labeled("validate", &cfg.benchmark_cfg, || {
51 | let _response = policy_evaluator.validate(request.clone(), &run_env.policy_settings);
52 | });
53 | });
54 |
55 | // The evaluation is done, we can shutdown the tokio task that is running
56 | // the CallbackHandler
57 | if callback_handler_shutdown_channel_tx.send(()).is_err() {
58 | error!("Cannot shut down the CallbackHandler task");
59 | } else if let Err(e) = callback_handle.await {
60 | error!(
61 | error = e.to_string().as_str(),
62 | "Error waiting for the CallbackHandler task"
63 | );
64 | }
65 |
66 | Ok(())
67 | }
68 |
--------------------------------------------------------------------------------
/src/callback_handler/mod.rs:
--------------------------------------------------------------------------------
1 | use crate::run::{HostCapabilitiesMode, PullAndRunSettings};
2 | use anyhow::Result;
3 | use policy_evaluator::{callback_requests::CallbackRequest, kube};
4 | use std::path::PathBuf;
5 | use tokio::sync::{mpsc, oneshot};
6 |
7 | use self::proxy::CallbackHandlerProxy;
8 |
9 | mod proxy;
10 |
11 | #[derive(Clone)]
12 | pub(crate) enum ProxyMode {
13 | Record { destination: PathBuf },
14 | Replay { source: PathBuf },
15 | }
16 |
17 | /// This is an abstraction over the callback_handler provided by the
18 | /// policy_evaluator crate.
19 | /// The goal is to allow kwctl to have a proxy handler, that can
20 | /// record and reply any kind of policy <-> host capability exchange
21 | pub(crate) enum CallbackHandler {
22 | Direct(policy_evaluator::callback_handler::CallbackHandler),
23 | Proxy(proxy::CallbackHandlerProxy),
24 | }
25 |
26 | impl CallbackHandler {
27 | pub async fn new(
28 | cfg: &PullAndRunSettings,
29 | kube_client: Option,
30 | shutdown_channel: oneshot::Receiver<()>,
31 | ) -> Result {
32 | match &cfg.host_capabilities_mode {
33 | HostCapabilitiesMode::Proxy(proxy_mode) => {
34 | new_proxy(proxy_mode, cfg, kube_client, shutdown_channel).await
35 | }
36 | HostCapabilitiesMode::Direct => {
37 | new_transparent(cfg, kube_client, shutdown_channel).await
38 | }
39 | }
40 | }
41 |
42 | pub async fn loop_eval(&mut self) {
43 | match self {
44 | CallbackHandler::Direct(direct) => direct.loop_eval().await,
45 | CallbackHandler::Proxy(proxy) => proxy.loop_eval().await,
46 | }
47 | }
48 |
49 | pub fn sender_channel(&self) -> mpsc::Sender {
50 | match self {
51 | CallbackHandler::Direct(direct) => direct.sender_channel(),
52 | CallbackHandler::Proxy(proxy) => proxy.sender_channel(),
53 | }
54 | }
55 | }
56 |
57 | async fn new_proxy(
58 | mode: &ProxyMode,
59 | cfg: &PullAndRunSettings,
60 | kube_client: Option,
61 | shutdown_channel: oneshot::Receiver<()>,
62 | ) -> Result {
63 | let proxy = CallbackHandlerProxy::new(
64 | mode,
65 | shutdown_channel,
66 | cfg.sources.clone(),
67 | cfg.sigstore_trust_root.clone(),
68 | kube_client,
69 | )
70 | .await?;
71 |
72 | Ok(CallbackHandler::Proxy(proxy))
73 | }
74 |
75 | async fn new_transparent(
76 | cfg: &PullAndRunSettings,
77 | kube_client: Option,
78 | shutdown_channel: oneshot::Receiver<()>,
79 | ) -> Result {
80 | let mut callback_handler_builder =
81 | policy_evaluator::callback_handler::CallbackHandlerBuilder::new(shutdown_channel)
82 | .registry_config(cfg.sources.clone())
83 | .trust_root(cfg.sigstore_trust_root.clone());
84 | if let Some(kc) = kube_client {
85 | callback_handler_builder = callback_handler_builder.kube_client(kc);
86 | }
87 |
88 | let real_callback_handler = callback_handler_builder.build().await?;
89 |
90 | Ok(CallbackHandler::Direct(real_callback_handler))
91 | }
92 |
--------------------------------------------------------------------------------
/src/completions.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{anyhow, Result};
2 | use clap_complete::{
3 | generate,
4 | shells::{Bash, Elvish, Fish, PowerShell, Zsh},
5 | };
6 | use std::io;
7 |
8 | pub(crate) fn completions(shell: &str) -> Result<()> {
9 | let mut app = crate::cli::build_cli();
10 |
11 | match shell {
12 | "bash" => {
13 | generate(Bash, &mut app, "kwctl", &mut io::stdout());
14 | Ok(())
15 | }
16 | "fish" => {
17 | generate(Fish, &mut app, "kwctl", &mut io::stdout());
18 | Ok(())
19 | }
20 | "zsh" => {
21 | generate(Zsh, &mut app, "kwctl", &mut io::stdout());
22 | Ok(())
23 | }
24 | "elvish" => {
25 | generate(Elvish, &mut app, "kwctl", &mut io::stdout());
26 | Ok(())
27 | }
28 | "powershell" => {
29 | generate(PowerShell, &mut app, "kwctl", &mut io::stdout());
30 | Ok(())
31 | }
32 | unknown => Err(anyhow!("Unknown shell '{}'", unknown)),
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/info.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use clap::crate_version;
3 | use itertools::Itertools;
4 | use policy_evaluator::{
5 | burrego,
6 | policy_fetcher::store::{Store, DEFAULT_ROOT},
7 | };
8 |
9 | pub(crate) fn info() -> Result<()> {
10 | let builtins: String = burrego::get_builtins()
11 | .keys()
12 | .sorted()
13 | .map(|builtin| format!(" - {builtin}"))
14 | .join("\n");
15 |
16 | let store = Store::default();
17 |
18 | println!(
19 | r#"kwctl version: {}
20 |
21 | Open Policy Agent/Gatekeeper implemented builtins:
22 | {}
23 |
24 | Policy store: {}
25 | Config directory: {}
26 | kwctl cache directory: {}
27 | "#,
28 | crate_version!(),
29 | builtins,
30 | store.root.to_string_lossy(),
31 | DEFAULT_ROOT.config_dir().to_string_lossy(),
32 | crate::scaffold::DEFAULT_KWCTL_CACHE.to_string_lossy(),
33 | );
34 |
35 | Ok(())
36 | }
37 |
--------------------------------------------------------------------------------
/src/inspect.rs:
--------------------------------------------------------------------------------
1 | use crate::{Registry, Sources};
2 | use anyhow::{anyhow, Result};
3 | use is_terminal::IsTerminal;
4 | use policy_evaluator::{
5 | constants::*,
6 | policy_evaluator::PolicyExecutionMode,
7 | policy_fetcher::{
8 | oci_client::{
9 | manifest::{OciImageManifest, OciManifest},
10 | secrets::RegistryAuth,
11 | },
12 | sigstore::{
13 | cosign::{ClientBuilder, CosignCapabilities},
14 | registry::{oci_reference::OciReference, Auth, ClientConfig},
15 | },
16 | },
17 | policy_metadata::Metadata,
18 | };
19 | use prettytable::{format::FormatBuilder, Table};
20 | use std::io::{self};
21 | use std::{collections::HashMap, convert::TryFrom, str::FromStr};
22 | use termimad::{terminal_size, FmtText, MadSkin};
23 |
24 | pub(crate) async fn inspect(
25 | uri_or_sha_prefix: &str,
26 | output: OutputType,
27 | sources: Option,
28 | no_color: bool,
29 | no_signatures: bool,
30 | ) -> Result<()> {
31 | let uri = crate::utils::map_path_to_uri(uri_or_sha_prefix)?;
32 | let wasm_path = crate::utils::wasm_path(&uri)?;
33 | let metadata_printer = MetadataPrinter::from(&output);
34 |
35 | let metadata = Metadata::from_path(&wasm_path)
36 | .map_err(|e| anyhow!("Error parsing policy metadata: {}", e))?;
37 |
38 | match metadata {
39 | Some(metadata) => metadata_printer.print(&metadata, no_color)?,
40 | None => return Err(anyhow!(
41 | "No Kubewarden metadata found inside of '{}'.\nPolicies can be annotated with the `kwctl annotate` command.",
42 | uri
43 | )),
44 | };
45 |
46 | if no_signatures {
47 | return Ok(());
48 | }
49 |
50 | let signatures = fetch_signatures_manifest(&uri, sources).await;
51 | match signatures {
52 | Ok(signatures) => {
53 | if let Some(signatures) = signatures {
54 | let sigstore_printer = SignaturesPrinter::from(&output);
55 | sigstore_printer.print(&signatures);
56 | }
57 | }
58 | Err(error) => {
59 | println!();
60 | if error
61 | .to_string()
62 | .as_str()
63 | .starts_with("OCI API error: manifest unknown on")
64 | {
65 | println!("No sigstore signatures found");
66 | } else {
67 | println!("Cannot determine if the policy has been signed. There was an error while attempting to fetch its signatures from the remote registry: {error} ")
68 | }
69 | }
70 | }
71 |
72 | Ok(())
73 | }
74 |
75 | pub(crate) enum OutputType {
76 | Yaml,
77 | Pretty,
78 | }
79 |
80 | impl TryFrom