├── VERSION ├── hack ├── tools │ └── bin │ │ └── .gitkeep ├── gen-styles.sh ├── tools.mk ├── build.sh ├── gen-new-disak8sstig-version.sh ├── get-build-ld-flags.sh └── run.sh ├── CODEOWNERS ├── CONTRIBUTING.md ├── pkg ├── report │ ├── templates │ │ └── html │ │ │ └── input.css │ └── report_suite_test.go ├── provider │ ├── managedk8s │ │ ├── export_test.go │ │ ├── ruleset │ │ │ ├── securityhardenedk8s │ │ │ │ ├── rules │ │ │ │ │ ├── options.go │ │ │ │ │ ├── rules_suite_test.go │ │ │ │ │ └── doc.go │ │ │ │ └── options.go │ │ │ └── disak8sstig │ │ │ │ ├── rules │ │ │ │ ├── doc.go │ │ │ │ ├── rules_suite_test.go │ │ │ │ ├── options.go │ │ │ │ ├── 242390.go │ │ │ │ └── 242390_test.go │ │ │ │ └── options.go │ │ ├── managedk8s_suite_test.go │ │ └── options.go │ ├── garden │ │ ├── ruleset │ │ │ └── securityhardenedshoot │ │ │ │ ├── rules │ │ │ │ ├── options.go │ │ │ │ ├── rules_suite_test.go │ │ │ │ ├── 2001.go │ │ │ │ ├── 2003.go │ │ │ │ └── 2004.go │ │ │ │ └── options.go │ │ └── options.go │ ├── gardener │ │ ├── provider_suite_test.go │ │ ├── ruleset │ │ │ └── disak8sstig │ │ │ │ ├── rules │ │ │ │ ├── stringgenerator.go │ │ │ │ ├── doc.go │ │ │ │ ├── rules_suite_test.go │ │ │ │ ├── options.go │ │ │ │ └── 242377.go │ │ │ │ └── options.go │ │ ├── provider_test.go │ │ └── options.go │ ├── virtualgarden │ │ ├── ruleset │ │ │ └── disak8sstig │ │ │ │ ├── rules │ │ │ │ ├── options.go │ │ │ │ ├── doc.go │ │ │ │ ├── rules_suite_test.go │ │ │ │ └── 242400.go │ │ │ │ └── options.go │ │ └── options.go │ └── provider.go ├── shared │ ├── images │ │ └── images.go │ ├── kubernetes │ │ └── option │ │ │ └── options_suite_test.go │ ├── ruleset │ │ ├── disak8sstig │ │ │ ├── rules │ │ │ │ ├── stringgenerator.go │ │ │ │ ├── doc.go │ │ │ │ ├── rules_suite_test.go │ │ │ │ ├── 242395.go │ │ │ │ ├── 245542.go │ │ │ │ ├── 242386.go │ │ │ │ ├── 242388.go │ │ │ │ ├── 242389.go │ │ │ │ ├── 242421.go │ │ │ │ ├── 242461.go │ │ │ │ ├── 242402.go │ │ │ │ ├── 242429.go │ │ │ │ ├── 242430.go │ │ │ │ ├── 242419.go │ │ │ │ ├── 242431.go │ │ │ │ ├── 242397.go │ │ │ │ ├── 242409.go │ │ │ │ ├── 242464.go │ │ │ │ ├── 242462.go │ │ │ │ ├── 242463.go │ │ │ │ ├── 242381.go │ │ │ │ ├── 242438.go │ │ │ │ ├── ids.go │ │ │ │ ├── 242378.go │ │ │ │ ├── 242391.go │ │ │ │ ├── 242395_test.go │ │ │ │ ├── 242420.go │ │ │ │ ├── 242376.go │ │ │ │ ├── 242392.go │ │ │ │ ├── 242434.go │ │ │ │ ├── 242387.go │ │ │ │ └── 242418.go │ │ │ ├── option │ │ │ │ └── options_suite_test.go │ │ │ └── retryerrors │ │ │ │ ├── retryerrors_suite_test.go │ │ │ │ └── retryerrors.go │ │ └── ruleset.go │ └── provider │ │ └── provider.go ├── rule │ ├── rule_suite_test.go │ ├── retry │ │ ├── retry_suite_test.go │ │ └── options.go │ ├── skiprule.go │ └── helpers.go ├── kubernetes │ ├── pod │ │ ├── pod_suite_test.go │ │ ├── fake │ │ │ ├── fake_suite_test.go │ │ │ └── pod.go │ │ └── privileged.go │ ├── utils │ │ └── utils_suite_test.go │ └── config │ │ ├── mount.go │ │ ├── kubeProxy.go │ │ ├── etcd.go │ │ └── kubelet.go ├── internal │ ├── utils │ │ ├── utils_suite_test.go │ │ └── set.go │ ├── config │ │ └── option_config.go │ ├── stringgen │ │ ├── fake │ │ │ └── generator.go │ │ └── generator.go │ └── kubernetes │ │ └── utils │ │ └── utils.go ├── ruleset │ └── ruleset.go ├── metadata │ └── metadata.go └── config │ └── config.go ├── OWNERS ├── .dockerignore ├── tailwind.config.js ├── OWNERS_ALIASES ├── .github ├── ISSUE_TEMPLATE │ ├── enhancement_request.md │ └── bug_report.md ├── actions │ └── prepare-release │ │ └── action.yaml ├── workflows │ ├── reuse-check.yaml │ ├── pr-release-notes-validation.yaml │ ├── non-release.yaml │ ├── export-digests-from-component-descriptor.sh │ └── release.yaml ├── pull_request_template.md └── renovate.json5 ├── imagevector ├── images.yaml ├── images.go └── imagevector.go ├── _typos.toml ├── .ocm ├── base-component.yaml └── branch-info.yaml ├── docs ├── providers │ ├── gardener.md │ ├── garden.md │ ├── virtualgarden.md │ └── managedk8s.md ├── rulesets │ └── disa-k8s-stig │ │ └── ruleset.md ├── development │ └── getting-started.md └── usage │ ├── security-hardened-k8s-shoot.md │ └── migrate-selector-rule-options.md ├── .gitignore ├── LICENSES └── MIT.txt ├── REUSE.toml ├── Dockerfile ├── cmd └── diki │ └── main.go ├── example └── config │ ├── garden.yaml │ └── virtualgarden.yaml ├── .golangci.yaml └── Makefile /VERSION: -------------------------------------------------------------------------------- 1 | v0.23.0-dev 2 | -------------------------------------------------------------------------------- /hack/tools/bin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # diki maintainers 2 | * @gardener/diki-maintainers -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please refer to the [Gardener contributor guide](https://gardener.cloud/docs/contribute). -------------------------------------------------------------------------------- /pkg/report/templates/html/input.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | 3 | reviewers: 4 | - diki-reviewers 5 | approvers: 6 | - diki-approvers 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | bin 6 | testbin 7 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './pkg/report/templates/html/*.html', 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | prefix: 'tw-', 10 | plugins: [], 11 | } 12 | -------------------------------------------------------------------------------- /OWNERS_ALIASES: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | 3 | aliases: 4 | diki-reviewers: 5 | - AleksandarSavchev 6 | - dimityrmirchev 7 | - georgibaltiev 8 | diki-approvers: 9 | - AleksandarSavchev 10 | - dimityrmirchev 11 | - georgibaltiev 12 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/export_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package managedk8s 6 | 7 | func SetInClusterConfigFunc(f inClusterConfigGetter) { 8 | inClusterConfigFunc = f 9 | } 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement Request 3 | about: Suggest an enhancement 4 | labels: kind/enhancement 5 | 6 | --- 7 | 8 | 9 | 10 | **What would you like to be added**: 11 | 12 | **Why is this needed**: 13 | -------------------------------------------------------------------------------- /imagevector/images.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | --- 6 | images: 7 | - name: diki-ops 8 | sourceRepository: github.com/gardener/diki 9 | repository: europe-docker.pkg.dev/gardener-project/releases/gardener/diki-ops 10 | -------------------------------------------------------------------------------- /pkg/shared/images/images.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package images 6 | 7 | const ( 8 | // DikiOpsImageName is a constant for the name of the github.com/gardener/diki diki-ops image. 9 | DikiOpsImageName = "diki-ops" 10 | ) 11 | -------------------------------------------------------------------------------- /.github/actions/prepare-release/action.yaml: -------------------------------------------------------------------------------- 1 | name: Prepare-Release 2 | 3 | runs: 4 | using: composite 5 | steps: 6 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 7 | with: 8 | go-version: '1.25' 9 | - name: prepare-release 10 | shell: bash 11 | run: | 12 | set -eu 13 | make generate 14 | -------------------------------------------------------------------------------- /_typos.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | extend-ignore-re = [ 3 | # This enables # spellchecker:disable-line 4 | "(?Rm)^.*(#|//)\\s*spellchecker:disable-line$", 5 | # This enables # spellchecker:off/on 6 | "(?s)(#|//)\\s*spellchecker:off.*?\\n\\s*(#|//)\\s*spellchecker:on", 7 | ] 8 | 9 | [files] 10 | extend-exclude = [ 11 | "go.mod", 12 | "go.sum", 13 | ] 14 | -------------------------------------------------------------------------------- /imagevector/images.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package imagevector 6 | 7 | import ( 8 | _ "embed" 9 | ) 10 | 11 | // Images YAML contains the contents of the images.yaml file. 12 | // 13 | //go:embed images.yaml 14 | var ImagesYAML string 15 | -------------------------------------------------------------------------------- /.github/workflows/reuse-check.yaml: -------------------------------------------------------------------------------- 1 | name: REUSE Compliance Check 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 10 | - name: REUSE Compliance Check 11 | uses: fsfe/reuse-action@676e2d560c9a403aa252096d99fcab3e1132b0f5 # v6.0.0 12 | -------------------------------------------------------------------------------- /pkg/provider/garden/ruleset/securityhardenedshoot/rules/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | type RuleOption interface { 8 | Options1000 | 9 | Options1001 | 10 | Options1002 | 11 | Options1003 | 12 | Options2000 | 13 | Options2007 14 | } 15 | -------------------------------------------------------------------------------- /.ocm/base-component.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | main-source: 6 | labels: 7 | - name: cloud.gardener.cnudie/dso/scanning-hints/source_analysis/v1 8 | value: 9 | policy: skip 10 | comment: | 11 | we use gosec for sast scanning. See attached log. 12 | -------------------------------------------------------------------------------- /.ocm/branch-info.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | release-branch-template: release-v$major.$minor # e.g. release-v1.0 6 | branch-policy: 7 | significant-part: minor # major, minor, patch 8 | supported-versions-count: 1 9 | release-cadence: null # d (days), w (weeks), y | yr (years) 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug 4 | labels: kind/bug 5 | 6 | --- 7 | 8 | 9 | 10 | **What happened**: 11 | 12 | **What you expected to happen**: 13 | 14 | **How to reproduce it (as minimally and precisely as possible)**: 15 | 16 | **Anything else we need to know**: 17 | 18 | **Environment**: 19 | -------------------------------------------------------------------------------- /pkg/rule/rule_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rule_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestRule(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Rule Test Suite") 17 | } 18 | -------------------------------------------------------------------------------- /pkg/kubernetes/pod/pod_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package pod_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestPod(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Pod Test Suite") 17 | } 18 | -------------------------------------------------------------------------------- /pkg/report/report_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package report_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestReport(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Report Test Suite") 17 | } 18 | -------------------------------------------------------------------------------- /pkg/internal/utils/utils_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package utils_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestUtils(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Utils Test Suite") 17 | } 18 | -------------------------------------------------------------------------------- /pkg/kubernetes/pod/fake/fake_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package fake_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestFakes(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Fake Test Suite") 17 | } 18 | -------------------------------------------------------------------------------- /pkg/kubernetes/utils/utils_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package utils_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestUtils(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Utils Test Suite") 17 | } 18 | -------------------------------------------------------------------------------- /pkg/shared/kubernetes/option/options_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package option_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestOptions(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Options Test Suite") 17 | } 18 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/stringgenerator.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import "github.com/gardener/diki/pkg/internal/stringgen" 8 | 9 | var ( 10 | // Generator is a not secure random Generator. Exposed for testing purposes. 11 | Generator stringgen.StringGenerator = stringgen.Default() 12 | ) 13 | -------------------------------------------------------------------------------- /pkg/kubernetes/config/mount.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package config 6 | 7 | // Mount is the .Spec.mount field of a container. 8 | type Mount struct { 9 | Destination string `json:"destination"` 10 | Type string `json:"type"` 11 | Source string `json:"source"` 12 | Options []string `json:"options"` 13 | } 14 | -------------------------------------------------------------------------------- /pkg/provider/gardener/provider_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package gardener_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestGardener(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Provider Gardener Suite") 17 | } 18 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/ruleset/securityhardenedk8s/rules/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | type RuleOption interface { 8 | Options2000 | 9 | Options2001 | 10 | Options2002 | 11 | Options2003 | 12 | Options2004 | 13 | Options2005 | 14 | Options2006 | 15 | Options2007 | 16 | Options2008 17 | } 18 | -------------------------------------------------------------------------------- /pkg/provider/gardener/ruleset/disak8sstig/rules/stringgenerator.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import "github.com/gardener/diki/pkg/internal/stringgen" 8 | 9 | var ( 10 | // Generator is a not secure random Generator. Exposed for testing purposes. 11 | Generator stringgen.StringGenerator = stringgen.Default() 12 | ) 13 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/option/options_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package option_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestOptions(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Options Test Suite") 17 | } 18 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/managedk8s_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package managedk8s_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestManagedK8s(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Provider managedk8s Suite") 17 | } 18 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/retryerrors/retryerrors_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package retryerrors_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestRetryErrors(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Retry Errors Test Suite") 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/pr-release-notes-validation.yaml: -------------------------------------------------------------------------------- 1 | name: Validate release-notes from PRs 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - edited 7 | - reopened 8 | 9 | permissions: 10 | pull-requests: read 11 | 12 | jobs: 13 | validate-release-notes: 14 | uses: gardener/cc-utils/.github/workflows/validate-release-notes.yaml@master 15 | permissions: 16 | pull-requests: read 17 | with: 18 | pull-request-body: ${{ github.event.pull_request.body }} 19 | -------------------------------------------------------------------------------- /pkg/provider/garden/ruleset/securityhardenedshoot/rules/rules_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestRules(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Security Hardener Shoot Cluster Test Suite") 17 | } 18 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/ruleset/securityhardenedk8s/rules/rules_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func TestRules(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "Security Hardener Kubernetes Cluster Test Suite") 17 | } 18 | -------------------------------------------------------------------------------- /docs/providers/gardener.md: -------------------------------------------------------------------------------- 1 | # Gardener 2 | 3 | ## Provider 4 | 5 | The `Gardener` provider is capable of accessing a `seed/shoot` environment and running `rulesets` against it. 6 | 7 | ## Rulesets 8 | 9 | The `Gardener` provider implements the following `rulesets`: 10 | - [DISA Kubernetes Security Technical Implementation Guide](../rulesets/disa-k8s-stig/ruleset.md) 11 | - v2r4 12 | - v2r3 13 | 14 | ### Configuration 15 | 16 | See an [example Diki configuration](../../example/config/gardener.yaml) for this provider. -------------------------------------------------------------------------------- /hack/gen-styles.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | 7 | set -e 8 | 9 | tailwindcss -c ./tailwind.config.js -i ./pkg/report/templates/html/input.css -o ./pkg/report/templates/html/output.css --minify 10 | 11 | cat < ./pkg/report/templates/html/_styles.tpl 12 | {{define "_styles"}} 13 | 16 | {{end}} 17 | EOF 18 | -------------------------------------------------------------------------------- /docs/providers/garden.md: -------------------------------------------------------------------------------- 1 | # Garden 2 | 3 | ## Provider 4 | 5 | The `Garden` provider is capable of accessing a Garden cluster environment and running `rulesets` against it. 6 | 7 | ## Rulesets 8 | 9 | The `Garden` provider implements the following `rulesets`: 10 | - [Security Hardened Shoot Cluster](../rulesets/security-hardened-shoot-cluster/ruleset.md) 11 | - v0.2.1 12 | - v0.2.0 13 | - v0.1.0 14 | 15 | ### Configuration 16 | 17 | See an [example Diki configuration](../../example/config/garden.yaml) for this provider. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin 8 | testbin 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Dependency directories (remove the comment below to include it) 17 | # vendor/ 18 | 19 | # ignore test kubeconfigs 20 | .kube 21 | 22 | /hack/tools/bin 23 | 24 | # gosec 25 | gosec-report.sarif 26 | 27 | # Visual Studio Code settings 28 | .vscode/ 29 | 30 | -------------------------------------------------------------------------------- /docs/providers/virtualgarden.md: -------------------------------------------------------------------------------- 1 | # Virtual Garden 2 | 3 | ## Provider 4 | 5 | The `Virtual Garden` provider is capable of accessing a `runtime/virtual garden` environment and running `rulesets` against it. 6 | 7 | ## Rulesets 8 | 9 | The `Gardener` provider implements the following `rulesets`: 10 | - [DISA Kubernetes Security Technical Implementation Guide](../rulesets/disa-k8s-stig/ruleset.md) 11 | - v2r4 12 | - v2r3 13 | 14 | ### Configuration 15 | 16 | See an [example Diki configuration](../../example/config/virtualgarden.yaml) for this provider. 17 | -------------------------------------------------------------------------------- /.github/workflows/non-release.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | build: 8 | uses: ./.github/workflows/build.yaml 9 | with: 10 | mode: snapshot 11 | secrets: inherit 12 | permissions: 13 | contents: write 14 | packages: write 15 | id-token: write 16 | 17 | component-descriptor: 18 | uses: gardener/cc-utils/.github/workflows/post-build.yaml@master 19 | needs: 20 | - build 21 | secrets: inherit 22 | permissions: 23 | id-token: write 24 | contents: write 25 | -------------------------------------------------------------------------------- /pkg/internal/config/option_config.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package config 6 | 7 | import "github.com/gardener/diki/pkg/config" 8 | 9 | // IndexedRuleOptionsConfig represents per rule options and the index at which the option is configured in the ruleOptions configuration. 10 | type IndexedRuleOptionsConfig struct { 11 | config.RuleOptionsConfig 12 | // Index is the rule option's index in the ruleOptions configuration. 13 | Index int 14 | } 15 | -------------------------------------------------------------------------------- /pkg/provider/virtualgarden/ruleset/disak8sstig/rules/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "github.com/gardener/diki/pkg/shared/ruleset/disak8sstig/option" 9 | sharedrules "github.com/gardener/diki/pkg/shared/ruleset/disak8sstig/rules" 10 | ) 11 | 12 | type RuleOption interface { 13 | sharedrules.Options242390 | option.Options242442 | sharedrules.Options245543 | sharedrules.Options254800 | option.FileOwnerOptions 14 | } 15 | -------------------------------------------------------------------------------- /pkg/provider/gardener/ruleset/disak8sstig/rules/doc.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Package rules implements rules that correspond to the latest supported ruleset version. 6 | // These rules can be reused by an older supported ruleset versions 7 | // in case a rule implementation did not change. 8 | // Rule implementations that had changed in latest supported version 9 | // but still need to be supported because of old ruleset versions 10 | // should be separated in ruleset versioned specific package. 11 | package rules 12 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/ruleset/disak8sstig/rules/doc.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Package rules implements rules that correspond to the latest supported ruleset version. 6 | // These rules can be reused by an older supported ruleset versions 7 | // in case a rule implementation did not change. 8 | // Rule implementations that had changed in latest supported version 9 | // but still need to be supported because of old ruleset versions 10 | // should be separated in ruleset versioned specific package. 11 | package rules 12 | -------------------------------------------------------------------------------- /pkg/provider/virtualgarden/ruleset/disak8sstig/rules/doc.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Package rules implements rules that correspond to the latest supported ruleset version. 6 | // These rules can be reused by an older supported ruleset versions 7 | // in case a rule implementation did not change. 8 | // Rule implementations that had changed in latest supported version 9 | // but still need to be supported because of old ruleset versions 10 | // should be separated in ruleset versioned specific package. 11 | package rules 12 | -------------------------------------------------------------------------------- /pkg/rule/retry/retry_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package retry_test 6 | 7 | import ( 8 | "log/slog" 9 | "os" 10 | "testing" 11 | 12 | . "github.com/onsi/ginkgo/v2" 13 | . "github.com/onsi/gomega" 14 | ) 15 | 16 | var testLogger *slog.Logger 17 | 18 | func TestRetry(t *testing.T) { 19 | handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}) 20 | logger := slog.New(handler) 21 | testLogger = logger 22 | RegisterFailHandler(Fail) 23 | RunSpecs(t, "Retry Test Suite") 24 | } 25 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/ruleset/securityhardenedk8s/rules/doc.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Package rules implements rules that correspond to the latest supported ruleset version. 6 | // These rules can be reused by an older supported ruleset versions 7 | // in case a rule implementation did not change. 8 | // Rule implementations that had changed in latest supported version 9 | // but still need to be supported because of old ruleset versions 10 | // should be separated in ruleset versioned specific package. 11 | package rules 12 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/doc.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Package rules implements rules that are shared across providers 6 | // and correspond to the latest supported ruleset version. 7 | // These rules can be reused by an older supported ruleset versions 8 | // in case a rule implementation did not change. 9 | // Rule implementations that had changed in latest supported version 10 | // but still need to be supported because of old ruleset versions 11 | // should be separated in ruleset versioned specific package. 12 | package rules 13 | -------------------------------------------------------------------------------- /pkg/kubernetes/config/kubeProxy.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package config 6 | 7 | // KubeProxyConfig describes kube-proxy configuration values. 8 | type KubeProxyConfig struct { 9 | ClientConnection KPClientConnection `yaml:"clientConnection" json:"clientConnection"` 10 | FeatureGates map[string]bool `yaml:"featureGates" json:"featureGates"` 11 | } 12 | 13 | // KPClientConnection describes kube-proxy configuration values for client connection. 14 | type KPClientConnection struct { 15 | Kubeconfig string `yaml:"kubeconfig" json:"kubeconfig"` 16 | } 17 | -------------------------------------------------------------------------------- /pkg/provider/gardener/ruleset/disak8sstig/rules/rules_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules_test 6 | 7 | import ( 8 | "log/slog" 9 | "os" 10 | "testing" 11 | 12 | . "github.com/onsi/ginkgo/v2" 13 | . "github.com/onsi/gomega" 14 | ) 15 | 16 | var testLogger *slog.Logger 17 | 18 | func TestRules(t *testing.T) { 19 | handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}) 20 | logger := slog.New(handler) 21 | testLogger = logger 22 | RegisterFailHandler(Fail) 23 | RunSpecs(t, "DISA Kubernetes STIG rules Test Suite") 24 | } 25 | -------------------------------------------------------------------------------- /pkg/internal/stringgen/fake/generator.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package fake 6 | 7 | import "github.com/gardener/diki/pkg/internal/stringgen" 8 | 9 | var _ stringgen.StringGenerator = (*FakeRandString)(nil) 10 | 11 | // Generate returns the same rune n times 12 | func (r *FakeRandString) Generate(n int) string { 13 | b := make([]rune, n) 14 | for i := 0; i < n; i++ { 15 | b[i] = r.Rune 16 | } 17 | r.Rune++ 18 | return string(b) 19 | } 20 | 21 | // FakeRandString is a generator that satisfies [stringgen.StringGenerator] 22 | type FakeRandString struct { 23 | Rune rune 24 | } 25 | -------------------------------------------------------------------------------- /pkg/provider/gardener/ruleset/disak8sstig/rules/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "github.com/gardener/diki/pkg/shared/ruleset/disak8sstig/option" 9 | sharedrules "github.com/gardener/diki/pkg/shared/ruleset/disak8sstig/rules" 10 | ) 11 | 12 | type RuleOption interface { 13 | sharedrules.Options242390 | 14 | Options242400 | 15 | option.Options242414 | 16 | option.Options242415 | 17 | option.Options242442 | 18 | Options242451 | 19 | Options242466 | 20 | Options242467 | 21 | sharedrules.Options245543 | 22 | sharedrules.Options254800 | 23 | option.FileOwnerOptions 24 | } 25 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/rules_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules_test 6 | 7 | import ( 8 | "log/slog" 9 | "os" 10 | "testing" 11 | 12 | . "github.com/onsi/ginkgo/v2" 13 | . "github.com/onsi/gomega" 14 | 15 | "github.com/gardener/diki/pkg/shared/provider" 16 | ) 17 | 18 | var testLogger provider.Logger 19 | 20 | func TestRules(t *testing.T) { 21 | handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}) 22 | logger := slog.New(handler) 23 | testLogger = logger 24 | RegisterFailHandler(Fail) 25 | RunSpecs(t, "DISA Kubernetes STIG rules Test Suite") 26 | } 27 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/ruleset/disak8sstig/rules/rules_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules_test 6 | 7 | import ( 8 | "log/slog" 9 | "os" 10 | "testing" 11 | 12 | . "github.com/onsi/ginkgo/v2" 13 | . "github.com/onsi/gomega" 14 | 15 | "github.com/gardener/diki/pkg/shared/provider" 16 | ) 17 | 18 | var testLogger provider.Logger 19 | 20 | func TestRules(t *testing.T) { 21 | handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}) 22 | logger := slog.New(handler) 23 | testLogger = logger 24 | RegisterFailHandler(Fail) 25 | RunSpecs(t, "DISA Kubernetes STIG rules Test Suite") 26 | } 27 | -------------------------------------------------------------------------------- /pkg/provider/virtualgarden/ruleset/disak8sstig/rules/rules_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules_test 6 | 7 | import ( 8 | "log/slog" 9 | "os" 10 | "testing" 11 | 12 | . "github.com/onsi/ginkgo/v2" 13 | . "github.com/onsi/gomega" 14 | 15 | "github.com/gardener/diki/pkg/shared/provider" 16 | ) 17 | 18 | var testLogger provider.Logger 19 | 20 | func TestRules(t *testing.T) { 21 | handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}) 22 | logger := slog.New(handler) 23 | testLogger = logger 24 | RegisterFailHandler(Fail) 25 | RunSpecs(t, "DISA Kubernetes STIG rules Test Suite") 26 | } 27 | -------------------------------------------------------------------------------- /hack/tools.mk: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | TAILWINDCSS := $(TOOLS_BIN_DIR)/tailwindcss 6 | 7 | # default tool versions 8 | TAILWINDCSS_VERSION ?= v3.3.3 9 | 10 | ######################################### 11 | # Tools # 12 | ######################################### 13 | $(TAILWINDCSS): $(call tool_version_file,$(TAILWINDCSS),$(TAILWINDCSS_VERSION)) 14 | curl -L -o $(TAILWINDCSS) https://github.com/tailwindlabs/tailwindcss/releases/download/$(TAILWINDCSS_VERSION)/tailwindcss-$(shell uname -s | tr '[:upper:]' '[:lower:]' | sed 's/darwin/macos/')-$(shell uname -m | sed 's/x86_64/x64/') 15 | chmod +x $(TAILWINDCSS) 16 | -------------------------------------------------------------------------------- /pkg/ruleset/ruleset.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package ruleset 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/gardener/diki/pkg/rule" 11 | ) 12 | 13 | // RulesetResult contains the results of Rule runs belonging to the same Ruleset. 14 | type RulesetResult struct { 15 | RulesetID string 16 | RulesetName string 17 | RulesetVersion string 18 | RuleResults []rule.RuleResult 19 | } 20 | 21 | // Ruleset is a set of Rules. 22 | type Ruleset interface { 23 | ID() string 24 | Name() string 25 | Version() string 26 | Run(ctx context.Context) (RulesetResult, error) 27 | RunRule(ctx context.Context, id string) (rule.RuleResult, error) 28 | } 29 | -------------------------------------------------------------------------------- /imagevector/imagevector.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package imagevector 6 | 7 | import ( 8 | "github.com/gardener/gardener/pkg/utils/imagevector" 9 | "k8s.io/apimachinery/pkg/util/runtime" 10 | ) 11 | 12 | var imageVector imagevector.ImageVector 13 | 14 | func init() { 15 | var err error 16 | 17 | imageVector, err = imagevector.Read([]byte(ImagesYAML)) 18 | runtime.Must(err) 19 | 20 | imageVector, err = imagevector.WithEnvOverride(imageVector, imagevector.OverrideEnv) 21 | runtime.Must(err) 22 | } 23 | 24 | // ImageVector is the image vector that contains all the needed images. 25 | func ImageVector() imagevector.ImageVector { 26 | return imageVector 27 | } 28 | -------------------------------------------------------------------------------- /pkg/kubernetes/config/etcd.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package config 6 | 7 | // EtcdConfig describes ETCD configuration values. 8 | type EtcdConfig struct { 9 | InitialCluster string `yaml:"initial-cluster"` 10 | ClientTransportSecurity TransportSecurity `yaml:"client-transport-security"` 11 | PeerTransportSecurity TransportSecurity `yaml:"peer-transport-security"` 12 | } 13 | 14 | // TransportSecurity is the transport security configuration in an ETCD configuration. 15 | type TransportSecurity struct { 16 | AutoTLS *bool `yaml:"auto-tls"` 17 | CertAuth *bool `yaml:"client-cert-auth"` 18 | CertFile *string `yaml:"cert-file"` 19 | KeyFile *string `yaml:"key-file"` 20 | } 21 | -------------------------------------------------------------------------------- /hack/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | 7 | set -e 8 | 9 | platform="${1:-"linux-amd64 linux-arm64 darwin-amd64 darwin-arm64 windows-amd64"}" 10 | root_dir="$(readlink -f "$(dirname "${0}")/..")" 11 | bin_path="${root_dir}/bin" 12 | 13 | if [[ -z "${LD_FLAGS}" ]]; then 14 | LD_FLAGS=$("$root_dir"/hack/get-build-ld-flags.sh) 15 | fi 16 | 17 | for p in $platform 18 | do 19 | out_file=${bin_path}/diki-${p} 20 | echo "building diki for platform ${p}: ${out_file}" 21 | os=$(echo "${p}" | cut -d "-" -f 1) 22 | arch=$(echo "${p}" | cut -d "-" -f 2) 23 | CGO_ENABLED=0 GOOS=$os GOARCH=$arch GO111MODULE=on go build \ 24 | -ldflags "${LD_FLAGS}" \ 25 | -o "${out_file}" "${root_dir}"/cmd/diki/main.go 26 | done 27 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **How to categorize this PR?** 4 | 10 | /kind TODO 11 | 12 | **What this PR does / why we need it**: 13 | 14 | **Which issue(s) this PR fixes**: 15 | Fixes # 16 | 17 | **Special notes for your reviewer**: 18 | 19 | **Release note**: 20 | 29 | ```feature user 30 | 31 | ``` 32 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /docs/providers/managedk8s.md: -------------------------------------------------------------------------------- 1 | # Managed Kubernetes 2 | 3 | ## Provider 4 | 5 | The `Managed Kubernetes` provider is capable of accessing a managed Kubernetes environment and running 6 | `rulesets` against it. 7 | 8 | ## Rulesets 9 | 10 | The `Managed Kubernetes` provider implements the following `rulesets`: 11 | 12 | - [DISA Kubernetes Security Technical Implementation Guide](../rulesets/disa-k8s-stig/ruleset.md) 13 | - v2r4 14 | - v2r3 15 | 16 | - [Security Hardened Kubernetes Cluster](../rulesets/security-hardened-k8s/ruleset.md) 17 | - v0.1.0 18 | 19 | ### Configuration 20 | 21 | See an [example Diki configuration](../../example/config/managedk8s.yaml) for this provider. 22 | 23 | #### Kubeconfig 24 | 25 | The `Managed Kubernetes` provider requires a valid `kubeconfig` file to connect to the managed Kubernetes cluster. 26 | The provider is supporting three ways to provide the `kubeconfig` file: 27 | 28 | - Directly providing the path to the `kubeconfig` file in the provider configuration. 29 | - Using the `KUBECONFIG` environment variable to point to the `kubeconfig` file. 30 | - Using a mounted ServiceAccount token in a Kubernetes Pod. 31 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/ruleset/disak8sstig/rules/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "github.com/gardener/diki/pkg/shared/kubernetes/option" 9 | disaoption "github.com/gardener/diki/pkg/shared/ruleset/disak8sstig/option" 10 | sharedrules "github.com/gardener/diki/pkg/shared/ruleset/disak8sstig/rules" 11 | ) 12 | 13 | type RuleOption interface { 14 | sharedrules.Options242383 | 15 | sharedrules.Options242393 | 16 | sharedrules.Options242394 | 17 | sharedrules.Options242396 | 18 | Options242400 | 19 | sharedrules.Options242404 | 20 | sharedrules.Options242406 | 21 | sharedrules.Options242407 | 22 | disaoption.Options242414 | 23 | disaoption.Options242415 | 24 | sharedrules.Options242417 | 25 | Options242442 | 26 | sharedrules.Options242448 | 27 | sharedrules.Options242449 | 28 | sharedrules.Options242450 | 29 | Options242451 | 30 | sharedrules.Options242452 | 31 | sharedrules.Options242453 | 32 | Options242466 | 33 | Options242467 | 34 | option.ClusterObjectSelector 35 | } 36 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | SPDX-PackageName = "Gardener diki" 3 | SPDX-PackageSupplier = "The Gardener project " 4 | SPDX-PackageDownloadLocation = "https://github.com/gardener/diki" 5 | 6 | [[annotations]] 7 | path = [".github/**", ".gitignore", ".golangci.yaml", "example/**", "CODEOWNERS", "OWNERS", "OWNERS_ALIASES", "VERSION", "go.mod", "go.sum", "_typos.toml", "tailwind.config.js", "pkg/report/templates/html/input.css", "pkg/report/templates/html/difference_report.html", "pkg/report/templates/html/merged_report.html", "pkg/report/templates/html/report.html"] 8 | precedence = "aggregate" 9 | SPDX-FileCopyrightText = "2017-2025 SAP SE or an SAP affiliate company and Gardener contributors" 10 | SPDX-License-Identifier = "Apache-2.0" 11 | 12 | [[annotations]] 13 | path = "**.md" 14 | precedence = "aggregate" 15 | SPDX-FileCopyrightText = "2017-2025 SAP SE or an SAP affiliate company and Gardener contributors" 16 | SPDX-License-Identifier = "CC-BY-4.0" 17 | 18 | [[annotations]] 19 | path = ["pkg/report/templates/html/_styles.tpl", "pkg/report/templates/html/output.css"] 20 | precedence = "aggregate" 21 | SPDX-FileCopyrightText = "Copyright (c) Tailwind Labs, Inc. (https://tailwindcss.com)" 22 | SPDX-License-Identifier = "MIT" 23 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/retryerrors/retryerrors.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package retryerrors 6 | 7 | import ( 8 | "regexp" 9 | ) 10 | 11 | var ( 12 | // ContainerNotFoundOnNodeRegexp regex to match container on node not found 13 | ContainerNotFoundOnNodeRegexp = regexp.MustCompile(`(?i)(command /bin/sh /run/containerd.*not found)`) 14 | // ContainerFileNotFoundOnNodeRegexp regex to match container file path on node not found 15 | ContainerFileNotFoundOnNodeRegexp = regexp.MustCompile(`(?i)(command /bin/sh (find|stat).*No such file or directory)`) 16 | // ContainerNotReadyRegexp regex to match container not yet in status or not running 17 | ContainerNotReadyRegexp = regexp.MustCompile(`(?i)(container with name .* (not \(yet\) in status|not \(yet\) running))`) 18 | // OpsPodNotFoundRegexp regex to match ops pod not found for DISA K8s STIG ruleset 19 | OpsPodNotFoundRegexp = regexp.MustCompile(`(?i)(pods "diki-[\d]{6}-.{10}" not found)`) 20 | // ObjectNotFoundRegexp regex to match object not found by nerdctl 21 | ObjectNotFoundRegexp = regexp.MustCompile(`(?i)(command /bin/sh /.*/nerdctl .* \[no such object)`) 22 | ) 23 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/ruleset/securityhardenedk8s/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package securityhardenedk8s 6 | 7 | import ( 8 | "log/slog" 9 | 10 | "k8s.io/client-go/rest" 11 | ) 12 | 13 | // CreateOption is a function that acts on a [Ruleset] 14 | // and is used to construct such objects. 15 | type CreateOption func(*Ruleset) 16 | 17 | // WithVersion sets the version of a [Ruleset]. 18 | func WithVersion(version string) CreateOption { 19 | return func(r *Ruleset) { 20 | r.version = version 21 | } 22 | } 23 | 24 | // WithConfig sets the Config of a [Ruleset]. 25 | func WithConfig(config *rest.Config) CreateOption { 26 | return func(r *Ruleset) { 27 | r.Config = config 28 | } 29 | } 30 | 31 | // WithNumberOfWorkers sets the max number of Workers of a [Ruleset]. 32 | func WithNumberOfWorkers(numWorkers int) CreateOption { 33 | return func(r *Ruleset) { 34 | if numWorkers <= 0 { 35 | panic("number of workers should be a positive number") 36 | } 37 | r.numWorkers = numWorkers 38 | } 39 | } 40 | 41 | // WithLogger the logger of a [Ruleset]. 42 | func WithLogger(logger *slog.Logger) CreateOption { 43 | return func(r *Ruleset) { 44 | r.logger = logger 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pkg/provider/garden/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package garden 6 | 7 | import ( 8 | "k8s.io/client-go/rest" 9 | 10 | "github.com/gardener/diki/pkg/shared/provider" 11 | ) 12 | 13 | // CreateOption is a function that acts on a [Provider] 14 | // and is used to construct such objects. 15 | type CreateOption func(*Provider) 16 | 17 | // WithID sets the id of a [Provider]. 18 | func WithID(id string) CreateOption { 19 | return func(p *Provider) { 20 | p.id = id 21 | } 22 | } 23 | 24 | // WithName sets the name of a [Provider]. 25 | func WithName(name string) CreateOption { 26 | return func(p *Provider) { 27 | p.name = name 28 | } 29 | } 30 | 31 | // WithConfig sets the Config of a [Provider]. 32 | func WithConfig(config *rest.Config) CreateOption { 33 | return func(p *Provider) { 34 | p.Config = config 35 | } 36 | } 37 | 38 | // WithMetadata sets the metadata of a [Provider]. 39 | func WithMetadata(metadata map[string]string) CreateOption { 40 | return func(p *Provider) { 41 | p.metadata = metadata 42 | } 43 | } 44 | 45 | // WithLogger sets the logger of a [Provider]. 46 | func WithLogger(logger provider.Logger) CreateOption { 47 | return func(p *Provider) { 48 | p.logger = logger 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/rule/retry/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package retry 6 | 7 | import "github.com/gardener/diki/pkg/rule" 8 | 9 | // CreateOption is a function that acts on a [RetryableRule] 10 | // and is used to construct such objects. 11 | type CreateOption func(*RetryableRule) 12 | 13 | // WithBaseRule sets the BaseRule of a [RetryableRule]. 14 | func WithBaseRule(baseRule rule.Rule) CreateOption { 15 | return func(rr *RetryableRule) { 16 | rr.BaseRule = baseRule 17 | } 18 | } 19 | 20 | // WithMaxRetries sets the MaxRetries of a [RetryableRule]. 21 | func WithMaxRetries(maxRetries int) CreateOption { 22 | return func(rr *RetryableRule) { 23 | if maxRetries < 0 { 24 | panic("maxRetries should not be a negative number") 25 | } 26 | rr.MaxRetries = maxRetries 27 | } 28 | } 29 | 30 | // WithRetryCondition sets the RetryCondition of a [RetryableRule]. 31 | func WithRetryCondition(retryCondition func(ruleResult rule.RuleResult) bool) CreateOption { 32 | return func(rr *RetryableRule) { 33 | rr.RetryCondition = retryCondition 34 | } 35 | } 36 | 37 | // WithLogger the logger of a [RetryableRule]. 38 | func WithLogger(logger Logger) CreateOption { 39 | return func(rr *RetryableRule) { 40 | rr.Logger = logger 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/internal/stringgen/generator.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package stringgen 6 | 7 | import ( 8 | "math/rand" 9 | "time" 10 | ) 11 | 12 | // StringGenerator generates a string with specified length 13 | type StringGenerator interface { 14 | Generate(int) string 15 | } 16 | 17 | // Default returns a new [*RandString] seeded with time.Now().UnixNano() 18 | // and with charset of "1234567890abcdefghijklmnopqrstuvwxyz" 19 | func Default() *RandString { 20 | chars := []rune("1234567890abcdefghijklmnopqrstuvwxyz") 21 | return NewRand(rand.NewSource(time.Now().UnixNano()), chars) 22 | } 23 | 24 | // RandString is not a secure random generator. 25 | type RandString struct { 26 | random *rand.Rand 27 | chars []rune 28 | } 29 | 30 | // NewRand returns a not secure random string generator. 31 | func NewRand(source rand.Source, chars []rune) *RandString { 32 | return &RandString{ 33 | random: rand.New(source), // #nosec G404 34 | chars: chars, 35 | } 36 | } 37 | 38 | // Generate generates a not secure random string with length n. 39 | func (r *RandString) Generate(n int) string { 40 | runes := make([]rune, n) 41 | for i := range runes { 42 | runes[i] = r.chars[r.random.Intn(len(r.chars))] 43 | } 44 | return string(runes) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/metadata/metadata.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package metadata 6 | 7 | // Version is used to represent a specific version of a ruleset. 8 | type Version struct { 9 | // Version is the name of the ruleset release. 10 | Version string `json:"version"` 11 | // Latest shows if the specific version is the latest one. 12 | Latest bool `json:"latest"` 13 | } 14 | 15 | // Ruleset is used to represent a specific ruleset and it's metadata. 16 | type Ruleset struct { 17 | // ID is the unique identifier of the ruleset. 18 | ID string `json:"id"` 19 | // Name is the user-friendly name of the ruleset. 20 | Name string `json:"name"` 21 | // Versions is used to showcase the supported versions of the specific ruleset. 22 | Versions []Version `json:"versions"` 23 | } 24 | 25 | // Provider is used to represent an available provider by it's name and unique identifier. 26 | type Provider struct { 27 | // ID is the unique identifier of the provider. 28 | ID string `json:"id"` 29 | // Name is the user-friendly name of the provider. 30 | Name string `json:"name"` 31 | } 32 | 33 | // ProviderDetailed is used to represent a specific provider and it's metadata. 34 | type ProviderDetailed struct { 35 | Provider 36 | Rulesets []Ruleset `json:"rulesets"` 37 | } 38 | -------------------------------------------------------------------------------- /pkg/provider/gardener/provider_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package gardener_test 6 | 7 | import ( 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | "k8s.io/client-go/rest" 11 | 12 | "github.com/gardener/diki/pkg/provider/gardener" 13 | ) 14 | 15 | var _ = Describe("gardener", func() { 16 | var ( 17 | id, name string 18 | shootConfig, seedConfig *rest.Config 19 | args gardener.Args 20 | ) 21 | 22 | BeforeEach(func() { 23 | id = "test" 24 | name = "test" 25 | shootConfig = &rest.Config{ 26 | Host: "foo", 27 | } 28 | seedConfig = &rest.Config{ 29 | Host: "bar", 30 | } 31 | }) 32 | 33 | Describe("#New", func() { 34 | It("should return correct provider object when correct values are used.", func() { 35 | provider, err := gardener.New( 36 | gardener.WithID(id), 37 | gardener.WithName(name), 38 | gardener.WithShootConfig(shootConfig), 39 | gardener.WithSeedConfig(seedConfig), 40 | gardener.WithArgs(args), 41 | ) 42 | 43 | Expect(provider.ID()).To(Equal(id)) 44 | Expect(provider.Name()).To(Equal(name)) 45 | Expect(provider.Args.ShootName).To(Equal(args.ShootName)) 46 | Expect(err).NotTo(HaveOccurred()) 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | FROM golang:1.25.5 AS go-builder 6 | 7 | ARG TARGETARCH 8 | WORKDIR /workspace 9 | 10 | COPY . . 11 | 12 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -a -ldflags="$(/workspace/hack/get-build-ld-flags.sh)" -o diki cmd/diki/main.go 13 | 14 | FROM gcr.io/distroless/static-debian12:nonroot AS diki 15 | WORKDIR / 16 | COPY --from=go-builder /workspace/diki . 17 | 18 | ENTRYPOINT ["/diki"] 19 | 20 | FROM alpine:3.23.0 AS diki-ops-builder 21 | 22 | ARG TARGETARCH 23 | 24 | RUN apk --no-cache add curl &&\ 25 | curl -sLf https://github.com/containerd/nerdctl/releases/download/v2.2.0/nerdctl-2.2.0-linux-${TARGETARCH}.tar.gz -o /nerdctl.tar.gz &&\ 26 | tar -C /usr/local/bin -xzvf nerdctl.tar.gz 27 | 28 | WORKDIR /volume 29 | 30 | RUN mkdir -p ./bin ./usr/local/bin ./sbin ./lib ./tmp \ 31 | && cp -d /bin/busybox ./bin && echo "package busybox" \ 32 | && cp -d /lib/ld-musl-* ./lib && echo "package musl" \ 33 | && cp -d /usr/sbin/chroot ./sbin && echo "package chroot" \ 34 | && cp -d /usr/local/bin/nerdctl ./usr/local/bin && echo "package nerdctl" 35 | 36 | FROM scratch AS diki-ops 37 | WORKDIR / 38 | COPY --from=diki-ops-builder /volume . 39 | -------------------------------------------------------------------------------- /docs/rulesets/disa-k8s-stig/ruleset.md: -------------------------------------------------------------------------------- 1 | # DISA Kubernetes Security Technical Implementation Guide 2 | 3 | ## Helpful Links 4 | 5 | ### Track the rules 6 | - [Cyber Trackr](https://cyber.trackr.live/stig) 7 | - [DISA STIGs](https://public.cyber.mil/stigs/downloads/) 8 | - [STIG Viewer](https://stigviewer.com/stigs/kubernetes/) 9 | 10 | ### Kubernetes Components 11 | - [ETCD Command Line Options (deprecated, yet overwriting)](https://etcd.io/docs/v3.4/op-guide/configuration) 12 | - [ETCD Config File Options](https://github.com/etcd-io/etcd/blob/main/etcd.conf.yml.sample) 13 | - [Kubernetes API Server Command Line Options](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver) 14 | - [Kubernetes Controller Manager Command Line Options](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager) 15 | - [Kubernetes Scheduler Command Line Options](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-scheduler) 16 | - [Kubernetes Proxy Command Line Options](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy) 17 | - [Kubelet Command Line Options (deprecated, yet overwriting)](https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet) 18 | - [Kubelet Config File Options](https://kubernetes.io/docs/reference/config-api/kubelet-config.v1beta1) 19 | - [Feature Gates](https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates) -------------------------------------------------------------------------------- /cmd/diki/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package main 6 | 7 | import ( 8 | "log" 9 | 10 | controllerruntime "sigs.k8s.io/controller-runtime" 11 | 12 | "github.com/gardener/diki/cmd/diki/app" 13 | "github.com/gardener/diki/pkg/provider" 14 | "github.com/gardener/diki/pkg/provider/builder" 15 | "github.com/gardener/diki/pkg/provider/garden" 16 | "github.com/gardener/diki/pkg/provider/gardener" 17 | "github.com/gardener/diki/pkg/provider/managedk8s" 18 | "github.com/gardener/diki/pkg/provider/virtualgarden" 19 | ) 20 | 21 | func main() { 22 | cmd := app.NewDikiCommand( 23 | map[string]provider.ProviderOption{ 24 | garden.ProviderID: {ProviderFromConfigFunc: builder.GardenProviderFromConfig, MetadataFunc: builder.GardenProviderMetadata}, 25 | gardener.ProviderID: {ProviderFromConfigFunc: builder.GardenerProviderFromConfig, MetadataFunc: builder.GardenerProviderMetadata}, 26 | managedk8s.ProviderID: {ProviderFromConfigFunc: builder.ManagedK8SProviderFromConfig, MetadataFunc: builder.ManagedK8SProviderMetadata, DefaultDikiConfigFunc: managedk8s.ManagedK8sDefaultDikiConfigFunc}, 27 | virtualgarden.ProviderID: {ProviderFromConfigFunc: builder.VirtualGardenProviderFromConfig, MetadataFunc: builder.VirtualGardenProviderMetadata}, 28 | }, 29 | ) 30 | 31 | if err := cmd.ExecuteContext(controllerruntime.SetupSignalHandler()); err != nil { 32 | log.Fatal(err) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hack/gen-new-disak8sstig-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | 7 | set -e 8 | 9 | old_version=${1} 10 | old_version_uppercase=$(echo "${old_version}" | tr '[:lower:]' '[:upper:]') 11 | new_version=${2} 12 | new_version_uppercase=$(echo "${new_version}" | tr '[:lower:]' '[:upper:]') 13 | 14 | disak8sstig_path="$(dirname "$0")/../pkg/provider/gardener/ruleset/disak8sstig" 15 | old_version_dir="${disak8sstig_path}/${old_version}" 16 | new_version_dir="${disak8sstig_path}/${new_version}" 17 | old_ruleset_file="${disak8sstig_path}/${old_version}_ruleset.go" 18 | new_ruleset_file="${disak8sstig_path}/${new_version}_ruleset.go" 19 | 20 | if [ -d "${new_version_dir}" ]; then 21 | echo "error: directory for ${new_version} already exists." 22 | exit 1 23 | fi 24 | 25 | if [ -f "${new_ruleset_file}" ]; then 26 | echo "error: ruleset file for ${new_version} already exists." 27 | exit 1 28 | fi 29 | 30 | cp -r "${old_version_dir}" "${new_version_dir}" 31 | cp "${old_ruleset_file}" "${new_ruleset_file}" 32 | 33 | find "${new_version_dir}" -name '*.go' -exec sed -i -e "s/${old_version}/${new_version}/g" -e "s/${old_version_uppercase}/${new_version_uppercase}/g" {} \; 34 | sed -i -e "s/${old_version}/${new_version}/g" -e "s/${old_version_uppercase}/${new_version_uppercase}/g" "${new_ruleset_file}" 35 | find "${new_version_dir}" -name '*.go' -exec bash -c 'mv "$0" "$(echo "$0" | sed s/'"${old_version}/${new_version}"'/)" 2>/dev/null' {} \; 36 | -------------------------------------------------------------------------------- /pkg/provider/virtualgarden/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package virtualgarden 6 | 7 | import ( 8 | "log/slog" 9 | 10 | "k8s.io/client-go/rest" 11 | ) 12 | 13 | // CreateOption is a function that acts on a [Provider] 14 | // and is used to construct such objects. 15 | type CreateOption func(*Provider) 16 | 17 | // WithID sets the id of a [Provider]. 18 | func WithID(id string) CreateOption { 19 | return func(p *Provider) { 20 | p.id = id 21 | } 22 | } 23 | 24 | // WithName sets the name of a [Provider]. 25 | func WithName(name string) CreateOption { 26 | return func(p *Provider) { 27 | p.name = name 28 | } 29 | } 30 | 31 | // WithAdditionalOpsPodLabels sets the AdditionalOpsPodLabels of a [Provider]. 32 | func WithAdditionalOpsPodLabels(labels map[string]string) CreateOption { 33 | return func(p *Provider) { 34 | p.AdditionalOpsPodLabels = labels 35 | } 36 | } 37 | 38 | // WithRuntimeConfig sets the ShootConfig of a [Provider]. 39 | func WithRuntimeConfig(config *rest.Config) CreateOption { 40 | return func(p *Provider) { 41 | p.RuntimeConfig = config 42 | } 43 | } 44 | 45 | // WithMetadata sets the metadata of a [Provider]. 46 | func WithMetadata(metadata map[string]string) CreateOption { 47 | return func(p *Provider) { 48 | p.metadata = metadata 49 | } 50 | } 51 | 52 | // WithLogger sets the logger of a [Provider]. 53 | func WithLogger(logger *slog.Logger) CreateOption { 54 | return func(p *Provider) { 55 | p.logger = logger 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package managedk8s 6 | 7 | import ( 8 | "k8s.io/client-go/rest" 9 | 10 | "github.com/gardener/diki/pkg/shared/provider" 11 | ) 12 | 13 | // CreateOption is a function that acts on a [Provider] 14 | // and is used to construct such objects. 15 | type CreateOption func(*Provider) 16 | 17 | // WithID sets the id of a [Provider]. 18 | func WithID(id string) CreateOption { 19 | return func(p *Provider) { 20 | p.id = id 21 | } 22 | } 23 | 24 | // WithName sets the name of a [Provider]. 25 | func WithName(name string) CreateOption { 26 | return func(p *Provider) { 27 | p.name = name 28 | } 29 | } 30 | 31 | // WithAdditionalOpsPodLabels sets the AdditionalOpsPodLabels of a [Provider]. 32 | func WithAdditionalOpsPodLabels(labels map[string]string) CreateOption { 33 | return func(p *Provider) { 34 | p.AdditionalOpsPodLabels = labels 35 | } 36 | } 37 | 38 | // WithConfig sets the Config of a [Provider]. 39 | func WithConfig(config *rest.Config) CreateOption { 40 | return func(p *Provider) { 41 | p.Config = config 42 | } 43 | } 44 | 45 | // WithMetadata sets the metadata of a [Provider]. 46 | func WithMetadata(metadata map[string]string) CreateOption { 47 | return func(p *Provider) { 48 | p.metadata = metadata 49 | } 50 | } 51 | 52 | // WithLogger sets the logger of a [Provider]. 53 | func WithLogger(logger provider.Logger) CreateOption { 54 | return func(p *Provider) { 55 | p.logger = logger 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pkg/internal/kubernetes/utils/utils.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package utils 6 | 7 | // PodSecurityStandardProfile defines the different restriction levels that can be applied to the default operations of a PodSecurity admission plugin. 8 | type PodSecurityStandardProfile string 9 | 10 | const ( 11 | // PSSProfilePrivileged indicates an unrestricted policy, which allows for known privilege escalations. 12 | PSSProfilePrivileged PodSecurityStandardProfile = "privileged" 13 | // PSSProfileBaseline indicates a minimally restrictive policy, which bars from privilege escalations. 14 | PSSProfileBaseline PodSecurityStandardProfile = "baseline" 15 | // PSSProfileRestricted indicates a heavily restrictive policy. 16 | PSSProfileRestricted PodSecurityStandardProfile = "restricted" 17 | ) 18 | 19 | // Level defines the order of restrictiveness of the different PodSecurityStandardProfile values. Higher number indicates more restrictions. 20 | func (profile PodSecurityStandardProfile) Level() int { 21 | switch profile { 22 | case PSSProfilePrivileged: 23 | return 1 24 | case PSSProfileBaseline: 25 | return 2 26 | case PSSProfileRestricted: 27 | return 3 28 | default: 29 | return -1 30 | } 31 | } 32 | 33 | // LessRestrictive is a comparator that checks if the calling profile is less restrictive than the argument profile that is evaluated. 34 | func (profile PodSecurityStandardProfile) LessRestrictive(argumentProfile PodSecurityStandardProfile) bool { 35 | return profile.Level() < argumentProfile.Level() 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/export-digests-from-component-descriptor.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | if [[ -z "${1:-}" ]]; then 5 | echo "Usage: ${BASH_SOURCE[0]} COMPONENT_DESCRIPTOR" >&2 6 | exit 1 7 | fi 8 | 9 | command -v yq >/dev/null 2>&1 || { echo "yq is needed" >&2; exit 1; } 10 | 11 | component_descriptor="$1" 12 | 13 | read_digest() { 14 | local os="$1" arch="$2" ref 15 | ref="$(yq -r " 16 | .component.resources[] 17 | | select(.name == \"diki\" and .extraIdentity.os == \"$os\" and .extraIdentity.architecture == \"$arch\") 18 | | .access.localReference 19 | " "$component_descriptor")" || return 1 20 | 21 | [[ -n "$ref" && "$ref" != "null" ]] || return 1 22 | printf '%s\n' "${ref#sha256:}" 23 | } 24 | 25 | DARWIN_SHA_AMD64="$(read_digest darwin amd64)" 26 | : "${DARWIN_SHA_AMD64:?failed to resolve digest for darwin/amd64}" 27 | DARWIN_SHA_ARM64="$(read_digest darwin arm64)" 28 | : "${DARWIN_SHA_ARM64:?failed to resolve digest for darwin/arm64}" 29 | LINUX_SHA_AMD64="$(read_digest linux amd64)" 30 | : "${LINUX_SHA_AMD64:?failed to resolve digest for linux/amd64}" 31 | LINUX_SHA_ARM64="$(read_digest linux arm64)" 32 | : "${LINUX_SHA_ARM64:?failed to resolve digest for linux/arm64}" 33 | WINDOWS_SHA="$(read_digest windows amd64)" 34 | : "${WINDOWS_SHA:?failed to resolve digest for windows/amd64}" 35 | 36 | printf 'darwin-amd64: %s\n' "$DARWIN_SHA_AMD64" 37 | printf 'darwin-arm64: %s\n' "$DARWIN_SHA_ARM64" 38 | printf 'linux-amd64: %s\n' "$LINUX_SHA_AMD64" 39 | printf 'linux-arm64: %s\n' "$LINUX_SHA_ARM64" 40 | printf 'windows-amd64: %s\n' "$WINDOWS_SHA" 41 | 42 | export DARWIN_SHA_AMD64 DARWIN_SHA_ARM64 LINUX_SHA_AMD64 LINUX_SHA_ARM64 WINDOWS_SHA 43 | -------------------------------------------------------------------------------- /pkg/provider/garden/ruleset/securityhardenedshoot/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package securityhardenedshoot 6 | 7 | import ( 8 | "log/slog" 9 | 10 | "k8s.io/client-go/rest" 11 | ) 12 | 13 | // CreateOption is a function that acts on a [Ruleset] 14 | // and is used to construct such objects. 15 | type CreateOption func(*Ruleset) 16 | 17 | // WithVersion sets the version of a [Ruleset]. 18 | func WithVersion(version string) CreateOption { 19 | return func(r *Ruleset) { 20 | r.version = version 21 | } 22 | } 23 | 24 | // WithConfig sets the Config of a [Ruleset]. 25 | func WithConfig(config *rest.Config) CreateOption { 26 | return func(r *Ruleset) { 27 | r.Config = config 28 | } 29 | } 30 | 31 | // WithArgs sets the args of a [Ruleset]. 32 | func WithArgs(args Args) CreateOption { 33 | return func(r *Ruleset) { 34 | if len(args.ProjectNamespace) == 0 { 35 | panic("project namespace should not be empty") 36 | } 37 | if len(args.ShootName) == 0 { 38 | panic("shoot name should not be empty") 39 | } 40 | 41 | r.args.ProjectNamespace = args.ProjectNamespace 42 | r.args.ShootName = args.ShootName 43 | } 44 | } 45 | 46 | // WithNumberOfWorkers sets the max number of Workers of a [Ruleset]. 47 | func WithNumberOfWorkers(numWorkers int) CreateOption { 48 | return func(r *Ruleset) { 49 | if numWorkers <= 0 { 50 | panic("number of workers should be a positive number") 51 | } 52 | r.numWorkers = numWorkers 53 | } 54 | } 55 | 56 | // WithLogger the logger of a [Ruleset]. 57 | func WithLogger(logger *slog.Logger) CreateOption { 58 | return func(r *Ruleset) { 59 | r.logger = logger 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pkg/internal/utils/set.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package utils 6 | 7 | import ( 8 | "slices" 9 | ) 10 | 11 | // EqualSets checks if two slices contain exactly the same elements independent of the ordering. 12 | func EqualSets(s1, s2 []string) bool { 13 | clone1 := slices.Clone(s1) 14 | clone2 := slices.Clone(s2) 15 | slices.Sort(clone1) 16 | slices.Sort(clone2) 17 | return slices.Equal(clone1, clone2) 18 | } 19 | 20 | // Subset checks if all elements of s1 are contained in s2. An empty s1 is always a subset of s2. 21 | func Subset(s1, s2 []string) bool { 22 | for _, s1v := range s1 { 23 | if !slices.Contains(s2, s1v) { 24 | return false 25 | } 26 | } 27 | return true 28 | } 29 | 30 | // Intersect checks if s1 and s2 intersect. Empty sets do not intersect. 31 | func Intersect(s1, s2 []string) bool { 32 | for _, s1v := range s1 { 33 | if slices.Contains(s2, s1v) { 34 | return true 35 | } 36 | } 37 | return false 38 | } 39 | 40 | // StartsWith checks if all ordered elements of s2 are the first elements that occur in s1. If s2 is empty, the function returns true. 41 | func StartsWith(s1 []string, s2 ...string) bool { 42 | if len(s2) > len(s1) { 43 | return false 44 | } 45 | 46 | for i := range s2 { 47 | if s2[i] != s1[i] { 48 | return false 49 | } 50 | } 51 | return true 52 | } 53 | 54 | // MatchLabels checks if all m2 keys and values are present in m1. If m1 or m2 is nil returns false. 55 | func MatchLabels(m1, m2 map[string]string) bool { 56 | if m1 == nil || m2 == nil { 57 | return false 58 | } 59 | 60 | for k, v := range m2 { 61 | if m1[k] != v { 62 | return false 63 | } 64 | } 65 | 66 | return true 67 | } 68 | -------------------------------------------------------------------------------- /pkg/provider/provider.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package provider 6 | 7 | import ( 8 | "context" 9 | 10 | "k8s.io/apimachinery/pkg/util/validation/field" 11 | 12 | "github.com/gardener/diki/pkg/config" 13 | "github.com/gardener/diki/pkg/metadata" 14 | "github.com/gardener/diki/pkg/rule" 15 | "github.com/gardener/diki/pkg/ruleset" 16 | ) 17 | 18 | // Provider defines a Diki provider. 19 | type Provider interface { 20 | ID() string 21 | Name() string 22 | Metadata() map[string]string 23 | RunAll(ctx context.Context) (ProviderResult, error) 24 | RunRuleset(ctx context.Context, rulesetID, rulesetVersion string) (ruleset.RulesetResult, error) 25 | RunRule(ctx context.Context, rulesetID, rulesetVersion, ruleID string) (rule.RuleResult, error) 26 | } 27 | 28 | // ProviderResult is the result of a provider run. 29 | type ProviderResult struct { 30 | ProviderID string 31 | ProviderName string 32 | Metadata map[string]string 33 | RulesetResults []ruleset.RulesetResult 34 | } 35 | 36 | // ProviderFromConfigFunc constructs a Provider from ProviderConfig. 37 | type ProviderFromConfigFunc func(conf config.ProviderConfig, fldPath *field.Path) (Provider, error) 38 | 39 | // MetadataFunc constructs a detailed Provider metadata object. 40 | type MetadataFunc func() metadata.ProviderDetailed 41 | 42 | // DefaultDikiConfigFunc constructs a default [config.DikiConfig] for a specific provider. 43 | type DefaultDikiConfigFunc func() config.DikiConfig 44 | 45 | // ProviderOption constructs a pair of a configuration and metadata function for a specific provider. 46 | type ProviderOption struct { 47 | ProviderFromConfigFunc 48 | MetadataFunc 49 | DefaultDikiConfigFunc 50 | } 51 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/ruleset/disak8sstig/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package disak8sstig 6 | 7 | import ( 8 | "log/slog" 9 | 10 | "k8s.io/client-go/rest" 11 | ) 12 | 13 | // CreateOption is a function that acts on a [Ruleset] 14 | // and is used to construct such objects. 15 | type CreateOption func(*Ruleset) 16 | 17 | // WithVersion sets the version of a [Ruleset]. 18 | func WithVersion(version string) CreateOption { 19 | return func(r *Ruleset) { 20 | r.version = version 21 | } 22 | } 23 | 24 | // WithAdditionalOpsPodLabels sets the AdditionalOpsPodLabels of a [Ruleset]. 25 | func WithAdditionalOpsPodLabels(labels map[string]string) CreateOption { 26 | return func(r *Ruleset) { 27 | r.AdditionalOpsPodLabels = labels 28 | } 29 | } 30 | 31 | // WithConfig sets the Config of a [Ruleset]. 32 | func WithConfig(config *rest.Config) CreateOption { 33 | return func(r *Ruleset) { 34 | r.Config = config 35 | } 36 | } 37 | 38 | // WithArgs sets the args of a [Ruleset]. 39 | func WithArgs(args Args) CreateOption { 40 | return func(r *Ruleset) { 41 | switch { 42 | case args.MaxRetries == nil: 43 | return 44 | case *args.MaxRetries < 0: 45 | panic("max retries should not be a negative number") 46 | default: 47 | r.args.MaxRetries = args.MaxRetries 48 | } 49 | } 50 | } 51 | 52 | // WithNumberOfWorkers sets the max number of Workers of a [Ruleset]. 53 | func WithNumberOfWorkers(numWorkers int) CreateOption { 54 | return func(r *Ruleset) { 55 | if numWorkers <= 0 { 56 | panic("number of workers should be a positive number") 57 | } 58 | r.numWorkers = numWorkers 59 | } 60 | } 61 | 62 | // WithLogger the logger of a [Ruleset]. 63 | func WithLogger(logger *slog.Logger) CreateOption { 64 | return func(r *Ruleset) { 65 | r.logger = logger 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pkg/provider/gardener/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package gardener 6 | 7 | import ( 8 | "log/slog" 9 | 10 | "k8s.io/client-go/rest" 11 | ) 12 | 13 | // CreateOption is a function that acts on a Provider 14 | // and is used to construct such objects. 15 | type CreateOption func(*Provider) 16 | 17 | // WithID sets the id of a Provider. 18 | func WithID(id string) CreateOption { 19 | return func(p *Provider) { 20 | p.id = id 21 | } 22 | } 23 | 24 | // WithName sets the name of a Provider. 25 | func WithName(name string) CreateOption { 26 | return func(p *Provider) { 27 | p.name = name 28 | } 29 | } 30 | 31 | // WithAdditionalOpsPodLabels sets the AdditionalOpsPodLabels of a [Provider]. 32 | func WithAdditionalOpsPodLabels(labels map[string]string) CreateOption { 33 | return func(p *Provider) { 34 | p.AdditionalOpsPodLabels = labels 35 | } 36 | } 37 | 38 | // WithShootConfig sets the ShootConfig of a Provider. 39 | func WithShootConfig(config *rest.Config) CreateOption { 40 | return func(p *Provider) { 41 | p.ShootConfig = config 42 | } 43 | } 44 | 45 | // WithSeedConfig sets the SeedConfig of a Provider. 46 | func WithSeedConfig(config *rest.Config) CreateOption { 47 | return func(p *Provider) { 48 | p.SeedConfig = config 49 | } 50 | } 51 | 52 | // WithArgs sets the arguments of a Provider. 53 | func WithArgs(args Args) CreateOption { 54 | return func(p *Provider) { 55 | p.Args = args 56 | } 57 | } 58 | 59 | // WithMetadata sets the metadata of a Provider. 60 | func WithMetadata(metadata map[string]string) CreateOption { 61 | return func(p *Provider) { 62 | p.metadata = metadata 63 | } 64 | } 65 | 66 | // WithLogger sets the logger of a Provider. 67 | func WithLogger(logger *slog.Logger) CreateOption { 68 | return func(p *Provider) { 69 | p.logger = logger 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pkg/provider/virtualgarden/ruleset/disak8sstig/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package disak8sstig 6 | 7 | import ( 8 | "log/slog" 9 | 10 | "k8s.io/client-go/rest" 11 | ) 12 | 13 | // CreateOption is a function that acts on a [Ruleset] 14 | // and is used to construct such objects. 15 | type CreateOption func(*Ruleset) 16 | 17 | // WithVersion sets the version of a [Ruleset]. 18 | func WithVersion(version string) CreateOption { 19 | return func(r *Ruleset) { 20 | r.version = version 21 | } 22 | } 23 | 24 | // WithAdditionalOpsPodLabels sets the AdditionalOpsPodLabels of a [Ruleset]. 25 | func WithAdditionalOpsPodLabels(labels map[string]string) CreateOption { 26 | return func(r *Ruleset) { 27 | r.AdditionalOpsPodLabels = labels 28 | } 29 | } 30 | 31 | // WithRuntimeConfig sets the RuntimeConfig of a [Ruleset]. 32 | func WithRuntimeConfig(config *rest.Config) CreateOption { 33 | return func(r *Ruleset) { 34 | r.RuntimeConfig = config 35 | } 36 | } 37 | 38 | // WithArgs sets the args of a [Ruleset]. 39 | func WithArgs(args Args) CreateOption { 40 | return func(r *Ruleset) { 41 | switch { 42 | case args.MaxRetries == nil: 43 | return 44 | case *args.MaxRetries < 0: 45 | panic("max retries should not be a negative number") 46 | default: 47 | r.args.MaxRetries = args.MaxRetries 48 | } 49 | } 50 | } 51 | 52 | // WithNumberOfWorkers sets the max number of Workers of a [Ruleset]. 53 | func WithNumberOfWorkers(numWorkers int) CreateOption { 54 | return func(r *Ruleset) { 55 | if numWorkers <= 0 { 56 | panic("number of workers should be a positive number") 57 | } 58 | r.numWorkers = numWorkers 59 | } 60 | } 61 | 62 | // WithLogger the logger of a [Ruleset]. 63 | func WithLogger(logger *slog.Logger) CreateOption { 64 | return func(r *Ruleset) { 65 | r.logger = logger 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pkg/rule/skiprule.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rule 6 | 7 | import ( 8 | "context" 9 | ) 10 | 11 | var ( 12 | _ Rule = &SkipRule{} 13 | _ Severity = &SkipRule{} 14 | ) 15 | 16 | // SkipRule is a Rule that always reports a predefined status. 17 | type SkipRule struct { 18 | id string 19 | name string 20 | severity SeverityLevel 21 | justification string 22 | status Status 23 | } 24 | 25 | // SkipRuleOption allows to additionally configure a SkipRule. 26 | type SkipRuleOption func(*SkipRule) 27 | 28 | // SkipRuleWithSeverity allows configuring the severity of a SkipRule. 29 | func SkipRuleWithSeverity(severity SeverityLevel) SkipRuleOption { 30 | return func(skipRule *SkipRule) { 31 | skipRule.severity = severity 32 | } 33 | } 34 | 35 | // NewSkipRule returns a new skipped Rule. 36 | func NewSkipRule(id, name, justification string, status Status, options ...SkipRuleOption) *SkipRule { 37 | skipRule := &SkipRule{ 38 | id: id, 39 | name: name, 40 | justification: justification, 41 | status: status, 42 | } 43 | 44 | for _, option := range options { 45 | option(skipRule) 46 | } 47 | 48 | return skipRule 49 | } 50 | 51 | // ID returns the id of the Rule. 52 | func (s *SkipRule) ID() string { 53 | return s.id 54 | } 55 | 56 | // Name returns the name of the Rule. 57 | func (s *SkipRule) Name() string { 58 | return s.name 59 | } 60 | 61 | // Severity returns the severity level of the Rule. 62 | func (s *SkipRule) Severity() SeverityLevel { 63 | return s.severity 64 | } 65 | 66 | // Run immediately returns a RuleResult containing 67 | // a single CheckResult with a predefined status and justification. 68 | func (s *SkipRule) Run(context.Context) (RuleResult, error) { 69 | return Result(s, []CheckResult{ 70 | { 71 | Status: s.status, 72 | Message: s.justification, 73 | }, 74 | }...), nil 75 | } 76 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242395.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | 10 | "k8s.io/apimachinery/pkg/labels" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | 13 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 14 | "github.com/gardener/diki/pkg/rule" 15 | ) 16 | 17 | var ( 18 | _ rule.Rule = &Rule242395{} 19 | _ rule.Severity = &Rule242395{} 20 | ) 21 | 22 | type Rule242395 struct { 23 | Client client.Client 24 | } 25 | 26 | func (r *Rule242395) ID() string { 27 | return ID242395 28 | } 29 | 30 | func (r *Rule242395) Name() string { 31 | return "Kubernetes dashboard must not be enabled." 32 | } 33 | 34 | func (r *Rule242395) Severity() rule.SeverityLevel { 35 | return rule.SeverityMedium 36 | } 37 | 38 | func (r *Rule242395) Run(ctx context.Context) (rule.RuleResult, error) { 39 | pods, err := kubeutils.GetPods(ctx, r.Client, "", labels.SelectorFromSet(labels.Set{"k8s-app": "kubernetes-dashboard"}), 300) 40 | if err != nil { 41 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), rule.NewTarget("kind", "PodList"))), nil 42 | } 43 | filteredPods := kubeutils.FilterPodsByOwnerRef(pods) 44 | 45 | replicaSets, err := kubeutils.GetReplicaSets(ctx, r.Client, "", labels.NewSelector(), 300) 46 | if err != nil { 47 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), rule.NewTarget("kind", "ReplicaSetList"))), nil 48 | } 49 | 50 | var checkResults []rule.CheckResult 51 | for _, pod := range filteredPods { 52 | target := kubeutils.TargetWithPod(rule.NewTarget(), pod, replicaSets) 53 | checkResults = append(checkResults, rule.FailedCheckResult("Kubernetes dashboard installed", target)) 54 | } 55 | 56 | if len(checkResults) == 0 { 57 | return rule.Result(r, rule.PassedCheckResult("Kubernetes dashboard not installed", rule.NewTarget())), nil 58 | } 59 | 60 | return rule.Result(r, checkResults...), nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/shared/provider/provider.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package provider 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | "fmt" 11 | "maps" 12 | 13 | "github.com/gardener/diki/pkg/provider" 14 | "github.com/gardener/diki/pkg/ruleset" 15 | ) 16 | 17 | // Logger is a minimalistic logger interface. 18 | type Logger interface { 19 | Info(string, ...any) 20 | Error(string, ...any) 21 | } 22 | 23 | // RunAll is a sample implementation for a [provider.Provider]. 24 | func RunAll(ctx context.Context, p provider.Provider, rulesets map[string]ruleset.Ruleset, log Logger) (provider.ProviderResult, error) { 25 | if len(rulesets) == 0 { 26 | return provider.ProviderResult{}, fmt.Errorf("no rulests are registered with the provider") 27 | } 28 | 29 | result := provider.ProviderResult{ 30 | ProviderName: p.Name(), 31 | ProviderID: p.ID(), 32 | Metadata: maps.Clone(p.Metadata()), 33 | RulesetResults: make([]ruleset.RulesetResult, 0, len(rulesets)), 34 | } 35 | 36 | var errAgg error 37 | log.Info("starting provider run", "number_of_rulesets", len(rulesets)) 38 | finishMsg := "finished ruleset run" 39 | for _, rs := range rulesets { 40 | select { 41 | case <-ctx.Done(): 42 | return provider.ProviderResult{}, ctx.Err() 43 | default: 44 | log.Info("starting ruleset run", "ruleset", rs.ID(), "version", rs.Version()) 45 | if res, err := rs.Run(ctx); err != nil { 46 | errAgg = errors.Join(errAgg, fmt.Errorf("ruleset with id %s and version %s errored: %w", rs.ID(), rs.Version(), err)) 47 | log.Error(finishMsg, "ruleset", rs.ID(), "version", rs.Version(), "error", err) 48 | } else { 49 | result.RulesetResults = append(result.RulesetResults, res) 50 | log.Info(finishMsg, "ruleset", rs.ID(), "version", rs.Version()) 51 | } 52 | } 53 | } 54 | log.Info("finished provider run") 55 | 56 | if errAgg != nil { 57 | return provider.ProviderResult{}, errAgg 58 | } 59 | return result, nil 60 | } 61 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/245542.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | 13 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 14 | "github.com/gardener/diki/pkg/rule" 15 | ) 16 | 17 | var ( 18 | _ rule.Rule = &Rule245542{} 19 | _ rule.Severity = &Rule245542{} 20 | ) 21 | 22 | type Rule245542 struct { 23 | Client client.Client 24 | Namespace string 25 | DeploymentName string 26 | ContainerName string 27 | } 28 | 29 | func (r *Rule245542) ID() string { 30 | return ID245542 31 | } 32 | 33 | func (r *Rule245542) Name() string { 34 | return "Kubernetes API Server must disable basic authentication to protect information in transit." 35 | } 36 | 37 | func (r *Rule245542) Severity() rule.SeverityLevel { 38 | return rule.SeverityHigh 39 | } 40 | 41 | func (r *Rule245542) Run(ctx context.Context) (rule.RuleResult, error) { 42 | const optName = "basic-auth-file" 43 | deploymentName := "kube-apiserver" 44 | containerName := "kube-apiserver" 45 | 46 | if r.DeploymentName != "" { 47 | deploymentName = r.DeploymentName 48 | } 49 | 50 | if r.ContainerName != "" { 51 | containerName = r.ContainerName 52 | } 53 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 54 | 55 | basicAuthFileOptionSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, optName) 56 | if err != nil { 57 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 58 | } 59 | 60 | // empty options are required 61 | if len(basicAuthFileOptionSlice) == 0 { 62 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s has not been set.", optName), target)), nil 63 | } 64 | 65 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set.", optName), target)), nil 66 | } 67 | -------------------------------------------------------------------------------- /hack/get-build-ld-flags.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | 7 | set -e 8 | 9 | PACKAGE_PATHS="${1:-"k8s.io/component-base"}" 10 | VERSION_PATH="${2:-$(dirname "$0")/../VERSION}" 11 | PROGRAM_NAME="${3:-diki}" 12 | VERSION_VERSIONFILE="$(cat "$VERSION_PATH")" 13 | VERSION="${EFFECTIVE_VERSION:-$VERSION_VERSIONFILE}" 14 | 15 | MAJOR_VERSION="" 16 | MINOR_VERSION="" 17 | 18 | if [[ "${VERSION}" =~ ^v([0-9]+)\.([0-9]+)(\.[0-9]+)?([-].*)?([+].*)?$ ]]; then 19 | MAJOR_VERSION=${BASH_REMATCH[1]} 20 | MINOR_VERSION=${BASH_REMATCH[2]} 21 | if [[ -n "${BASH_REMATCH[4]}" ]]; then 22 | MINOR_VERSION+="+" 23 | fi 24 | fi 25 | 26 | # .dockerignore ignores all files unrelevant for build (e.g. docs) to only copy relevant source files to the build 27 | # container. Hence, git will always detect a dirty work tree when building in a container (many deleted files). 28 | # This command filters out all deleted files that are ignored by .dockerignore to only detect changes to relevant files 29 | # as a dirty work tree. 30 | # Additionally, it filters out changes to the `VERSION` file, as this is currently the only way to inject the 31 | # version-to-build in our pipelines (see https://github.com/gardener/cc-utils/issues/431). 32 | TREE_STATE="$([ -z "$(git status --porcelain 2>/dev/null | grep -vf <(git ls-files --cached --deleted --ignored --exclude-from=.dockerignore) -e 'VERSION')" ] && echo clean || echo dirty)" 33 | 34 | for PACKAGE_PATH in $PACKAGE_PATHS 35 | do 36 | echo "-X $PACKAGE_PATH/version.gitMajor=$MAJOR_VERSION 37 | -X $PACKAGE_PATH/version.gitMinor=$MINOR_VERSION 38 | -X $PACKAGE_PATH/version.gitVersion=$VERSION 39 | -X $PACKAGE_PATH/version.gitTreeState=$TREE_STATE 40 | -X $PACKAGE_PATH/version.gitCommit=$(git rev-parse --verify HEAD) 41 | -X $PACKAGE_PATH/version.buildDate=$(date '+%Y-%m-%dT%H:%M:%S%z' | sed 's/\([0-9][0-9]\)$/:\1/g') 42 | -X $PACKAGE_PATH/version/verflag.programName=$PROGRAM_NAME" 43 | done 44 | -------------------------------------------------------------------------------- /pkg/provider/garden/ruleset/securityhardenedshoot/rules/2001.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | 10 | gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule2001{} 20 | _ rule.Severity = &Rule2001{} 21 | ) 22 | 23 | type Rule2001 struct { 24 | Client client.Client 25 | ShootName string 26 | ShootNamespace string 27 | } 28 | 29 | func (r *Rule2001) ID() string { 30 | return "2001" 31 | } 32 | 33 | func (r *Rule2001) Name() string { 34 | return "Shoot clusters must disable ssh access to worker nodes." 35 | } 36 | 37 | func (r *Rule2001) Severity() rule.SeverityLevel { 38 | return rule.SeverityMedium 39 | } 40 | 41 | func (r *Rule2001) Run(ctx context.Context) (rule.RuleResult, error) { 42 | shoot := &gardencorev1beta1.Shoot{ObjectMeta: metav1.ObjectMeta{Name: r.ShootName, Namespace: r.ShootNamespace}} 43 | if err := r.Client.Get(ctx, client.ObjectKeyFromObject(shoot), shoot); err != nil { 44 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), kubeutils.TargetWithK8sObject(rule.NewTarget(), metav1.TypeMeta{Kind: "Shoot"}, shoot.ObjectMeta))), nil 45 | } 46 | 47 | switch { 48 | case shoot.Spec.Provider.WorkersSettings == nil || shoot.Spec.Provider.WorkersSettings.SSHAccess == nil: 49 | return rule.Result(r, rule.FailedCheckResult("SSH access is not disabled for worker nodes.", rule.NewTarget())), nil 50 | case !shoot.Spec.Provider.WorkersSettings.SSHAccess.Enabled: 51 | return rule.Result(r, rule.PassedCheckResult("SSH access is disabled for worker nodes.", rule.NewTarget())), nil 52 | default: 53 | return rule.Result(r, rule.FailedCheckResult("SSH access is enabled for worker nodes.", rule.NewTarget())), nil 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/ruleset/disak8sstig/rules/242390.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "net/http" 11 | 12 | "github.com/gardener/diki/pkg/rule" 13 | sharedrules "github.com/gardener/diki/pkg/shared/ruleset/disak8sstig/rules" 14 | ) 15 | 16 | var ( 17 | _ rule.Rule = &Rule242390{} 18 | _ rule.Severity = &Rule242390{} 19 | ) 20 | 21 | type Rule242390 struct { 22 | Client *http.Client 23 | KAPIExternalURL string 24 | } 25 | 26 | func (r *Rule242390) ID() string { 27 | return sharedrules.ID242390 28 | } 29 | 30 | func (r *Rule242390) Name() string { 31 | return "The Kubernetes API server must have anonymous authentication disabled." 32 | } 33 | 34 | func (r *Rule242390) Severity() rule.SeverityLevel { 35 | return rule.SeverityHigh 36 | } 37 | 38 | func (r *Rule242390) Run(ctx context.Context) (rule.RuleResult, error) { 39 | request, err := http.NewRequestWithContext(ctx, http.MethodGet, r.KAPIExternalURL, nil) 40 | if err != nil { 41 | return rule.Result(r, rule.ErroredCheckResult(fmt.Sprintf("could not create request: %s", err.Error()), rule.NewTarget())), nil 42 | } 43 | 44 | response, err := r.Client.Do(request) 45 | if err != nil { 46 | return rule.Result(r, rule.ErroredCheckResult(fmt.Sprintf("could not access kube-apiserver: %s", err.Error()), rule.NewTarget())), nil 47 | } 48 | 49 | if response.StatusCode >= 500 && response.StatusCode <= 599 { 50 | return rule.Result(r, rule.WarningCheckResult("Cannot determine if anonymous authentication is enabled for the kube-apiserver.", rule.NewTarget("details", "the request returned 5xx status code"))), nil 51 | } else if response.StatusCode == http.StatusUnauthorized { 52 | return rule.Result(r, rule.PassedCheckResult("The kube-apiserver has anonymous authentication disabled.", rule.NewTarget())), nil 53 | } 54 | return rule.Result(r, rule.FailedCheckResult("The kube-apiserver has anonymous authentication enabled.", rule.NewTarget())), nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242386.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | 13 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 14 | "github.com/gardener/diki/pkg/rule" 15 | ) 16 | 17 | var ( 18 | _ rule.Rule = &Rule242386{} 19 | _ rule.Severity = &Rule242386{} 20 | ) 21 | 22 | type Rule242386 struct { 23 | Client client.Client 24 | Namespace string 25 | DeploymentName string 26 | ContainerName string 27 | } 28 | 29 | func (r *Rule242386) ID() string { 30 | return ID242386 31 | } 32 | 33 | func (r *Rule242386) Name() string { 34 | return "The Kubernetes API server must have the insecure port flag disabled." 35 | } 36 | 37 | func (r *Rule242386) Severity() rule.SeverityLevel { 38 | return rule.SeverityHigh 39 | } 40 | 41 | func (r *Rule242386) Run(ctx context.Context) (rule.RuleResult, error) { 42 | const optName = "insecure-port" 43 | deploymentName := "kube-apiserver" 44 | containerName := "kube-apiserver" 45 | 46 | if r.DeploymentName != "" { 47 | deploymentName = r.DeploymentName 48 | } 49 | 50 | if r.ContainerName != "" { 51 | containerName = r.ContainerName 52 | } 53 | target := rule.NewTarget("kind", "Deployment", "name", deploymentName, "namespace", r.Namespace) 54 | 55 | insecurePortOptionSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, optName) 56 | if err != nil { 57 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 58 | } 59 | 60 | if len(insecurePortOptionSlice) == 0 { 61 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s not set.", optName), target)), nil 62 | } 63 | 64 | // insecure-port is deprecated but still needed for health checks. ref https://github.com/kubernetes/kubernetes/issues/43784 65 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set.", optName), target)), nil 66 | } 67 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242388.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | 13 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 14 | "github.com/gardener/diki/pkg/rule" 15 | ) 16 | 17 | var ( 18 | _ rule.Rule = &Rule242388{} 19 | _ rule.Severity = &Rule242388{} 20 | ) 21 | 22 | type Rule242388 struct { 23 | Client client.Client 24 | Namespace string 25 | DeploymentName string 26 | ContainerName string 27 | } 28 | 29 | func (r *Rule242388) ID() string { 30 | return ID242388 31 | } 32 | 33 | func (r *Rule242388) Name() string { 34 | return "The Kubernetes API server must have the insecure bind address not set." 35 | } 36 | 37 | func (r *Rule242388) Severity() rule.SeverityLevel { 38 | return rule.SeverityHigh 39 | } 40 | 41 | func (r *Rule242388) Run(ctx context.Context) (rule.RuleResult, error) { 42 | const optName = "insecure-bind-address" 43 | deploymentName := "kube-apiserver" 44 | containerName := "kube-apiserver" 45 | 46 | if r.DeploymentName != "" { 47 | deploymentName = r.DeploymentName 48 | } 49 | 50 | if r.ContainerName != "" { 51 | containerName = r.ContainerName 52 | } 53 | target := rule.NewTarget("kind", "Deployment", "name", deploymentName, "namespace", r.Namespace) 54 | 55 | insecureBindAddressOptionSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, optName) 56 | if err != nil { 57 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 58 | } 59 | 60 | if len(insecureBindAddressOptionSlice) == 0 { 61 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s not set.", optName), target)), nil 62 | } 63 | 64 | // insecure-bind-address is deprecated but still needed for health checks. ref https://github.com/kubernetes/kubernetes/issues/43784 65 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set.", optName), target)), nil 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | next-version: 6 | type: choice 7 | options: 8 | - bump-minor 9 | - bump-patch 10 | 11 | 12 | jobs: 13 | build: 14 | uses: ./.github/workflows/build.yaml 15 | with: 16 | mode: release 17 | permissions: 18 | contents: write 19 | packages: write 20 | id-token: write 21 | 22 | release-to-github-and-bump: 23 | uses: gardener/cc-utils/.github/workflows/release.yaml@master 24 | needs: 25 | - build 26 | permissions: 27 | contents: write 28 | packages: write 29 | id-token: write 30 | secrets: inherit 31 | with: 32 | release-commit-target: branch 33 | next-version: ${{ inputs.next-version }} 34 | next-version-callback-action-path: 35 | slack-channel-id: C9CEBQPGE # #sap-tech-gardener 36 | assets: | 37 | - name: diki-darwin-amd64 38 | type: ocm-resource 39 | id: 40 | name: diki 41 | os: darwin 42 | architecture: amd64 43 | - name: diki-darwin-arm64 44 | type: ocm-resource 45 | id: 46 | name: diki 47 | os: darwin 48 | architecture: arm64 49 | - name: diki-linux-amd64 50 | type: ocm-resource 51 | id: 52 | name: diki 53 | os: linux 54 | architecture: amd64 55 | - name: diki-linux-arm64 56 | type: ocm-resource 57 | id: 58 | name: diki 59 | os: linux 60 | architecture: arm64 61 | - name: diki-windows-amd64.exe 62 | type: ocm-resource 63 | id: 64 | name: diki 65 | os: windows 66 | architecture: amd64 67 | 68 | publish-to-package-repositories: 69 | permissions: 70 | contents: write 71 | actions: read 72 | needs: 73 | - release-to-github-and-bump 74 | secrets: inherit 75 | uses: ./.github/workflows/publish-to-package-repositories.yaml 76 | with: 77 | component-descriptor: ${{ needs.release-to-github-and-bump.outputs.component-descriptor }} 78 | -------------------------------------------------------------------------------- /example/config/garden.yaml: -------------------------------------------------------------------------------- 1 | providers: # contains information about known providers 2 | - id: garden # unique provider identifier 3 | name: "Garden" # user friendly name of the provider 4 | metadata: 5 | foo: bar 6 | args: 7 | kubeconfigPath: /tmp/garden.config # path to garden cluster kubeconfig 8 | rulesets: 9 | - id: security-hardened-shoot-cluster 10 | name: Security Hardened Shoot Cluster 11 | version: v0.2.1 12 | args: 13 | projectNamespace: garden-project-name # name of project namespace containing the shoot resource to be tested 14 | shootName: foo # name of shoot resource to be tested 15 | ruleOptions: 16 | # - ruleID: "1000" 17 | # args: 18 | # extensions: 19 | # - type: extension-type-1 20 | # - type: extension-type-2 21 | # - ruleID: "1001" 22 | # args: 23 | # allowedClassifications: 24 | # - supported 25 | # - preview 26 | # - ruleID: "1002" 27 | # args: 28 | # machineImages: 29 | # - name: image-name 30 | # allowedClassifications: 31 | # - supported 32 | # - preview 33 | # - ruleID: "1003" 34 | # args: 35 | # allowedLakomScopes: # Config can be skipped as it defaults to the full list of valid scopes 36 | # - KubeSystemManagedByGardener 37 | # - KubeSystem 38 | # - Cluster 39 | # - ruleID: "2000" 40 | # args: 41 | # acceptedEndpoints: 42 | # - path: /readyz 43 | # - path: /healthz 44 | # - ruleID: "2001" 45 | # skip: 46 | # enabled: true 47 | # justification: "the whole rule is accepted for ... reasons" 48 | # - ruleID: "2007" 49 | # args: 50 | # minPodSecurityStandardsProfile: baseline # if set it will indicate the min Pod Security Standards profile that is allowed. Possible values are "privileged", "baseline" and "restricted". 51 | # metadata: # optional, additional metadata to be added to summary json report 52 | # foo: bar 53 | # bar: 54 | # foo: bar 55 | output: 56 | path: /tmp/test-output.json # optional, path to summary json report. If --output flag is set this configuration is ignored 57 | minStatus: Passed 58 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242389.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | 13 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 14 | "github.com/gardener/diki/pkg/rule" 15 | ) 16 | 17 | var ( 18 | _ rule.Rule = &Rule242389{} 19 | _ rule.Severity = &Rule242389{} 20 | ) 21 | 22 | type Rule242389 struct { 23 | Client client.Client 24 | Namespace string 25 | DeploymentName string 26 | ContainerName string 27 | } 28 | 29 | func (r *Rule242389) ID() string { 30 | return ID242389 31 | } 32 | 33 | func (r *Rule242389) Name() string { 34 | return "The Kubernetes API server must have the secure port set." 35 | } 36 | 37 | func (r *Rule242389) Severity() rule.SeverityLevel { 38 | return rule.SeverityMedium 39 | } 40 | 41 | func (r *Rule242389) Run(ctx context.Context) (rule.RuleResult, error) { 42 | const option = "secure-port" 43 | deploymentName := "kube-apiserver" 44 | containerName := "kube-apiserver" 45 | 46 | if r.DeploymentName != "" { 47 | deploymentName = r.DeploymentName 48 | } 49 | 50 | if r.ContainerName != "" { 51 | containerName = r.ContainerName 52 | } 53 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 54 | 55 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 56 | if err != nil { 57 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 58 | } 59 | 60 | switch { 61 | case len(optSlice) == 0: 62 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 63 | case len(optSlice) > 1: 64 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 65 | case optSlice[0] == "0": 66 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", option), target)), nil 67 | default: 68 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", option), target)), nil 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /pkg/provider/gardener/ruleset/disak8sstig/options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package disak8sstig 6 | 7 | import ( 8 | "log/slog" 9 | 10 | "k8s.io/client-go/rest" 11 | ) 12 | 13 | // CreateOption is a function that acts on a [Ruleset] 14 | // and is used to construct such objects. 15 | type CreateOption func(*Ruleset) 16 | 17 | // WithVersion sets the version of a [Ruleset]. 18 | func WithVersion(version string) CreateOption { 19 | return func(r *Ruleset) { 20 | r.version = version 21 | } 22 | } 23 | 24 | // WithAdditionalOpsPodLabels sets the AdditionalOpsPodLabels of a [Ruleset]. 25 | func WithAdditionalOpsPodLabels(labels map[string]string) CreateOption { 26 | return func(r *Ruleset) { 27 | r.AdditionalOpsPodLabels = labels 28 | } 29 | } 30 | 31 | // WithShootConfig sets the ShootConfig of a [Ruleset]. 32 | func WithShootConfig(config *rest.Config) CreateOption { 33 | return func(r *Ruleset) { 34 | r.ShootConfig = config 35 | } 36 | } 37 | 38 | // WithSeedConfig sets the SeedConfig of a [Ruleset]. 39 | func WithSeedConfig(config *rest.Config) CreateOption { 40 | return func(r *Ruleset) { 41 | r.SeedConfig = config 42 | } 43 | } 44 | 45 | // WithShootNamespace sets the shootNamespace of a [Ruleset]. 46 | func WithShootNamespace(shootNamespace string) CreateOption { 47 | return func(r *Ruleset) { 48 | r.shootNamespace = shootNamespace 49 | } 50 | } 51 | 52 | // WithArgs sets the args of a [Ruleset]. 53 | func WithArgs(args Args) CreateOption { 54 | return func(r *Ruleset) { 55 | switch { 56 | case args.MaxRetries == nil: 57 | return 58 | case *args.MaxRetries < 0: 59 | panic("max retries should not be a negative number") 60 | default: 61 | r.args.MaxRetries = args.MaxRetries 62 | } 63 | } 64 | } 65 | 66 | // WithNumberOfWorkers sets the max number of Workers of a [Ruleset]. 67 | func WithNumberOfWorkers(numWorkers int) CreateOption { 68 | return func(r *Ruleset) { 69 | if numWorkers <= 0 { 70 | panic("number of workers should be a positive number") 71 | } 72 | r.numWorkers = numWorkers 73 | } 74 | } 75 | 76 | // WithLogger the logger of a [Ruleset]. 77 | func WithLogger(logger *slog.Logger) CreateOption { 78 | return func(r *Ruleset) { 79 | r.logger = logger 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /docs/development/getting-started.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | ### Core Diki packages 4 | 5 | - the [provider package](../../pkg/provider/) defines a `provider`. This can be anything that you can run `rulesets` and `rules` against, i.e. a kubernetes cluster, cloud provider account, etc. 6 | - the [ruleset package](../../pkg/ruleset/) defines a `ruleset`. A `ruleset` is a versioned combination of `rules`. 7 | - the [rule package](../../pkg/rule/) defines a `rule`. A `rule` is a concrete implementation of a requirement. 8 | - the [report package](../../pkg/report/) defines a `report`. A `report` is the output of a `diki` run. 9 | 10 | See the [provider specific documentation](../providers/). 11 | 12 | ## Running diki Locally 13 | 14 | This part will walk you through the process of running Diki against a local shoot cluster for development purposes. This guide uses the Gardener's local development setup. 15 | If you encounter difficulties, please open an issue so that we can make this process easier. 16 | 17 | ### Prerequisites 18 | 19 | Make sure that you have a running local Gardener setup with a created shoot cluster. The steps to complete this can be found [here](https://github.com/gardener/gardener/blob/master/docs/deployment/getting_started_locally.md). 20 | 21 | ### Diki configuration 22 | 23 | You can use the [example gardener configuration](../../example/config/gardener.yaml) for this run. You will need to modify the `provider.args` field with correct kubeconfigs and shoot name/ namespace. You can can find a guide on how to get the shoot's kubeconfig [here](https://github.com/gardener/gardener/blob/master/docs/deployment/getting_started_locally.md). 24 | 25 | ### Diki run 26 | 27 | To run Diki you can use the [run script](../../hack/run.sh). It will use default ldflags flags or you can configure them by setting the `LD_FLAGS` env var. You will need to set the `IMAGEVECTOR_OVERWRITE` env var to overwrite the [images.yaml](../../imagevector/images.yaml) file to a file that specifies the version of the `diki-ops` image or change it's repository. 28 | 29 | After creating the overwrite images file you can run diki as follows: 30 | ```bash 31 | IMAGEVECTOR_OVERWRITE=/path/to/images/file ./hack/run.sh --config=/path/to/config/file 32 | ``` 33 | 34 | For more information about the script you can use it's help command: 35 | ```bash 36 | ./hack/run.sh --help 37 | ``` 38 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242421.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strings" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242421{} 20 | _ rule.Severity = &Rule242421{} 21 | ) 22 | 23 | type Rule242421 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242421) ID() string { 31 | return ID242421 32 | } 33 | 34 | func (r *Rule242421) Name() string { 35 | return "Kubernetes Controller Manager must have the SSL Certificate Authority set." 36 | } 37 | 38 | func (r *Rule242421) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242421) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "root-ca-file" 44 | deploymentName := "kube-controller-manager" 45 | containerName := "kube-controller-manager" 46 | 47 | if r.DeploymentName != "" { 48 | deploymentName = r.DeploymentName 49 | } 50 | 51 | if r.ContainerName != "" { 52 | containerName = r.ContainerName 53 | } 54 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 55 | 56 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 57 | if err != nil { 58 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 59 | } 60 | 61 | switch { 62 | case len(optSlice) == 0: 63 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 64 | case len(optSlice) > 1: 65 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 66 | case strings.TrimSpace(optSlice[0]) == "": 67 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s is empty.", option), target)), nil 68 | default: 69 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set.", option), target)), nil 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242461.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strings" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242461{} 20 | _ rule.Severity = &Rule242461{} 21 | ) 22 | 23 | type Rule242461 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242461) ID() string { 31 | return ID242461 32 | } 33 | 34 | func (r *Rule242461) Name() string { 35 | return "The Kubernetes API Server audit logs must be enabled." 36 | } 37 | 38 | func (r *Rule242461) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242461) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "audit-policy-file" 44 | deploymentName := "kube-apiserver" 45 | containerName := "kube-apiserver" 46 | 47 | if r.DeploymentName != "" { 48 | deploymentName = r.DeploymentName 49 | } 50 | 51 | if r.ContainerName != "" { 52 | containerName = r.ContainerName 53 | } 54 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 55 | 56 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 57 | if err != nil { 58 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 59 | } 60 | 61 | // setting option is required 62 | switch { 63 | case len(optSlice) == 0: 64 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 65 | case len(optSlice) > 1: 66 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 67 | case strings.TrimSpace(optSlice[0]) == "": 68 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s is empty.", option), target)), nil 69 | default: 70 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set.", option), target)), nil 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242402.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strings" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242402{} 20 | _ rule.Severity = &Rule242402{} 21 | ) 22 | 23 | type Rule242402 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242402) ID() string { 31 | return ID242402 32 | } 33 | 34 | func (r *Rule242402) Name() string { 35 | return "The Kubernetes API Server must have an audit log path set." 36 | } 37 | 38 | func (r *Rule242402) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242402) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "audit-log-path" 44 | deploymentName := "kube-apiserver" 45 | containerName := "kube-apiserver" 46 | 47 | if r.DeploymentName != "" { 48 | deploymentName = r.DeploymentName 49 | } 50 | 51 | if r.ContainerName != "" { 52 | containerName = r.ContainerName 53 | } 54 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 55 | 56 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 57 | if err != nil { 58 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 59 | } 60 | 61 | // setting option is required 62 | switch { 63 | case len(optSlice) == 0: 64 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 65 | case len(optSlice) > 1: 66 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 67 | case strings.TrimSpace(optSlice[0]) == "": 68 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s is empty.", option), target)), nil 69 | default: 70 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set.", option), target)), nil 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242429.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strings" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242429{} 20 | _ rule.Severity = &Rule242429{} 21 | ) 22 | 23 | type Rule242429 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242429) ID() string { 31 | return ID242429 32 | } 33 | 34 | func (r *Rule242429) Name() string { 35 | return "Kubernetes etcd must have the SSL Certificate Authority set." 36 | } 37 | 38 | func (r *Rule242429) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242429) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "etcd-cafile" 44 | deploymentName := "kube-apiserver" 45 | containerName := "kube-apiserver" 46 | 47 | if r.DeploymentName != "" { 48 | deploymentName = r.DeploymentName 49 | } 50 | 51 | if r.ContainerName != "" { 52 | containerName = r.ContainerName 53 | } 54 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 55 | 56 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 57 | if err != nil { 58 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 59 | } 60 | 61 | // setting option is required 62 | switch { 63 | case len(optSlice) == 0: 64 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 65 | case len(optSlice) > 1: 66 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 67 | case strings.TrimSpace(optSlice[0]) == "": 68 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s is empty.", option), target)), nil 69 | default: 70 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set.", option), target)), nil 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242430.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strings" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242430{} 20 | _ rule.Severity = &Rule242430{} 21 | ) 22 | 23 | type Rule242430 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242430) ID() string { 31 | return ID242430 32 | } 33 | 34 | func (r *Rule242430) Name() string { 35 | return "Kubernetes etcd must have a certificate for communication." 36 | } 37 | 38 | func (r *Rule242430) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242430) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "etcd-certfile" 44 | deploymentName := "kube-apiserver" 45 | containerName := "kube-apiserver" 46 | 47 | if r.DeploymentName != "" { 48 | deploymentName = r.DeploymentName 49 | } 50 | 51 | if r.ContainerName != "" { 52 | containerName = r.ContainerName 53 | } 54 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 55 | 56 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 57 | if err != nil { 58 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 59 | } 60 | 61 | // setting option is required 62 | switch { 63 | case len(optSlice) == 0: 64 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 65 | case len(optSlice) > 1: 66 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 67 | case strings.TrimSpace(optSlice[0]) == "": 68 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s is empty.", option), target)), nil 69 | default: 70 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set.", option), target)), nil 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242419.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strings" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242419{} 20 | _ rule.Severity = &Rule242419{} 21 | ) 22 | 23 | type Rule242419 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242419) ID() string { 31 | return ID242419 32 | } 33 | 34 | func (r *Rule242419) Name() string { 35 | return "Kubernetes API Server must have the SSL Certificate Authority set." 36 | } 37 | 38 | func (r *Rule242419) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242419) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "client-ca-file" 44 | deploymentName := "kube-apiserver" 45 | containerName := "kube-apiserver" 46 | 47 | if r.DeploymentName != "" { 48 | deploymentName = r.DeploymentName 49 | } 50 | 51 | if r.ContainerName != "" { 52 | containerName = r.ContainerName 53 | } 54 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 55 | 56 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 57 | if err != nil { 58 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 59 | } 60 | 61 | // setting option is required 62 | switch { 63 | case len(optSlice) == 0: 64 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 65 | case len(optSlice) > 1: 66 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 67 | case strings.TrimSpace(optSlice[0]) == "": 68 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s is empty.", option), target)), nil 69 | default: 70 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set.", option), target)), nil 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242431.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strings" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242431{} 20 | _ rule.Severity = &Rule242431{} 21 | ) 22 | 23 | type Rule242431 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242431) ID() string { 31 | return ID242431 32 | } 33 | 34 | func (r *Rule242431) Name() string { 35 | return "Kubernetes etcd must have a key file for secure communication." 36 | } 37 | 38 | func (r *Rule242431) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242431) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "etcd-keyfile" 44 | deploymentName := "kube-apiserver" 45 | containerName := "kube-apiserver" 46 | 47 | if r.DeploymentName != "" { 48 | deploymentName = r.DeploymentName 49 | } 50 | 51 | if r.ContainerName != "" { 52 | containerName = r.ContainerName 53 | } 54 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 55 | 56 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 57 | if err != nil { 58 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 59 | } 60 | 61 | // setting option is required 62 | 63 | switch { 64 | case len(optSlice) == 0: 65 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 66 | case len(optSlice) > 1: 67 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 68 | case strings.TrimSpace(optSlice[0]) == "": 69 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s is empty.", option), target)), nil 70 | default: 71 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set.", option), target)), nil 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pkg/provider/gardener/ruleset/disak8sstig/rules/242377.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "slices" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | sharedrules "github.com/gardener/diki/pkg/shared/ruleset/disak8sstig/rules" 17 | ) 18 | 19 | var ( 20 | _ rule.Rule = &Rule242377{} 21 | _ rule.Severity = &Rule242377{} 22 | ) 23 | 24 | type Rule242377 struct { 25 | Client client.Client 26 | Namespace string 27 | } 28 | 29 | func (r *Rule242377) ID() string { 30 | return sharedrules.ID242377 31 | } 32 | 33 | func (r *Rule242377) Name() string { 34 | return "Kubernetes Scheduler must use TLS 1.2, at a minimum, to protect the confidentiality of sensitive data during electronic dissemination." 35 | } 36 | 37 | func (r *Rule242377) Severity() rule.SeverityLevel { 38 | return rule.SeverityMedium 39 | } 40 | 41 | func (r *Rule242377) Run(ctx context.Context) (rule.RuleResult, error) { 42 | const ( 43 | ksName = "kube-scheduler" 44 | option = "tls-min-version" 45 | ) 46 | target := rule.NewTarget("cluster", "seed", "name", ksName, "namespace", r.Namespace, "kind", "Deployment") 47 | 48 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, ksName, ksName, r.Namespace, option) 49 | if err != nil { 50 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 51 | } 52 | 53 | // empty options are allowed because min version defaults to TLS 1.2 54 | switch { 55 | case len(optSlice) == 0: 56 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 57 | case len(optSlice) > 1: 58 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 59 | case slices.Contains([]string{"VersionTLS10", "VersionTLS11"}, optSlice[0]): 60 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", option), target)), nil 61 | default: 62 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", option), target)), nil 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242397.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/client-go/rest" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | 15 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 16 | "github.com/gardener/diki/pkg/rule" 17 | ) 18 | 19 | var ( 20 | _ rule.Rule = &Rule242397{} 21 | _ rule.Severity = &Rule242397{} 22 | ) 23 | 24 | type Rule242397 struct { 25 | Client client.Client 26 | V1RESTClient rest.Interface 27 | } 28 | 29 | func (r *Rule242397) ID() string { 30 | return ID242397 31 | } 32 | 33 | func (r *Rule242397) Name() string { 34 | return "The Kubernetes kubelet staticPodPath must not enable static pods." 35 | } 36 | 37 | func (r *Rule242397) Severity() rule.SeverityLevel { 38 | return rule.SeverityHigh 39 | } 40 | 41 | func (r *Rule242397) Run(ctx context.Context) (rule.RuleResult, error) { 42 | var checkResults []rule.CheckResult 43 | 44 | nodes, err := kubeutils.GetNodes(ctx, r.Client, 300) 45 | if err != nil { 46 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), rule.NewTarget("kind", "NodeList"))), nil 47 | } 48 | 49 | if len(nodes) == 0 { 50 | return rule.Result(r, rule.WarningCheckResult("No nodes found.", rule.NewTarget())), nil 51 | } 52 | 53 | const staticPodPathConfigOption = "staticPodPath" 54 | for _, node := range nodes { 55 | target := kubeutils.TargetWithK8sObject(rule.NewTarget(), metav1.TypeMeta{Kind: "Node"}, node.ObjectMeta) 56 | if !kubeutils.NodeReadyStatus(node) { 57 | checkResults = append(checkResults, rule.WarningCheckResult("Node is not in Ready state.", target)) 58 | continue 59 | } 60 | 61 | kubeletConfig, err := kubeutils.GetNodeConfigz(ctx, r.V1RESTClient, node.Name) 62 | if err != nil { 63 | checkResults = append(checkResults, rule.ErroredCheckResult(err.Error(), target)) 64 | continue 65 | } 66 | 67 | if kubeletConfig.StaticPodPath == nil { 68 | checkResults = append(checkResults, rule.PassedCheckResult(fmt.Sprintf("Option %s not set.", staticPodPathConfigOption), target)) 69 | } else { 70 | checkResults = append(checkResults, rule.FailedCheckResult(fmt.Sprintf("Option %s set.", staticPodPathConfigOption), target)) 71 | } 72 | } 73 | 74 | return rule.Result(r, checkResults...), nil 75 | } 76 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242409.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | 13 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 14 | "github.com/gardener/diki/pkg/rule" 15 | ) 16 | 17 | var ( 18 | _ rule.Rule = &Rule242409{} 19 | _ rule.Severity = &Rule242409{} 20 | ) 21 | 22 | type Rule242409 struct { 23 | Client client.Client 24 | Namespace string 25 | DeploymentName string 26 | ContainerName string 27 | } 28 | 29 | func (r *Rule242409) ID() string { 30 | return ID242409 31 | } 32 | 33 | func (r *Rule242409) Name() string { 34 | return "Kubernetes Controller Manager must disable profiling." 35 | } 36 | 37 | func (r *Rule242409) Severity() rule.SeverityLevel { 38 | return rule.SeverityMedium 39 | } 40 | 41 | func (r *Rule242409) Run(ctx context.Context) (rule.RuleResult, error) { 42 | const option = "profiling" 43 | deploymentName := "kube-controller-manager" 44 | containerName := "kube-controller-manager" 45 | 46 | if r.DeploymentName != "" { 47 | deploymentName = r.DeploymentName 48 | } 49 | 50 | if r.ContainerName != "" { 51 | containerName = r.ContainerName 52 | } 53 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 54 | 55 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 56 | if err != nil { 57 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 58 | } 59 | 60 | switch { 61 | case len(optSlice) == 0: 62 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 63 | case len(optSlice) > 1: 64 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 65 | case optSlice[0] == "true": 66 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", option), target)), nil 67 | case optSlice[0] == "false": 68 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", option), target)), nil 69 | default: 70 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s set to neither 'true' nor 'false'.", option), target)), nil 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242464.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strconv" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242464{} 20 | _ rule.Severity = &Rule242464{} 21 | ) 22 | 23 | type Rule242464 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242464) ID() string { 31 | return ID242464 32 | } 33 | 34 | func (r *Rule242464) Name() string { 35 | return "The Kubernetes API Server audit log retention must be set." 36 | } 37 | 38 | func (r *Rule242464) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242464) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "audit-log-maxage" 44 | deploymentName := "kube-apiserver" 45 | containerName := "kube-apiserver" 46 | 47 | if r.DeploymentName != "" { 48 | deploymentName = r.DeploymentName 49 | } 50 | 51 | if r.ContainerName != "" { 52 | containerName = r.ContainerName 53 | } 54 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 55 | 56 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 57 | if err != nil { 58 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 59 | } 60 | 61 | if len(optSlice) == 0 { 62 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 63 | } 64 | 65 | if len(optSlice) > 1 { 66 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 67 | } 68 | 69 | auditLogMaxAge, err := strconv.ParseInt(optSlice[0], 10, 0) 70 | if err != nil { 71 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 72 | } 73 | 74 | if auditLogMaxAge < 30 { 75 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", option), target)), nil 76 | } 77 | 78 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", option), target)), nil 79 | } 80 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242462.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strconv" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242462{} 20 | _ rule.Severity = &Rule242462{} 21 | ) 22 | 23 | type Rule242462 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242462) ID() string { 31 | return ID242462 32 | } 33 | 34 | func (r *Rule242462) Name() string { 35 | return "The Kubernetes API Server must be set to audit log max size." 36 | } 37 | 38 | func (r *Rule242462) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242462) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "audit-log-maxsize" 44 | deploymentName := "kube-apiserver" 45 | containerName := "kube-apiserver" 46 | 47 | if r.DeploymentName != "" { 48 | deploymentName = r.DeploymentName 49 | } 50 | 51 | if r.ContainerName != "" { 52 | containerName = r.ContainerName 53 | } 54 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 55 | 56 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 57 | if err != nil { 58 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 59 | } 60 | 61 | if len(optSlice) == 0 { 62 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 63 | } 64 | 65 | if len(optSlice) > 1 { 66 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 67 | } 68 | 69 | auditLogMaxSize, err := strconv.ParseInt(optSlice[0], 10, 0) 70 | if err != nil { 71 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 72 | } 73 | 74 | if auditLogMaxSize < 100 { 75 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", option), target)), nil 76 | } 77 | 78 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", option), target)), nil 79 | } 80 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242463.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strconv" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242463{} 20 | _ rule.Severity = &Rule242463{} 21 | ) 22 | 23 | type Rule242463 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242463) ID() string { 31 | return ID242463 32 | } 33 | 34 | func (r *Rule242463) Name() string { 35 | return "The Kubernetes API Server must be set to audit log maximum backup." 36 | } 37 | 38 | func (r *Rule242463) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242463) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "audit-log-maxbackup" 44 | deploymentName := "kube-apiserver" 45 | containerName := "kube-apiserver" 46 | 47 | if r.DeploymentName != "" { 48 | deploymentName = r.DeploymentName 49 | } 50 | 51 | if r.ContainerName != "" { 52 | containerName = r.ContainerName 53 | } 54 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 55 | 56 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 57 | if err != nil { 58 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 59 | } 60 | 61 | if len(optSlice) == 0 { 62 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 63 | } 64 | if len(optSlice) > 1 { 65 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 66 | } 67 | 68 | auditLogMaxBackup, err := strconv.ParseInt(optSlice[0], 10, 0) 69 | if err != nil { 70 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 71 | } 72 | 73 | if auditLogMaxBackup < 10 { 74 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", option), target)), nil 75 | } 76 | 77 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", option), target)), nil 78 | } 79 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242381.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | 13 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 14 | "github.com/gardener/diki/pkg/rule" 15 | ) 16 | 17 | var ( 18 | _ rule.Rule = &Rule242381{} 19 | _ rule.Severity = &Rule242381{} 20 | ) 21 | 22 | type Rule242381 struct { 23 | Client client.Client 24 | Namespace string 25 | DeploymentName string 26 | ContainerName string 27 | } 28 | 29 | func (r *Rule242381) ID() string { 30 | return ID242381 31 | } 32 | 33 | func (r *Rule242381) Name() string { 34 | return "The Kubernetes Controller Manager must create unique service accounts for each work payload." 35 | } 36 | 37 | func (r *Rule242381) Severity() rule.SeverityLevel { 38 | return rule.SeverityHigh 39 | } 40 | 41 | func (r *Rule242381) Run(ctx context.Context) (rule.RuleResult, error) { 42 | const option = "use-service-account-credentials" 43 | deploymentName := "kube-controller-manager" 44 | containerName := "kube-controller-manager" 45 | 46 | if r.DeploymentName != "" { 47 | deploymentName = r.DeploymentName 48 | } 49 | 50 | if r.ContainerName != "" { 51 | containerName = r.ContainerName 52 | } 53 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 54 | 55 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 56 | if err != nil { 57 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 58 | } 59 | 60 | switch { 61 | case len(optSlice) == 0: 62 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 63 | case len(optSlice) > 1: 64 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 65 | case optSlice[0] == "false": 66 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", option), target)), nil 67 | case optSlice[0] == "true": 68 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", option), target)), nil 69 | default: 70 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s set to neither 'true' nor 'false'.", option), target)), nil 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pkg/kubernetes/config/kubelet.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package config 6 | 7 | // NodeConfigz contains the runtime kubelet config. 8 | type NodeConfigz struct { 9 | KubeletConfig KubeletConfig `yaml:"kubeletconfig" json:"kubeletconfig"` 10 | } 11 | 12 | // KubeletConfig describes kubelet configuration values. 13 | type KubeletConfig struct { 14 | Authentication KubeletAuthentication `yaml:"authentication" json:"authentication"` 15 | Authorization KubeletAuthorization `yaml:"authorization" json:"authorization"` 16 | MaxPods *int32 `yaml:"maxPods" json:"maxPods"` 17 | ReadOnlyPort *int32 `yaml:"readOnlyPort" json:"readOnlyPort"` 18 | ServerTLSBootstrap *bool `yaml:"serverTLSBootstrap" json:"serverTLSBootstrap"` 19 | StaticPodPath *string `yaml:"staticPodPath" json:"staticPodPath"` 20 | TLSPrivateKeyFile *string `yaml:"tlsPrivateKeyFile" json:"tlsPrivateKeyFile"` 21 | TLSCertFile *string `yaml:"tlsCertFile" json:"tlsCertFile"` 22 | FeatureGates map[string]bool `yaml:"featureGates" json:"featureGates"` 23 | ProtectKernelDefaults *bool `yaml:"protectKernelDefaults" json:"protectKernelDefaults"` 24 | StreamingConnectionIdleTimeout *string `yaml:"streamingConnectionIdleTimeout" json:"streamingConnectionIdleTimeout"` 25 | } 26 | 27 | // KubeletAuthentication describes kubelet configuration values for authentication mechanisms. 28 | type KubeletAuthentication struct { 29 | Anonymous KubeletAnonymousAuthentication `yaml:"anonymous" json:"anonymous"` 30 | X509 KubeletX509Authentication `yaml:"x509" json:"x509"` 31 | } 32 | 33 | // KubeletAnonymousAuthentication describes kubelet configuration values for anonymous authentication. 34 | type KubeletAnonymousAuthentication struct { 35 | Enabled *bool `yaml:"enabled" json:"enabled"` 36 | } 37 | 38 | // KubeletX509Authentication describes kubelet configuration values for x509 client certificate authentication. 39 | type KubeletX509Authentication struct { 40 | ClientCAFile *string `yaml:"clientCAFile" json:"clientCAFile"` 41 | } 42 | 43 | // KubeletAuthorization describes kubelet configuration values for authorization mechanisms. 44 | type KubeletAuthorization struct { 45 | Mode *string `yaml:"mode" json:"mode"` 46 | } 47 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | ":semanticCommitsDisabled", 6 | "regexManagers:githubActionsVersions", 7 | "group:monorepos" 8 | ], 9 | "labels": ["kind/enhancement"], 10 | "postUpdateOptions": ["gomodTidy"], 11 | "automergeStrategy": "squash", 12 | "customManagers": [ 13 | { 14 | // Generic detection for pod-like image specifications. 15 | "customType": "regex", 16 | "fileMatch": ["^\.ci\/pipeline_definitions$"], 17 | "matchStrings": ["image: ['\"]?(?.*?):(?.*?)['\"]?\\s"], 18 | "datasourceTemplate": "docker" 19 | }, 20 | { 21 | // Generic detection for go version in github-actions. 22 | "customType": "regex", 23 | "fileMatch": ["^\.github\/workflows\/upload-diki-binaries.yaml$"], 24 | "matchStrings": ["go-version: '(?.*?)'\\s"], 25 | "depNameTemplate": "golang", 26 | "datasourceTemplate": "docker" 27 | }, 28 | { 29 | // Generic detection for nerdctl binary in Dockerfile. 30 | "customType": "regex", 31 | "fileMatch": ["^Dockerfile$"], 32 | "matchStrings": [ 33 | "https:\/\/github\\.com\/containerd\/nerdctl\/releases\/download\/.*?\/nerdctl-(?.*?)-linux", 34 | ], 35 | "depNameTemplate": "containerd/nerdctl", 36 | "datasourceTemplate": "github-releases" 37 | } 38 | ], 39 | "packageRules": [ 40 | { 41 | // Group golang updates in one PR. 42 | "groupName": "golang", 43 | "matchDatasources": ["docker", "go", "golang-version"], 44 | "matchPackageNames": ["go", "golang"], 45 | }, 46 | { 47 | // Update only patchlevels of major dependencies like kubernetes and controller-runtime. 48 | // Minor and major upgrades most likely require manual adaptations of the code. 49 | "matchDatasources": ["go"], 50 | "matchUpdateTypes": ["major", "minor"], 51 | "matchPackagePatterns": [ 52 | "k8s\\.io\/.+", 53 | "sigs\\.k8s\\.io\/controller-runtime", 54 | ], 55 | "enabled": false 56 | }, 57 | { 58 | // Ignore dependency updates from k8s.io/kube-openapi because it depends on k8s.io/apiserver. 59 | "matchDatasources": ["go"], 60 | "matchPackagePatterns": ["k8s\\.io\/kube-openapi"], 61 | "enabled": false 62 | }, 63 | { 64 | // Group github-actions minor updates in one PR 65 | "matchManagers": ["github-actions"], 66 | "groupName": "github-actions" 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /pkg/rule/helpers.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rule 6 | 7 | // Result returns a [RuleResult] containing the passed checks. 8 | func Result(r Rule, checkResults ...CheckResult) RuleResult { 9 | result := RuleResult{ 10 | RuleID: r.ID(), 11 | RuleName: r.Name(), 12 | CheckResults: checkResults, 13 | } 14 | 15 | if severity, ok := r.(Severity); ok { 16 | result.Severity = severity.Severity() 17 | } 18 | return result 19 | } 20 | 21 | // PassedCheckResult returns a [CheckResult] with Passed status and the given message and target 22 | func PassedCheckResult(message string, target Target) CheckResult { 23 | return CheckResult{ 24 | Status: Passed, 25 | Message: message, 26 | Target: target, 27 | } 28 | } 29 | 30 | // FailedCheckResult returns a [CheckResult] with Failed status and the given message and target 31 | func FailedCheckResult(message string, target Target) CheckResult { 32 | return CheckResult{ 33 | Status: Failed, 34 | Message: message, 35 | Target: target, 36 | } 37 | } 38 | 39 | // WarningCheckResult returns a [CheckResult] with Warning status and the given message and target 40 | func WarningCheckResult(message string, target Target) CheckResult { 41 | return CheckResult{ 42 | Status: Warning, 43 | Message: message, 44 | Target: target, 45 | } 46 | } 47 | 48 | // ErroredCheckResult returns a [CheckResult] with Errored status and the given message and target 49 | func ErroredCheckResult(message string, target Target) CheckResult { 50 | return CheckResult{ 51 | Status: Errored, 52 | Message: message, 53 | Target: target, 54 | } 55 | } 56 | 57 | // NotImplementedCheckResult returns a [CheckResult] with v status and the given message and target 58 | func NotImplementedCheckResult(message string, target Target) CheckResult { 59 | return CheckResult{ 60 | Status: NotImplemented, 61 | Message: message, 62 | Target: target, 63 | } 64 | } 65 | 66 | // SkippedCheckResult returns a [CheckResult] with Skipped status and the given message and target 67 | func SkippedCheckResult(message string, target Target) CheckResult { 68 | return CheckResult{ 69 | Status: Skipped, 70 | Message: message, 71 | Target: target, 72 | } 73 | } 74 | 75 | // AcceptedCheckResult returns a [CheckResult] with Accepted status and the given message and target 76 | func AcceptedCheckResult(message string, target Target) CheckResult { 77 | return CheckResult{ 78 | Status: Accepted, 79 | Message: message, 80 | Target: target, 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242438.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "time" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242438{} 20 | _ rule.Severity = &Rule242438{} 21 | ) 22 | 23 | type Rule242438 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242438) ID() string { 31 | return ID242438 32 | } 33 | 34 | func (r *Rule242438) Name() string { 35 | return "Kubernetes API Server must configure timeouts to limit attack surface." 36 | } 37 | 38 | func (r *Rule242438) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242438) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "request-timeout" 44 | deploymentName := "kube-apiserver" 45 | containerName := "kube-apiserver" 46 | 47 | if r.DeploymentName != "" { 48 | deploymentName = r.DeploymentName 49 | } 50 | 51 | if r.ContainerName != "" { 52 | containerName = r.ContainerName 53 | } 54 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 55 | 56 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 57 | if err != nil { 58 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 59 | } 60 | 61 | // if not set defaults to allowed value https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/ 62 | if len(optSlice) == 0 { 63 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s has not been set.", option), target.With("details", "defaults to 1m0s"))), nil 64 | } 65 | 66 | if len(optSlice) > 1 { 67 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 68 | } 69 | 70 | duration, err := time.ParseDuration(optSlice[0]) 71 | if err != nil { 72 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 73 | } 74 | 75 | if duration <= 0 { 76 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", option), target)), nil 77 | } 78 | 79 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", option), target)), nil 80 | } 81 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/ids.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | const ( 8 | ID242376 = "242376" 9 | ID242377 = "242377" 10 | ID242378 = "242378" 11 | ID242379 = "242379" 12 | ID242380 = "242380" 13 | ID242381 = "242381" 14 | ID242382 = "242382" 15 | ID242383 = "242383" 16 | ID242384 = "242384" 17 | ID242385 = "242385" 18 | ID242386 = "242386" 19 | ID242387 = "242387" 20 | ID242388 = "242388" 21 | ID242389 = "242389" 22 | ID242390 = "242390" 23 | ID242391 = "242391" 24 | ID242392 = "242392" 25 | ID242393 = "242393" 26 | ID242394 = "242394" 27 | ID242395 = "242395" 28 | ID242396 = "242396" 29 | ID242397 = "242397" 30 | ID242398 = "242398" 31 | ID242399 = "242399" 32 | ID242400 = "242400" 33 | ID242402 = "242402" 34 | ID242403 = "242403" 35 | ID242404 = "242404" 36 | ID242405 = "242405" 37 | ID242406 = "242406" 38 | ID242407 = "242407" 39 | ID242408 = "242408" 40 | ID242409 = "242409" 41 | ID242410 = "242410" 42 | ID242411 = "242411" 43 | ID242412 = "242412" 44 | ID242413 = "242413" 45 | ID242414 = "242414" 46 | ID242415 = "242415" 47 | ID242417 = "242417" 48 | ID242418 = "242418" 49 | ID242419 = "242419" 50 | ID242420 = "242420" 51 | ID242421 = "242421" 52 | ID242422 = "242422" 53 | ID242423 = "242423" 54 | ID242424 = "242424" 55 | ID242425 = "242425" 56 | ID242426 = "242426" 57 | ID242427 = "242427" 58 | ID242428 = "242428" 59 | ID242429 = "242429" 60 | ID242430 = "242430" 61 | ID242431 = "242431" 62 | ID242432 = "242432" 63 | ID242433 = "242433" 64 | ID242434 = "242434" 65 | ID242436 = "242436" 66 | ID242437 = "242437" 67 | ID242438 = "242438" 68 | ID242442 = "242442" 69 | ID242443 = "242443" 70 | ID242444 = "242444" 71 | ID242445 = "242445" 72 | ID242446 = "242446" 73 | ID242447 = "242447" 74 | ID242448 = "242448" 75 | ID242449 = "242449" 76 | ID242450 = "242450" 77 | ID242451 = "242451" 78 | ID242452 = "242452" 79 | ID242453 = "242453" 80 | ID242454 = "242454" 81 | ID242455 = "242455" 82 | ID242456 = "242456" 83 | ID242457 = "242457" 84 | ID242459 = "242459" 85 | ID242460 = "242460" 86 | ID242461 = "242461" 87 | ID242462 = "242462" 88 | ID242463 = "242463" 89 | ID242464 = "242464" 90 | ID242465 = "242465" 91 | ID242466 = "242466" 92 | ID242467 = "242467" 93 | ID245541 = "245541" 94 | ID245542 = "245542" 95 | ID245543 = "245543" 96 | ID245544 = "245544" 97 | ID254800 = "254800" 98 | ID254801 = "254801" 99 | ID274882 = "274882" 100 | ID274883 = "274883" 101 | ID274884 = "274884" 102 | ) 103 | -------------------------------------------------------------------------------- /pkg/kubernetes/pod/fake/pod.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package fake 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | 11 | corev1 "k8s.io/api/core/v1" 12 | 13 | "github.com/gardener/diki/pkg/kubernetes/pod" 14 | ) 15 | 16 | // FakeSimplePodContext is used to fake the work of SimplePodContext. 17 | type FakeSimplePodContext struct { 18 | executeReturnString [][]string 19 | executeReturnError [][]error 20 | createCount int 21 | } 22 | 23 | // NewFakeSimplePodContext creates a new FakeSimplePodContext. 24 | func NewFakeSimplePodContext(executeReturnString [][]string, executeReturnError [][]error) *FakeSimplePodContext { 25 | return &FakeSimplePodContext{ 26 | executeReturnString: executeReturnString, 27 | executeReturnError: executeReturnError, 28 | } 29 | } 30 | 31 | // Create returns the preset values. 32 | func (mspc *FakeSimplePodContext) Create(_ context.Context, _ func() *corev1.Pod) (pod.PodExecutor, error) { 33 | if mspc.createCount >= len(mspc.executeReturnString) { 34 | return nil, errors.New("not enough return strings have been faked") 35 | } 36 | if mspc.createCount >= len(mspc.executeReturnError) { 37 | return nil, errors.New("not enough return errors have been faked") 38 | } 39 | mspc.createCount++ 40 | return NewFakePodExecutor(mspc.executeReturnString[mspc.createCount-1], mspc.executeReturnError[mspc.createCount-1]), nil 41 | } 42 | 43 | // Delete always returns nil. 44 | func (mspc *FakeSimplePodContext) Delete(_ context.Context, _, _ string) error { 45 | return nil 46 | } 47 | 48 | // FakePodExecutor is used to fake the work of PodExecutor. 49 | type FakePodExecutor struct { 50 | executeReturnString []string 51 | executeReturnError []error 52 | executeCount int 53 | } 54 | 55 | // NewFakePodExecutor creates a new FakePodExecutor. 56 | func NewFakePodExecutor(executeReturnString []string, executeReturnError []error) *FakePodExecutor { 57 | return &FakePodExecutor{ 58 | executeReturnString: executeReturnString, 59 | executeReturnError: executeReturnError, 60 | executeCount: 0, 61 | } 62 | } 63 | 64 | // Execute returns the preset values. 65 | func (mpe *FakePodExecutor) Execute(_ context.Context, _ string, _ string) (string, error) { 66 | if mpe.executeCount >= len(mpe.executeReturnString) { 67 | return "", errors.New("not enough return strings have been faked") 68 | } 69 | if mpe.executeCount >= len(mpe.executeReturnError) { 70 | return "", errors.New("not enough return errors have been faked") 71 | } 72 | mpe.executeCount++ 73 | return mpe.executeReturnString[mpe.executeCount-1], mpe.executeReturnError[mpe.executeCount-1] 74 | } 75 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242378.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "slices" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242378{} 20 | _ rule.Severity = &Rule242378{} 21 | ) 22 | 23 | type Rule242378 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242378) ID() string { 31 | return ID242378 32 | } 33 | 34 | func (r *Rule242378) Name() string { 35 | return "The Kubernetes API Server must use TLS 1.2, at a minimum, to protect the confidentiality of sensitive data during electronic dissemination." 36 | } 37 | 38 | func (r *Rule242378) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242378) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "tls-min-version" 44 | 45 | deploymentName := "kube-apiserver" 46 | containerName := "kube-apiserver" 47 | 48 | if r.DeploymentName != "" { 49 | deploymentName = r.DeploymentName 50 | } 51 | 52 | if r.ContainerName != "" { 53 | containerName = r.ContainerName 54 | } 55 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 56 | 57 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 58 | if err != nil { 59 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 60 | } 61 | 62 | // empty options are allowed because min version defaults to TLS 1.2 63 | switch { 64 | case len(optSlice) == 0: 65 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 66 | case len(optSlice) > 1: 67 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 68 | case slices.Contains([]string{"VersionTLS10", "VersionTLS11"}, optSlice[0]): 69 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", option), target)), nil 70 | case slices.Contains([]string{"VersionTLS12", "VersionTLS13"}, optSlice[0]): 71 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", option), target)), nil 72 | default: 73 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set to unknown value.", option), target)), nil 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242391.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/client-go/rest" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | 15 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 16 | "github.com/gardener/diki/pkg/rule" 17 | ) 18 | 19 | var ( 20 | _ rule.Rule = &Rule242391{} 21 | _ rule.Severity = &Rule242391{} 22 | ) 23 | 24 | type Rule242391 struct { 25 | Client client.Client 26 | V1RESTClient rest.Interface 27 | } 28 | 29 | func (r *Rule242391) ID() string { 30 | return ID242391 31 | } 32 | 33 | func (r *Rule242391) Name() string { 34 | return "The Kubernetes Kubelet must have anonymous authentication disabled." 35 | } 36 | 37 | func (r *Rule242391) Severity() rule.SeverityLevel { 38 | return rule.SeverityHigh 39 | } 40 | 41 | func (r *Rule242391) Run(ctx context.Context) (rule.RuleResult, error) { 42 | var checkResults []rule.CheckResult 43 | 44 | nodes, err := kubeutils.GetNodes(ctx, r.Client, 300) 45 | if err != nil { 46 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), rule.NewTarget("kind", "NodeList"))), nil 47 | } 48 | 49 | if len(nodes) == 0 { 50 | return rule.Result(r, rule.WarningCheckResult("No nodes found.", rule.NewTarget())), nil 51 | } 52 | 53 | const anonymousAuthConfigOption = "authentication.anonymous.enabled" 54 | for _, node := range nodes { 55 | target := kubeutils.TargetWithK8sObject(rule.NewTarget(), metav1.TypeMeta{Kind: "Node"}, node.ObjectMeta) 56 | if !kubeutils.NodeReadyStatus(node) { 57 | checkResults = append(checkResults, rule.WarningCheckResult("Node is not in Ready state.", target)) 58 | continue 59 | } 60 | 61 | kubeletConfig, err := kubeutils.GetNodeConfigz(ctx, r.V1RESTClient, node.Name) 62 | if err != nil { 63 | checkResults = append(checkResults, rule.ErroredCheckResult(err.Error(), target)) 64 | continue 65 | } 66 | 67 | switch { 68 | case kubeletConfig.Authentication.Anonymous.Enabled == nil: 69 | checkResults = append(checkResults, rule.FailedCheckResult(fmt.Sprintf("Option %s not set.", anonymousAuthConfigOption), target)) 70 | case *kubeletConfig.Authentication.Anonymous.Enabled: 71 | checkResults = append(checkResults, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", anonymousAuthConfigOption), target)) 72 | default: 73 | checkResults = append(checkResults, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", anonymousAuthConfigOption), target)) 74 | } 75 | } 76 | 77 | return rule.Result(r, checkResults...), nil 78 | } 79 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242395_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules_test 6 | 7 | import ( 8 | "context" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | corev1 "k8s.io/api/core/v1" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" 16 | 17 | "github.com/gardener/diki/pkg/rule" 18 | "github.com/gardener/diki/pkg/shared/ruleset/disak8sstig/rules" 19 | ) 20 | 21 | var _ = Describe("#242395", func() { 22 | var ( 23 | fakeClient client.Client 24 | pod *corev1.Pod 25 | ctx = context.TODO() 26 | ) 27 | 28 | BeforeEach(func() { 29 | fakeClient = fakeclient.NewClientBuilder().Build() 30 | pod = &corev1.Pod{ 31 | ObjectMeta: metav1.ObjectMeta{ 32 | Name: "pod", 33 | Namespace: "namespace", 34 | Labels: map[string]string{}, 35 | }, 36 | } 37 | }) 38 | 39 | It("should return passed checkResult when dashboard is not installed", func() { 40 | pod1 := pod.DeepCopy() 41 | pod1.Name = "pod1" 42 | Expect(fakeClient.Create(ctx, pod1)).To(Succeed()) 43 | 44 | pod2 := pod.DeepCopy() 45 | pod2.Name = "pod2" 46 | Expect(fakeClient.Create(ctx, pod2)).To(Succeed()) 47 | 48 | r := &rules.Rule242395{Client: fakeClient} 49 | 50 | ruleResult, err := r.Run(ctx) 51 | Expect(err).ToNot(HaveOccurred()) 52 | 53 | expectedCheckResults := []rule.CheckResult{ 54 | rule.PassedCheckResult("Kubernetes dashboard not installed", rule.NewTarget()), 55 | } 56 | 57 | Expect(ruleResult.CheckResults).To(Equal(expectedCheckResults)) 58 | }) 59 | 60 | It("should return failed checkResult when dashboard is not installed", func() { 61 | pod1 := pod.DeepCopy() 62 | pod1.Name = "pod1" 63 | pod1.Labels["k8s-app"] = "kubernetes-dashboard" 64 | Expect(fakeClient.Create(ctx, pod1)).To(Succeed()) 65 | 66 | pod2 := pod.DeepCopy() 67 | pod2.Name = "pod2" 68 | pod2.Labels["k8s-app"] = "kubernetes-dashboard" 69 | Expect(fakeClient.Create(ctx, pod2)).To(Succeed()) 70 | 71 | r := &rules.Rule242395{Client: fakeClient} 72 | 73 | ruleResult, err := r.Run(ctx) 74 | Expect(err).ToNot(HaveOccurred()) 75 | 76 | expectedCheckResults := []rule.CheckResult{ 77 | rule.FailedCheckResult("Kubernetes dashboard installed", rule.NewTarget("name", pod1.Name, "namespace", pod1.Namespace, "kind", "Pod")), 78 | rule.FailedCheckResult("Kubernetes dashboard installed", rule.NewTarget("name", pod2.Name, "namespace", pod2.Namespace, "kind", "Pod")), 79 | } 80 | 81 | Expect(ruleResult.CheckResults).To(Equal(expectedCheckResults)) 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242420.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strings" 11 | 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/client-go/rest" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | 16 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 17 | "github.com/gardener/diki/pkg/rule" 18 | ) 19 | 20 | var ( 21 | _ rule.Rule = &Rule242420{} 22 | _ rule.Severity = &Rule242420{} 23 | ) 24 | 25 | type Rule242420 struct { 26 | Client client.Client 27 | V1RESTClient rest.Interface 28 | } 29 | 30 | func (r *Rule242420) ID() string { 31 | return ID242420 32 | } 33 | 34 | func (r *Rule242420) Name() string { 35 | return "Kubernetes Kubelet must have the SSL Certificate Authority set." 36 | } 37 | 38 | func (r *Rule242420) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242420) Run(ctx context.Context) (rule.RuleResult, error) { 43 | var checkResults []rule.CheckResult 44 | 45 | nodes, err := kubeutils.GetNodes(ctx, r.Client, 300) 46 | if err != nil { 47 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), rule.NewTarget("kind", "NodeList"))), nil 48 | } 49 | 50 | if len(nodes) == 0 { 51 | return rule.Result(r, rule.WarningCheckResult("No nodes found.", rule.NewTarget())), nil 52 | } 53 | 54 | const clientCAFileConfigOption = "authentication.x509.clientCAFile" 55 | for _, node := range nodes { 56 | target := kubeutils.TargetWithK8sObject(rule.NewTarget(), metav1.TypeMeta{Kind: "Node"}, node.ObjectMeta) 57 | if !kubeutils.NodeReadyStatus(node) { 58 | checkResults = append(checkResults, rule.WarningCheckResult("Node is not in Ready state.", target)) 59 | continue 60 | } 61 | 62 | kubeletConfig, err := kubeutils.GetNodeConfigz(ctx, r.V1RESTClient, node.Name) 63 | if err != nil { 64 | checkResults = append(checkResults, rule.ErroredCheckResult(err.Error(), target)) 65 | continue 66 | } 67 | 68 | switch { 69 | case kubeletConfig.Authentication.X509.ClientCAFile == nil: 70 | checkResults = append(checkResults, rule.FailedCheckResult(fmt.Sprintf("Option %s not set.", clientCAFileConfigOption), target)) 71 | case strings.TrimSpace(*kubeletConfig.Authentication.X509.ClientCAFile) == "": 72 | checkResults = append(checkResults, rule.FailedCheckResult(fmt.Sprintf("Option %s is empty.", clientCAFileConfigOption), target)) 73 | default: 74 | checkResults = append(checkResults, rule.PassedCheckResult(fmt.Sprintf("Option %s set.", clientCAFileConfigOption), target)) 75 | } 76 | } 77 | 78 | return rule.Result(r, checkResults...), nil 79 | } 80 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | run: 4 | concurrency: 4 5 | 6 | linters: 7 | enable: 8 | - gocritic 9 | - gosec 10 | - revive 11 | exclusions: 12 | generated: lax 13 | rules: 14 | - linters: 15 | - revive 16 | path: pkg/provider/gardener/ruleset/disak8sstig/rules/ 17 | text: 'exported: exported' 18 | - linters: 19 | - revive 20 | path: pkg/provider/managedk8s/ruleset/disak8sstig/rules/ 21 | text: 'exported: exported' 22 | - linters: 23 | - revive 24 | path: pkg/provider/managedk8s/ruleset/securityhardenedk8s/rules/ 25 | text: 'exported: exported' 26 | - linters: 27 | - revive 28 | path: pkg/provider/virtualgarden/ruleset/disak8sstig/rules/ 29 | text: 'exported: exported' 30 | - linters: 31 | - revive 32 | path: pkg/provider/garden/ruleset/securityhardenedshoot/rules/ 33 | text: 'exported: exported' 34 | - linters: 35 | - revive 36 | path: pkg/shared/ruleset/disak8sstig/rules/ 37 | text: 'exported: exported' 38 | settings: 39 | revive: 40 | rules: 41 | # recommended rules https://github.com/mgechev/revive/tree/v1.9.0?tab=readme-ov-file#recommended-configuration 42 | - name: blank-imports 43 | - name: context-as-argument 44 | - name: context-keys-type 45 | - name: dot-imports 46 | arguments: 47 | - allowedPackages: 48 | - github.com/onsi/ginkgo/v2 49 | - github.com/onsi/gomega 50 | - github.com/onsi/gomega/gstruct 51 | - name: error-return 52 | - name: error-strings 53 | - name: error-naming 54 | - name: exported 55 | arguments: 56 | - disableStutteringCheck 57 | - name: increment-decrement 58 | - name: var-naming 59 | # var-naming arguments are ordered https://github.com/mgechev/revive/blob/v1.11.0/RULES_DESCRIPTIONS.md#var-naming 60 | arguments: 61 | - [] 62 | - [] 63 | - 64 | - skipPackageNameChecks: true 65 | - name: var-declaration 66 | # - name: package-comments 67 | - name: range 68 | - name: receiver-naming 69 | - name: time-naming 70 | - name: unexported-return 71 | # - name: indent-error-flow 72 | - name: errorf 73 | - name: empty-block 74 | - name: superfluous-else 75 | - name: unused-parameter 76 | - name: unreachable-code 77 | - name: redefines-builtin-id 78 | staticcheck: 79 | # Default: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"] 80 | checks: 81 | - all 82 | - -ST1000 # checks if at least one file in a package has comments 83 | - -SA1019 # checks if deprecated API is used 84 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242376.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "slices" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242376{} 20 | _ rule.Severity = &Rule242376{} 21 | ) 22 | 23 | type Rule242376 struct { 24 | Client client.Client 25 | Namespace string 26 | DeploymentName string 27 | ContainerName string 28 | } 29 | 30 | func (r *Rule242376) ID() string { 31 | return ID242376 32 | } 33 | 34 | func (r *Rule242376) Name() string { 35 | return "The Kubernetes Controller Manager must use TLS 1.2, at a minimum, to protect the confidentiality of sensitive data during electronic dissemination." 36 | } 37 | 38 | func (r *Rule242376) Severity() rule.SeverityLevel { 39 | return rule.SeverityMedium 40 | } 41 | 42 | func (r *Rule242376) Run(ctx context.Context) (rule.RuleResult, error) { 43 | const option = "tls-min-version" 44 | deploymentName := "kube-controller-manager" 45 | containerName := "kube-controller-manager" 46 | 47 | if r.DeploymentName != "" { 48 | deploymentName = r.DeploymentName 49 | } 50 | 51 | if r.ContainerName != "" { 52 | containerName = r.ContainerName 53 | } 54 | 55 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 56 | 57 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 58 | if err != nil { 59 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 60 | } 61 | 62 | // empty options are allowed because min version defaults to TLS 1.2 63 | switch { 64 | case len(optSlice) == 0: 65 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 66 | case len(optSlice) > 1: 67 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 68 | case slices.Contains([]string{"VersionTLS10", "VersionTLS11"}, optSlice[0]): 69 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", option), target)), nil 70 | case slices.Contains([]string{"VersionTLS12", "VersionTLS13"}, optSlice[0]): 71 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", option), target)), nil 72 | default: 73 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set to unknown value.", option), target)), nil 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /example/config/virtualgarden.yaml: -------------------------------------------------------------------------------- 1 | providers: # contains information about known providers 2 | - id: virtualgarden # unique provider identifier 3 | name: "Virtual Garden" # user friendly name of the provider 4 | metadata: 5 | foo: bar 6 | args: 7 | # additionalOpsPodLabels: # pod labels that will be added to diki ops pods 8 | # foo: bar 9 | runtimeKubeconfigPath: /tmp/runtime.config # path to runtime cluster admin kubeconfig 10 | rulesets: 11 | - id: disa-kubernetes-stig 12 | name: DISA Kubernetes Security Technical Implementation Guide 13 | version: v2r4 14 | # args: 15 | # maxRetries: 1 # number of maximum rule run retries. Defaults to 1 16 | ruleOptions: 17 | # - ruleID: "242376" 18 | # skip: 19 | # enabled: true 20 | # justification: "the whole rule is accepted for ... reasons" 21 | # - ruleID: "242390" 22 | # args: 23 | # acceptedEndpoints: 24 | # - path: /healthz 25 | # - path: /livez 26 | # - ruleID: "242442" 27 | # args: 28 | # expectedVersionedImages: 29 | # - name: "eu.gcr.io/foo" 30 | # - name: "eu.gcr.io/bar" 31 | - ruleID: "242445" 32 | args: 33 | expectedFileOwner: 34 | # users and groups default to ["0"] 35 | # 36 | # Gardener images use distroless nonroot user with ID 65532 37 | # https://github.com/GoogleContainerTools/distroless/blob/main/base/base.bzl#L8 38 | users: ["0", "65532"] 39 | groups: ["0", "65532"] 40 | - ruleID: "242446" 41 | args: 42 | expectedFileOwner: 43 | # users and groups default to ["0"] 44 | # 45 | # Gardener images use distroless nonroot user with ID 65532 46 | # https://github.com/GoogleContainerTools/distroless/blob/main/base/base.bzl#L8 47 | users: ["0", "65532"] 48 | groups: ["0", "65532"] 49 | - ruleID: "242451" 50 | args: 51 | expectedFileOwner: 52 | # users and groups default to ["0"] 53 | # 54 | # Gardener images use distroless nonroot user with ID 65532 55 | # https://github.com/GoogleContainerTools/distroless/blob/main/base/base.bzl#L8 56 | users: ["0", "65532"] 57 | groups: ["0", "65532"] 58 | - ruleID: "245543" 59 | args: 60 | acceptedTokens: 61 | - user: "health-check" 62 | uid: "health-check" 63 | # groups: "group1,group2,group3" 64 | # metadata: # optional, additional metadata to be added to summary json report 65 | # foo: bar 66 | # bar: 67 | # foo: bar 68 | output: 69 | path: /tmp/test-output.json # optional, path to summary json report. If --output flag is set this configuration is ignored 70 | minStatus: Passed 71 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242392.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/client-go/rest" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | 15 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 16 | "github.com/gardener/diki/pkg/rule" 17 | ) 18 | 19 | var ( 20 | _ rule.Rule = &Rule242392{} 21 | _ rule.Severity = &Rule242392{} 22 | ) 23 | 24 | type Rule242392 struct { 25 | Client client.Client 26 | V1RESTClient rest.Interface 27 | } 28 | 29 | func (r *Rule242392) ID() string { 30 | return ID242392 31 | } 32 | 33 | func (r *Rule242392) Name() string { 34 | return "The Kubernetes kubelet must enable explicit authorization." 35 | } 36 | 37 | func (r *Rule242392) Severity() rule.SeverityLevel { 38 | return rule.SeverityHigh 39 | } 40 | 41 | func (r *Rule242392) Run(ctx context.Context) (rule.RuleResult, error) { 42 | var checkResults []rule.CheckResult 43 | 44 | nodes, err := kubeutils.GetNodes(ctx, r.Client, 300) 45 | if err != nil { 46 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), rule.NewTarget("kind", "NodeList"))), nil 47 | } 48 | 49 | if len(nodes) == 0 { 50 | return rule.Result(r, rule.WarningCheckResult("No nodes found.", rule.NewTarget())), nil 51 | } 52 | 53 | const authorizationModeConfigOption = "authorization.mode" 54 | for _, node := range nodes { 55 | target := kubeutils.TargetWithK8sObject(rule.NewTarget(), metav1.TypeMeta{Kind: "Node"}, node.ObjectMeta) 56 | if !kubeutils.NodeReadyStatus(node) { 57 | checkResults = append(checkResults, rule.WarningCheckResult("Node is not in Ready state.", target)) 58 | continue 59 | } 60 | 61 | kubeletConfig, err := kubeutils.GetNodeConfigz(ctx, r.V1RESTClient, node.Name) 62 | if err != nil { 63 | checkResults = append(checkResults, rule.ErroredCheckResult(err.Error(), target)) 64 | continue 65 | } 66 | 67 | switch { 68 | case kubeletConfig.Authorization.Mode == nil: 69 | checkResults = append(checkResults, rule.FailedCheckResult(fmt.Sprintf("Option %s not set.", authorizationModeConfigOption), target)) 70 | case *kubeletConfig.Authorization.Mode != "Webhook": 71 | checkResults = append(checkResults, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", authorizationModeConfigOption), target.With("details", fmt.Sprintf("Authorization Mode set to %s", *kubeletConfig.Authorization.Mode)))) 72 | default: 73 | checkResults = append(checkResults, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", authorizationModeConfigOption), target)) 74 | } 75 | } 76 | 77 | return rule.Result(r, checkResults...), nil 78 | } 79 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242434.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/client-go/rest" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | 15 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 16 | "github.com/gardener/diki/pkg/rule" 17 | ) 18 | 19 | var ( 20 | _ rule.Rule = &Rule242434{} 21 | _ rule.Severity = &Rule242434{} 22 | ) 23 | 24 | type Rule242434 struct { 25 | Client client.Client 26 | V1RESTClient rest.Interface 27 | } 28 | 29 | func (r *Rule242434) ID() string { 30 | return ID242434 31 | } 32 | 33 | func (r *Rule242434) Name() string { 34 | return "Kubernetes Kubelet must enable kernel protection." 35 | } 36 | 37 | func (r *Rule242434) Severity() rule.SeverityLevel { 38 | return rule.SeverityHigh 39 | } 40 | 41 | func (r *Rule242434) Run(ctx context.Context) (rule.RuleResult, error) { 42 | var checkResults []rule.CheckResult 43 | 44 | nodes, err := kubeutils.GetNodes(ctx, r.Client, 300) 45 | if err != nil { 46 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), rule.NewTarget("kind", "NodeList"))), nil 47 | } 48 | 49 | if len(nodes) == 0 { 50 | return rule.Result(r, rule.WarningCheckResult("No nodes found.", rule.NewTarget())), nil 51 | } 52 | 53 | const protectKernelDefaultsConfigOption = "protectKernelDefaults" 54 | for _, node := range nodes { 55 | target := kubeutils.TargetWithK8sObject(rule.NewTarget(), metav1.TypeMeta{Kind: "Node"}, node.ObjectMeta) 56 | if !kubeutils.NodeReadyStatus(node) { 57 | checkResults = append(checkResults, rule.WarningCheckResult("Node is not in Ready state.", target)) 58 | continue 59 | } 60 | 61 | kubeletConfig, err := kubeutils.GetNodeConfigz(ctx, r.V1RESTClient, node.Name) 62 | if err != nil { 63 | checkResults = append(checkResults, rule.ErroredCheckResult(err.Error(), target)) 64 | continue 65 | } 66 | 67 | // protectKernelDefaults defaults to not allowed value false. ref https://kubernetes.io/docs/reference/config-api/kubelet-config.v1beta1/ 68 | switch { 69 | case kubeletConfig.ProtectKernelDefaults == nil: 70 | checkResults = append(checkResults, rule.FailedCheckResult(fmt.Sprintf("Option %s not set.", protectKernelDefaultsConfigOption), target)) 71 | case *kubeletConfig.ProtectKernelDefaults: 72 | checkResults = append(checkResults, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", protectKernelDefaultsConfigOption), target)) 73 | default: 74 | checkResults = append(checkResults, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", protectKernelDefaultsConfigOption), target)) 75 | } 76 | } 77 | 78 | return rule.Result(r, checkResults...), nil 79 | } 80 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242387.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strconv" 11 | 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/client-go/rest" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | 16 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 17 | "github.com/gardener/diki/pkg/rule" 18 | ) 19 | 20 | var ( 21 | _ rule.Rule = &Rule242387{} 22 | _ rule.Severity = &Rule242387{} 23 | ) 24 | 25 | type Rule242387 struct { 26 | Client client.Client 27 | V1RESTClient rest.Interface 28 | } 29 | 30 | func (r *Rule242387) ID() string { 31 | return ID242387 32 | } 33 | 34 | func (r *Rule242387) Name() string { 35 | return `The Kubernetes Kubelet must have the "readOnlyPort" flag disabled.` 36 | } 37 | 38 | func (r *Rule242387) Severity() rule.SeverityLevel { 39 | return rule.SeverityHigh 40 | } 41 | 42 | func (r *Rule242387) Run(ctx context.Context) (rule.RuleResult, error) { 43 | var checkResults []rule.CheckResult 44 | 45 | nodes, err := kubeutils.GetNodes(ctx, r.Client, 300) 46 | if err != nil { 47 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), rule.NewTarget("kind", "NodeList"))), nil 48 | } 49 | 50 | if len(nodes) == 0 { 51 | return rule.Result(r, rule.WarningCheckResult("No nodes found.", rule.NewTarget())), nil 52 | } 53 | 54 | const readOnlyPortConfigOption = "readOnlyPort" 55 | for _, node := range nodes { 56 | target := kubeutils.TargetWithK8sObject(rule.NewTarget(), metav1.TypeMeta{Kind: "Node"}, node.ObjectMeta) 57 | if !kubeutils.NodeReadyStatus(node) { 58 | checkResults = append(checkResults, rule.WarningCheckResult("Node is not in Ready state.", target)) 59 | continue 60 | } 61 | 62 | kubeletConfig, err := kubeutils.GetNodeConfigz(ctx, r.V1RESTClient, node.Name) 63 | if err != nil { 64 | checkResults = append(checkResults, rule.ErroredCheckResult(err.Error(), target)) 65 | continue 66 | } 67 | 68 | // readOnlyPort defaults to allowed value disabled. ref https://kubernetes.io/docs/reference/config-api/kubelet-config.v1beta1/ 69 | switch { 70 | case kubeletConfig.ReadOnlyPort == nil: 71 | checkResults = append(checkResults, rule.PassedCheckResult(fmt.Sprintf("Option %s not set.", readOnlyPortConfigOption), target)) 72 | case *kubeletConfig.ReadOnlyPort == 0: 73 | checkResults = append(checkResults, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", readOnlyPortConfigOption), target)) 74 | default: 75 | checkResults = append(checkResults, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", readOnlyPortConfigOption), target.With("details", fmt.Sprintf("Read only port set to %s", strconv.Itoa(int(*kubeletConfig.ReadOnlyPort)))))) 76 | } 77 | } 78 | 79 | return rule.Result(r, checkResults...), nil 80 | } 81 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package config 6 | 7 | // DikiConfig is used to represent Diki configuration file. 8 | type DikiConfig struct { 9 | // Providers is a list of all known providers. 10 | Providers []ProviderConfig `yaml:"providers"` 11 | // Metadata represents additional values to be added to the generated report. 12 | Metadata map[string]any `yaml:"metadata,omitempty"` 13 | // Output describes options related to diki's output configuration. 14 | Output *OutputConfig `yaml:"output,omitempty"` 15 | } 16 | 17 | // ProviderConfig is used to describe and configure a provider. 18 | type ProviderConfig struct { 19 | // ID is the unique identifier of a provider. 20 | ID string `yaml:"id"` 21 | // Name is the user friendly name of a provider. 22 | Name string `yaml:"name"` 23 | // Metadata represents additional values used to describe a provider. 24 | Metadata map[string]string `yaml:"metadata"` 25 | // Rulesets represents ruleset specific configurations. 26 | Rulesets []RulesetConfig `yaml:"rulesets"` 27 | // Args are provider specific arguments that each provider should be able to parse. 28 | Args any `yaml:"args"` 29 | } 30 | 31 | // RulesetConfig is used to describe and configure a ruleset. 32 | type RulesetConfig struct { 33 | // ID is the unique identifier of a ruleset. 34 | ID string `yaml:"id"` 35 | // Name is the user friendly name of a ruleset. 36 | Name string `yaml:"name"` 37 | // Version is the ruleset's version. 38 | Version string `yaml:"version"` 39 | // RuleOptions is used to provide per rule configurations. 40 | RuleOptions []RuleOptionsConfig `yaml:"ruleOptions"` 41 | // Args are ruleset specific arguments that each ruleset should be able to parse. 42 | Args any `yaml:"args"` 43 | } 44 | 45 | // RuleOptionsConfig represents per rule options. 46 | type RuleOptionsConfig struct { 47 | // RuleID is the id of the rule. 48 | RuleID string `yaml:"ruleID"` 49 | // Skip is the rule's skip configuration. 50 | Skip *RuleOptionSkipConfig `yaml:"skip,omitempty"` 51 | // Args are rule specific arguments that each rule should be able to parse. 52 | Args any `yaml:"args,omitempty"` 53 | } 54 | 55 | // RuleOptionSkipConfig represents options allowing a rule skip. 56 | type RuleOptionSkipConfig struct { 57 | // Enabled determines if a rule should be skipped or not. 58 | Enabled bool `yaml:"enabled"` 59 | // Justification represents the reason why a rule is skipped. 60 | Justification string `yaml:"justification"` 61 | } 62 | 63 | // OutputConfig represents output configurations. 64 | type OutputConfig struct { 65 | // Path is the location which will be used to write a diki report. 66 | // 67 | // Deprecated: This field is deprecated and will be removed in a future release. 68 | Path string `yaml:"path"` 69 | // MinStatus is the minimal status that diki will report. 70 | MinStatus string `yaml:"minStatus"` 71 | } 72 | -------------------------------------------------------------------------------- /pkg/provider/virtualgarden/ruleset/disak8sstig/rules/242400.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | 13 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 14 | "github.com/gardener/diki/pkg/rule" 15 | sharedrules "github.com/gardener/diki/pkg/shared/ruleset/disak8sstig/rules" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule242400{} 20 | _ rule.Severity = &Rule242400{} 21 | ) 22 | 23 | type Rule242400 struct { 24 | Client client.Client 25 | Namespace string 26 | } 27 | 28 | func (r *Rule242400) ID() string { 29 | return sharedrules.ID242400 30 | } 31 | 32 | func (r *Rule242400) Name() string { 33 | return "The Kubernetes API server must have Alpha APIs disabled." 34 | } 35 | 36 | func (r *Rule242400) Severity() rule.SeverityLevel { 37 | return rule.SeverityMedium 38 | } 39 | 40 | func (r *Rule242400) Run(ctx context.Context) (rule.RuleResult, error) { 41 | const option = "feature-gates.AllAlpha" 42 | var ( 43 | checkResults []rule.CheckResult 44 | deployments = map[string]string{ 45 | "virtual-garden-kube-apiserver": "kube-apiserver", 46 | "virtual-garden-kube-controller-manager": "kube-controller-manager", 47 | } 48 | ) 49 | 50 | for deploymentName, containerName := range deployments { 51 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 52 | 53 | fgOptions, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, "feature-gates") 54 | if err != nil { 55 | checkResults = append(checkResults, rule.ErroredCheckResult(err.Error(), target)) 56 | continue 57 | } 58 | 59 | allAlphaOptions := kubeutils.FindInnerValue(fgOptions, "AllAlpha") 60 | // featureGates.AllAlpha defaults to false. ref https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/ 61 | switch { 62 | case len(allAlphaOptions) == 0: 63 | checkResults = append(checkResults, rule.PassedCheckResult(fmt.Sprintf("Option %s not set.", option), target)) 64 | case len(allAlphaOptions) > 1: 65 | checkResults = append(checkResults, rule.WarningCheckResult(fmt.Sprintf("Option %s set more than once in container command.", option), target)) 66 | case allAlphaOptions[0] == "true": 67 | checkResults = append(checkResults, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed value.", option), target)) 68 | case allAlphaOptions[0] == "false": 69 | checkResults = append(checkResults, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed value.", option), target)) 70 | default: 71 | checkResults = append(checkResults, rule.WarningCheckResult(fmt.Sprintf("Option %s set to neither 'true' nor 'false'.", option), target)) 72 | } 73 | } 74 | return rule.Result(r, checkResults...), nil 75 | } 76 | -------------------------------------------------------------------------------- /pkg/provider/garden/ruleset/securityhardenedshoot/rules/2003.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | 10 | gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 15 | "github.com/gardener/diki/pkg/rule" 16 | ) 17 | 18 | var ( 19 | _ rule.Rule = &Rule2003{} 20 | _ rule.Severity = &Rule2003{} 21 | ) 22 | 23 | type Rule2003 struct { 24 | Client client.Client 25 | ShootName string 26 | ShootNamespace string 27 | } 28 | 29 | func (r *Rule2003) ID() string { 30 | return "2003" 31 | } 32 | 33 | func (r *Rule2003) Name() string { 34 | return "Shoot clusters must enable kernel protection for Kubelets." 35 | } 36 | 37 | func (r *Rule2003) Severity() rule.SeverityLevel { 38 | return rule.SeverityHigh 39 | } 40 | 41 | func (r *Rule2003) Run(ctx context.Context) (rule.RuleResult, error) { 42 | shoot := &gardencorev1beta1.Shoot{ObjectMeta: metav1.ObjectMeta{Name: r.ShootName, Namespace: r.ShootNamespace}} 43 | if err := r.Client.Get(ctx, client.ObjectKeyFromObject(shoot), shoot); err != nil { 44 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), kubeutils.TargetWithK8sObject(rule.NewTarget(), metav1.TypeMeta{Kind: "Shoot"}, shoot.ObjectMeta))), nil 45 | } 46 | 47 | var checkResults []rule.CheckResult 48 | switch { 49 | case shoot.Spec.Kubernetes.Kubelet == nil || shoot.Spec.Kubernetes.Kubelet.ProtectKernelDefaults == nil: 50 | checkResults = append(checkResults, rule.PassedCheckResult("Default kubelet config does not disable kernel protection.", rule.NewTarget())) 51 | case *shoot.Spec.Kubernetes.Kubelet.ProtectKernelDefaults: 52 | checkResults = append(checkResults, rule.PassedCheckResult("Default kubelet config enables kernel protection.", rule.NewTarget())) 53 | default: 54 | checkResults = append(checkResults, rule.FailedCheckResult("Default kubelet config disables kernel protection.", rule.NewTarget())) 55 | } 56 | 57 | for _, w := range shoot.Spec.Provider.Workers { 58 | workerTarget := rule.NewTarget("worker", w.Name) 59 | switch { 60 | case w.Kubernetes == nil || w.Kubernetes.Kubelet == nil || w.Kubernetes.Kubelet.ProtectKernelDefaults == nil: 61 | checkResults = append(checkResults, rule.PassedCheckResult("Worker kubelet config does not disable kernel protection.", workerTarget)) 62 | case *w.Kubernetes.Kubelet.ProtectKernelDefaults: 63 | checkResults = append(checkResults, rule.PassedCheckResult("Worker kubelet config enables kernel protection.", workerTarget)) 64 | default: 65 | checkResults = append(checkResults, rule.FailedCheckResult("Worker kubelet config disables kernel protection.", workerTarget)) 66 | } 67 | } 68 | 69 | return rule.Result(r, checkResults...), nil 70 | } 71 | -------------------------------------------------------------------------------- /docs/usage/security-hardened-k8s-shoot.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: How can I check whether my shoot cluster fulfills the Security Hardened Kubernetes Cluster requirements? 3 | --- 4 | 5 | ## Show Security Hardened Kubernetes Compliance for a Gardener Shoot Cluster 6 | 7 | ### Introduction 8 | 9 | This part covers the topic of showing compliance with the Security Hardened Kubernetes Cluster requirements for a Gardener shoot cluster. The guide features the `managedk8s` provider, which implements rules from the Security Hardened Kubernetes Cluster ruleset. 10 | 11 | ### Prerequisites 12 | 13 | Make sure you have [diki installed](../../README.md#Installation) and have a running Gardener shoot cluster. 14 | 15 | We will be using the sample [Security Hardened Kubernetes Guide for Shoots configuration file](../../example/guides/security-hardened-k8s-shoot.yaml) for this run. 16 | 17 | ### Configuration 18 | 19 | #### Configure the `managedk8s` provider 20 | 21 | Set the following arguments: 22 | - `providers[id=="managedk8s"].args.kubeconfigPath` pointing to a shoot admin kubeconfig. 23 | - (optional) `providers[id=="managedk8s"].metadata.shootName` should be set to the name of the shoot cluster. The `metadata` field contains custom metadata from the user that will be present in the generated report. 24 | 25 | ``` yaml 26 | - id: managedk8s 27 | name: "Managed Kubernetes" 28 | metadata: # custom user metadata 29 | # shootName: 30 | args: 31 | kubeconfigPath: # path to shoot admin kubeconfig 32 | ``` 33 | 34 | In case you need instructions on how to generate such a kubeconfig, please read [Accessing Shoot Clusters](https://github.com/gardener/gardener/blob/master/docs/usage/shoot/shoot_access.md). 35 | 36 | #### Additional configurations 37 | 38 | Additional metadata such as the shoot's name can also be included in the `providers[id=="managedk8s].metadata` section. The metadata section can be used to add additional context to different diki runs. 39 | 40 | The provided configuration contains the recommended rule options for running the both providers, but you can modify rule options parameters according to requirements. All available options can be found in: 41 | - [managedk8s example configuration](../../example/config/managedk8s.yaml). 42 | 43 | ### Running the DISA K8s STIGs Ruleset 44 | 45 | To run diki against a Gardener shoot cluster, run the following command: 46 | 47 | ```bash 48 | diki run \ 49 | --config=./example/guides/security-hardened-k8s-shoot.yaml \ 50 | --provider=managedk8s \ 51 | --ruleset-id=security-hardened-k8s \ 52 | --ruleset-version=v0.1.0 \ 53 | --output=security-hardened-k8s-shoot-report.json 54 | ``` 55 | 56 | ### Generating a Report 57 | 58 | We can use the file generated in the previous step to create an html report by using the following command: 59 | 60 | ```bash 61 | diki report generate \ 62 | --output=security-hardened-k8s-shoot-report.html \ 63 | security-hardened-k8s-shoot-report.json 64 | ``` -------------------------------------------------------------------------------- /pkg/kubernetes/pod/privileged.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package pod 6 | 7 | import ( 8 | "maps" 9 | 10 | corev1 "k8s.io/api/core/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/utils/ptr" 13 | ) 14 | 15 | const ( 16 | // LabelInstanceID is used to group all pods created by a single ruleset. 17 | LabelInstanceID = "compliance.gardener.cloud/instanceID" 18 | 19 | // LabelComplianceRoleKey is used to label pods related to compliance operations in the cluster. 20 | LabelComplianceRoleKey = "compliance.gardener.cloud/role" 21 | 22 | // LabelComplianceRolePrivPod is used as the label value for LabelComplianceRoleKey indicating privileged diki pods. 23 | LabelComplianceRolePrivPod = "diki-privileged-pod" 24 | 25 | maxNameLength = 63 26 | ) 27 | 28 | // NewPrivilegedPod creates a new privileged Pod. 29 | func NewPrivilegedPod(name, namespace, image, nodeName string, additionalLabels map[string]string) func() *corev1.Pod { 30 | if len(name) > maxNameLength { 31 | name = name[:maxNameLength] 32 | } 33 | 34 | labels := map[string]string{} 35 | if additionalLabels != nil { 36 | labels = maps.Clone(additionalLabels) 37 | } 38 | 39 | pod := &corev1.Pod{ 40 | ObjectMeta: metav1.ObjectMeta{ 41 | Name: name, 42 | Namespace: namespace, 43 | Labels: labels, 44 | }, 45 | Spec: corev1.PodSpec{ 46 | ActiveDeadlineSeconds: ptr.To[int64](300), 47 | Containers: []corev1.Container{ 48 | { 49 | Name: "container", 50 | Image: image, 51 | Command: []string{"chroot", "/host", "/bin/bash", "-c", "nsenter -m -t $(pgrep -xo systemd) sleep 300"}, 52 | SecurityContext: &corev1.SecurityContext{ 53 | Privileged: ptr.To(true), 54 | }, 55 | VolumeMounts: []corev1.VolumeMount{ 56 | { 57 | Name: "host-root-volume", 58 | MountPath: "/host", 59 | ReadOnly: false, 60 | }, 61 | }, 62 | }, 63 | }, 64 | Volumes: []corev1.Volume{ 65 | { 66 | Name: "host-root-volume", 67 | VolumeSource: corev1.VolumeSource{ 68 | HostPath: &corev1.HostPathVolumeSource{ 69 | Path: "/", 70 | }, 71 | }, 72 | }, 73 | }, 74 | HostNetwork: true, 75 | HostPID: true, 76 | RestartPolicy: "Never", 77 | Tolerations: []corev1.Toleration{ 78 | { 79 | Effect: "NoSchedule", 80 | Operator: "Exists", 81 | }, 82 | { 83 | Effect: "NoExecute", 84 | Operator: "Exists", 85 | }, 86 | }, 87 | }, 88 | } 89 | 90 | if nodeName != "" { 91 | pod.Spec.NodeSelector = map[string]string{"kubernetes.io/hostname": nodeName} 92 | } 93 | 94 | // Labels that will always be applied to the pod and cannot be overwritten 95 | pod.Labels[LabelComplianceRoleKey] = LabelComplianceRolePrivPod 96 | 97 | return func() *corev1.Pod { 98 | return pod 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /pkg/provider/garden/ruleset/securityhardenedshoot/rules/2004.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "slices" 10 | 11 | gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | 15 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 16 | "github.com/gardener/diki/pkg/rule" 17 | ) 18 | 19 | var ( 20 | _ rule.Rule = &Rule2004{} 21 | _ rule.Severity = &Rule2004{} 22 | ) 23 | 24 | type Rule2004 struct { 25 | Client client.Client 26 | ShootName string 27 | ShootNamespace string 28 | } 29 | 30 | func (r *Rule2004) ID() string { 31 | return "2004" 32 | } 33 | 34 | func (r *Rule2004) Name() string { 35 | return "Shoot clusters must have ValidatingAdmissionWebhook admission plugin enabled." 36 | } 37 | 38 | func (r *Rule2004) Severity() rule.SeverityLevel { 39 | return rule.SeverityHigh 40 | } 41 | 42 | func (r *Rule2004) Run(ctx context.Context) (rule.RuleResult, error) { 43 | shoot := &gardencorev1beta1.Shoot{ObjectMeta: metav1.ObjectMeta{Name: r.ShootName, Namespace: r.ShootNamespace}} 44 | if err := r.Client.Get(ctx, client.ObjectKeyFromObject(shoot), shoot); err != nil { 45 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), kubeutils.TargetWithK8sObject(rule.NewTarget(), metav1.TypeMeta{Kind: "Shoot"}, shoot.ObjectMeta))), nil 46 | } 47 | 48 | if shoot.Spec.Kubernetes.KubeAPIServer == nil || shoot.Spec.Kubernetes.KubeAPIServer.AdmissionPlugins == nil { 49 | return rule.Result(r, rule.PassedCheckResult("The ValidatingAdmissionWebhook admission plugin is not disabled.", rule.NewTarget())), nil 50 | } 51 | 52 | var admissionPlugins = shoot.Spec.Kubernetes.KubeAPIServer.AdmissionPlugins 53 | 54 | // The ValidatingadmissionWebhook is required to be enabled by Gardener. ref: https://github.com/gardener/gardener/blob/834a3476ed4343225690cd0eaca32e03ab5b84f5/pkg/utils/validation/admissionplugins/admissionplugins.go#L71 55 | // Therefore, this rule check is always expected to pass. 56 | if slices.ContainsFunc(admissionPlugins, func(plugin gardencorev1beta1.AdmissionPlugin) bool { 57 | return plugin.Name == "ValidatingAdmissionWebhook" && plugin.Disabled != nil && *plugin.Disabled 58 | }) { 59 | return rule.Result(r, rule.FailedCheckResult("The ValidatingAdmissionWebhook admission plugin is disabled.", rule.NewTarget())), nil 60 | } 61 | if slices.ContainsFunc(admissionPlugins, func(plugin gardencorev1beta1.AdmissionPlugin) bool { 62 | return plugin.Name == "ValidatingAdmissionWebhook" && plugin.Disabled != nil && !*plugin.Disabled 63 | }) { 64 | return rule.Result(r, rule.PassedCheckResult("The ValidatingAdmissionWebhook admission plugin is enabled.", rule.NewTarget())), nil 65 | } 66 | return rule.Result(r, rule.PassedCheckResult("The ValidatingAdmissionWebhook admission plugin is not disabled.", rule.NewTarget())), nil 67 | } 68 | -------------------------------------------------------------------------------- /pkg/provider/managedk8s/ruleset/disak8sstig/rules/242390_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules_test 6 | 7 | import ( 8 | "context" 9 | "net/http" 10 | 11 | . "github.com/onsi/ginkgo/v2" 12 | . "github.com/onsi/gomega" 13 | manualfake "k8s.io/client-go/rest/fake" 14 | 15 | "github.com/gardener/diki/pkg/provider/managedk8s/ruleset/disak8sstig/rules" 16 | "github.com/gardener/diki/pkg/rule" 17 | ) 18 | 19 | var _ = Describe("#242390", func() { 20 | const ( 21 | enabledAnonymousAuthServer = "https://enabled-anonymous-auth-example.com" 22 | disabledAnonymousAuthServer = "https://disabled-anonymous-auth-example.com" 23 | unreachableServer = "https://unreachable-server-example.com" 24 | internalErrorServer = "https://internal-error-server-example.com" 25 | ) 26 | 27 | var ( 28 | mockClient *http.Client 29 | ctx = context.TODO() 30 | ) 31 | 32 | BeforeEach(func() { 33 | mockClient = manualfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 34 | switch req.URL.String() { 35 | case enabledAnonymousAuthServer: 36 | return &http.Response{StatusCode: http.StatusForbidden}, nil 37 | case disabledAnonymousAuthServer: 38 | return &http.Response{StatusCode: http.StatusUnauthorized}, nil 39 | case internalErrorServer: 40 | return &http.Response{StatusCode: http.StatusBadGateway}, nil 41 | default: 42 | return &http.Response{StatusCode: http.StatusNotFound}, http.ErrHandlerTimeout 43 | } 44 | }) 45 | }) 46 | 47 | DescribeTable("Run cases", 48 | func(hostURL string, expectedResult []rule.CheckResult) { 49 | r := rules.Rule242390{ 50 | KAPIExternalURL: hostURL, 51 | Client: mockClient, 52 | } 53 | ruleResult, err := r.Run(ctx) 54 | Expect(err).To(BeNil()) 55 | Expect(ruleResult.CheckResults).To(Equal(expectedResult)) 56 | }, 57 | Entry("should fail when the kube-apiserver anonymous authentication is enabled", enabledAnonymousAuthServer, []rule.CheckResult{ 58 | rule.FailedCheckResult("The kube-apiserver has anonymous authentication enabled.", rule.NewTarget()), 59 | }), 60 | Entry("should pass when the kube-apiserver anonymous authentication is disabled", disabledAnonymousAuthServer, []rule.CheckResult{ 61 | rule.PassedCheckResult("The kube-apiserver has anonymous authentication disabled.", rule.NewTarget()), 62 | }), 63 | Entry("should error when the kube-apiserver URL cannot be resolved", unreachableServer, []rule.CheckResult{ 64 | rule.ErroredCheckResult("could not access kube-apiserver: Get \"https://unreachable-server-example.com\": http: Handler timeout", rule.NewTarget()), 65 | }), 66 | Entry("should warn when the kube-apiserver URL cannot be reached", internalErrorServer, []rule.CheckResult{ 67 | rule.WarningCheckResult("Cannot determine if anonymous authentication is enabled for the kube-apiserver.", rule.NewTarget("details", "the request returned 5xx status code")), 68 | }), 69 | ) 70 | }) 71 | -------------------------------------------------------------------------------- /docs/usage/migrate-selector-rule-options.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: How can I migrate my current selector rule options to the new labelSelector options? 3 | --- 4 | 5 | ## Migrate rule options to the new labelSelector options 6 | 7 | With `v0.20.0` the `matchLabels` selectors for rule options are deprecated in favor of Kubernetes native `labelSelector`s. 8 | The new selectors support both `matchExpressions` and `matchLabels`: 9 | ``` yaml 10 | labelSelector: 11 | matchExpressions: 12 | - key: foo 13 | operator: In 14 | values: 15 | - bar 16 | - baz 17 | - qux 18 | matchLabels: 19 | foo: bar 20 | ``` 21 | 22 | This guide will show you how to migrate to the new `labelSelector`s for rule options. 23 | For specific rule options please check the [example config files](../../example/config) 24 | 25 | ### Rules where only `labelSelector` is added 26 | 27 | Affected rule options: 28 | - Security Hardened Kubernetes Cluster 29 | - 2000 30 | - 2001 31 | - 2002 32 | - 2003 33 | - 2004 34 | - 2006 35 | - 2007 36 | - 2008 37 | - DISA K8s STIG 38 | - 242383 39 | 40 | Old rule option: 41 | ``` yaml 42 | - ruleID: "XXXX" 43 | args: 44 | acceptedPods: 45 | - matchLabels: 46 | foo: bar 47 | namespaceMatchLabels: 48 | foo: bar 49 | ``` 50 | 51 | New rule option: 52 | ``` yaml 53 | - ruleID: "XXXX" 54 | args: 55 | acceptedPods: 56 | - labelSelector: 57 | matchLabels: 58 | foo: bar 59 | namespaceLabelSelector: 60 | matchLabels: 61 | foo: bar 62 | ``` 63 | 64 | ### Rules which used `podMatchLabels` field 65 | 66 | Affected rule options: 67 | - DISA K8s STIG 68 | - 242414 69 | - 242415 70 | - 242417 71 | 72 | Old rule option: 73 | ``` yaml 74 | - ruleID: "XXXX" 75 | args: 76 | acceptedPods: 77 | - podMatchLabels: 78 | foo: bar 79 | namespaceMatchLabels: 80 | foo: bar 81 | ``` 82 | 83 | New rule option: 84 | ``` yaml 85 | - ruleID: "XXXX" 86 | args: 87 | acceptedPods: 88 | - labelSelector: 89 | matchLabels: 90 | foo: bar 91 | namespaceLabelSelector: 92 | matchLabels: 93 | foo: bar 94 | ``` 95 | 96 | ### Kube-proxy rules 97 | Affected rule options: 98 | - DISA K8s STIG 99 | - 242400 100 | - 242442 101 | - 242447 102 | - 242448 103 | - 242451 104 | - 242466 105 | - 242467 106 | 107 | Old rule option: 108 | ``` yaml 109 | - ruleID: "XXXX" 110 | args: 111 | kubeProxyDisabled: true 112 | kubeProxyMatchLabels: 113 | foo: bar 114 | ``` 115 | 116 | New rule option: 117 | ``` yaml 118 | - ruleID: "XXXX" 119 | args: 120 | kubeProxy: 121 | disabled: true 122 | labelSelector: 123 | matchLabels: 124 | foo: bar 125 | ``` 126 | 127 | For rules `242447` & `242448` the `kube-proxy` options are directly composed 128 | ``` yaml 129 | - ruleID: "242447" 130 | args: 131 | labelSelector: 132 | matchLabels: 133 | foo: bar 134 | ``` 135 | -------------------------------------------------------------------------------- /hack/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | 7 | set -e 8 | 9 | rule_id="" 10 | provider="gardener" 11 | ruleset_id="disa-kubernetes-stig" 12 | ruleset_version="v2r4" 13 | run_all="false" 14 | 15 | 16 | function usage { 17 | cat <&2 68 | usage 69 | ;; 70 | *) 71 | break 72 | esac 73 | shift 74 | done 75 | 76 | if [ -z "${config}" ]; then 77 | echo "Error: --config flag not set!" >&2 78 | usage 79 | fi 80 | 81 | if [ -z "${LD_FLAGS}" ]; then 82 | EFFECTIVE_VERSION="$(cat "$(dirname "$0")/../VERSION")"-"$(git rev-parse HEAD)" 83 | LD_FLAGS=$(EFFECTIVE_VERSION=${EFFECTIVE_VERSION} "$(dirname "$0")"/get-build-ld-flags.sh) 84 | fi 85 | 86 | if [ "${run_all}" = "true" ]; then 87 | go run -ldflags "${LD_FLAGS}" \ 88 | "$(dirname "$0")"/../cmd/diki run \ 89 | --config="${config}" \ 90 | --all 91 | 92 | exit 0 93 | fi 94 | 95 | go run -ldflags "${LD_FLAGS}" \ 96 | "$(dirname "$0")"/../cmd/diki run \ 97 | --config="${config}" \ 98 | --rule-id="${rule_id}" \ 99 | --provider="${provider}" \ 100 | --ruleset-id="${ruleset_id}" \ 101 | --ruleset-version="${ruleset_version}" 102 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/ruleset.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package ruleset 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | "fmt" 11 | "sync" 12 | 13 | "github.com/gardener/diki/pkg/rule" 14 | "github.com/gardener/diki/pkg/ruleset" 15 | "github.com/gardener/diki/pkg/shared/provider" 16 | ) 17 | 18 | // Run is a sample implementation for a [ruleset.Ruleset]. 19 | func Run( 20 | ctx context.Context, 21 | r ruleset.Ruleset, 22 | rules map[string]rule.Rule, 23 | numWorkers int, 24 | log provider.Logger, 25 | ) (ruleset.RulesetResult, error) { 26 | if len(rules) == 0 { 27 | return ruleset.RulesetResult{}, fmt.Errorf("no rules are registered in the ruleset") 28 | } 29 | 30 | workers := 1 31 | if numWorkers > 0 { 32 | workers = numWorkers 33 | } 34 | 35 | result := ruleset.RulesetResult{ 36 | RulesetName: r.Name(), 37 | RulesetID: r.ID(), 38 | RulesetVersion: r.Version(), 39 | RuleResults: make([]rule.RuleResult, 0, len(rules)), 40 | } 41 | 42 | type run struct { 43 | result rule.RuleResult 44 | err error 45 | } 46 | 47 | rulesCh := make(chan rule.Rule) 48 | resultCh := make(chan run) 49 | 50 | wg := sync.WaitGroup{} 51 | log.Info("starting ruleset run", "number_of_rules", len(rules), "number_of_workers", workers) 52 | for i := 0; i < workers; i++ { 53 | wg.Add(1) 54 | go func() { 55 | for r := range rulesCh { 56 | log.Info("starting rule run", "rule_id", r.ID()) 57 | res, err := r.Run(ctx) 58 | res.RuleID = r.ID() 59 | res.RuleName = r.Name() 60 | 61 | if len(res.CheckResults) == 0 { 62 | res.CheckResults = append(res.CheckResults, rule.WarningCheckResult("Rule run did not report any status.", rule.NewTarget())) 63 | } 64 | 65 | resultCh <- run{result: res, err: err} 66 | } 67 | wg.Done() 68 | }() 69 | } 70 | 71 | go func() { 72 | defer close(rulesCh) 73 | for _, r := range rules { 74 | select { 75 | case <-ctx.Done(): 76 | return 77 | default: 78 | rulesCh <- r 79 | } 80 | } 81 | }() 82 | 83 | go func() { 84 | wg.Wait() 85 | close(resultCh) 86 | }() 87 | 88 | var err error 89 | resultCount := 0 90 | for run := range resultCh { 91 | resultCount++ 92 | remaining := len(rules) - resultCount 93 | finishMsg := "finished rule run" 94 | if run.err != nil { 95 | log.Error(finishMsg, "rule_id", run.result.RuleID, "remaining", remaining, "error", run.err) 96 | err = errors.Join(err, fmt.Errorf("rule with id %s errored: %w", run.result.RuleID, run.err)) 97 | } else { 98 | log.Info(finishMsg, "rule_id", run.result.RuleID, "remaining", remaining) 99 | result.RuleResults = append(result.RuleResults, run.result) 100 | } 101 | } 102 | 103 | if err := ctx.Err(); err != nil { 104 | return ruleset.RulesetResult{}, err 105 | } 106 | 107 | // TODO: maybe return both result and err 108 | if err != nil { 109 | return ruleset.RulesetResult{}, err 110 | } 111 | return result, nil 112 | } 113 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | ENSURE_GARDENER_MOD := $(shell go get github.com/gardener/gardener@$$(go list -m -f "{{.Version}}" github.com/gardener/gardener)) 6 | GARDENER_HACK_DIR := $(shell go list -m -f "{{.Dir}}" github.com/gardener/gardener)/hack 7 | REPO_ROOT := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 8 | HACK_DIR := $(REPO_ROOT)/hack 9 | VERSION := $(shell cat "$(REPO_ROOT)/VERSION") 10 | EFFECTIVE_VERSION := $(VERSION)-$(shell git rev-parse HEAD) 11 | 12 | 13 | LD_FLAGS := "-w $(shell EFFECTIVE_VERSION=$(EFFECTIVE_VERSION) bash $(HACK_DIR)/get-build-ld-flags.sh)" 14 | 15 | TOOLS_DIR := $(REPO_ROOT)/hack/tools 16 | include $(GARDENER_HACK_DIR)/tools.mk 17 | 18 | # additional tools 19 | include hack/tools.mk 20 | 21 | .PHONY: format 22 | format: $(GOIMPORTS) $(GOIMPORTSREVISER) 23 | @bash $(GARDENER_HACK_DIR)/format.sh ./cmd ./pkg ./imagevector 24 | 25 | .PHONY: test 26 | test: 27 | go test -cover ./... 28 | 29 | .PHONY: clean 30 | clean: 31 | @bash $(GARDENER_HACK_DIR)/clean.sh ./cmd/... ./pkg/... 32 | 33 | .PHONY: check 34 | check: $(GOIMPORTS) $(GOLANGCI_LINT) $(TYPOS) 35 | go vet ./... 36 | @REPO_ROOT=$(REPO_ROOT) bash $(GARDENER_HACK_DIR)/check.sh --golangci-lint-config=./.golangci.yaml ./cmd/... ./pkg/... 37 | 38 | @bash $(GARDENER_HACK_DIR)/check-typos.sh 39 | @bash $(GARDENER_HACK_DIR)/check-file-names.sh 40 | 41 | .PHONY: tidy 42 | tidy: 43 | @GO111MODULE=on go mod tidy 44 | 45 | .PHONY: gen-styles 46 | gen-styles: $(TAILWINDCSS) 47 | @./hack/gen-styles.sh 48 | 49 | .PHONY: generate 50 | generate: 51 | $(MAKE) gen-styles 52 | $(MAKE) format 53 | 54 | .PHONY: check-generate 55 | check-generate: 56 | @bash $(GARDENER_HACK_DIR)/check-generate.sh $(REPO_ROOT) 57 | 58 | .PHONY: sast 59 | sast: $(GOSEC) 60 | @bash $(GARDENER_HACK_DIR)/sast.sh 61 | 62 | .PHONY: sast-report 63 | sast-report: $(GOSEC) 64 | @bash $(GARDENER_HACK_DIR)/sast.sh --gosec-report true 65 | 66 | .PHONY: test-cov 67 | test-cov: 68 | @bash $(GARDENER_HACK_DIR)/test-cover.sh ./cmd/... ./pkg/... 69 | 70 | .PHONY: test-clean 71 | test-clean: 72 | @bash $(GARDENER_HACK_DIR)/test-cover-clean.sh 73 | 74 | .PHONY: verify 75 | verify: format check test sast 76 | 77 | .PHONY: verify-extended 78 | verify-extended: check-generate check format test test-cov test-clean sast-report 79 | 80 | #### BUILD #### 81 | 82 | .PHONY: build 83 | build: 84 | @$(REPO_ROOT)/hack/build.sh 85 | 86 | .PHONY: build-linux-amd64 87 | build-linux-amd64: 88 | @$(REPO_ROOT)/hack/build.sh "linux-amd64" 89 | 90 | .PHONY: build-linux-arm64 91 | build-linux-arm64: 92 | @$(REPO_ROOT)/hack/build.sh "linux-arm64" 93 | 94 | .PHONY: build-darwin-amd64 95 | build-darwin-amd64: 96 | @$(REPO_ROOT)/hack/build.sh "darwin-amd64" 97 | 98 | .PHONY: build-darwin-arm64 99 | build-darwin-arm64: 100 | @$(REPO_ROOT)/hack/build.sh "darwin-arm64" 101 | 102 | .PHONY: build-windows-amd64 103 | build-windows-amd64: 104 | @$(REPO_ROOT)/hack/build.sh "windows-amd64" 105 | -------------------------------------------------------------------------------- /pkg/shared/ruleset/disak8sstig/rules/242418.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package rules 6 | 7 | import ( 8 | "context" 9 | "crypto/tls" 10 | "fmt" 11 | "strings" 12 | 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | 15 | "github.com/gardener/diki/pkg/internal/utils" 16 | kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" 17 | "github.com/gardener/diki/pkg/rule" 18 | ) 19 | 20 | var ( 21 | _ rule.Rule = &Rule242418{} 22 | _ rule.Severity = &Rule242418{} 23 | ) 24 | 25 | type Rule242418 struct { 26 | Client client.Client 27 | Namespace string 28 | DeploymentName string 29 | ContainerName string 30 | } 31 | 32 | func (r *Rule242418) ID() string { 33 | return ID242418 34 | } 35 | 36 | func (r *Rule242418) Name() string { 37 | return "The Kubernetes API server must use approved cipher suites." 38 | } 39 | 40 | func (r *Rule242418) Severity() rule.SeverityLevel { 41 | return rule.SeverityMedium 42 | } 43 | 44 | func (r *Rule242418) Run(ctx context.Context) (rule.RuleResult, error) { 45 | var ( 46 | option = "tls-cipher-suites" 47 | deploymentName = "kube-apiserver" 48 | containerName = "kube-apiserver" 49 | ) 50 | 51 | if r.DeploymentName != "" { 52 | deploymentName = r.DeploymentName 53 | } 54 | 55 | if r.ContainerName != "" { 56 | containerName = r.ContainerName 57 | } 58 | target := rule.NewTarget("name", deploymentName, "namespace", r.Namespace, "kind", "Deployment") 59 | 60 | optSlice, err := kubeutils.GetCommandOptionFromDeployment(ctx, r.Client, deploymentName, containerName, r.Namespace, option) 61 | if err != nil { 62 | return rule.Result(r, rule.ErroredCheckResult(err.Error(), target)), nil 63 | } 64 | 65 | if len(optSlice) == 0 { 66 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has not been set.", option), target)), nil 67 | } 68 | 69 | if len(optSlice) > 1 { 70 | return rule.Result(r, rule.WarningCheckResult(fmt.Sprintf("Option %s has been set more than once in container command.", option), target)), nil 71 | } 72 | 73 | var ( 74 | ciphers = strings.Split(optSlice[0], ",") 75 | requiredCiphers = []string{ 76 | "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 77 | "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 78 | "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 79 | "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 80 | } 81 | unallowedCiphers = []string{ 82 | "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", 83 | "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", 84 | "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", 85 | "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", 86 | } 87 | ) 88 | 89 | for _, suite := range tls.InsecureCipherSuites() { 90 | unallowedCiphers = append(unallowedCiphers, suite.Name) 91 | } 92 | 93 | if utils.Subset(requiredCiphers, ciphers) && !utils.Intersect(unallowedCiphers, ciphers) { 94 | return rule.Result(r, rule.PassedCheckResult(fmt.Sprintf("Option %s set to allowed values.", option), target)), nil 95 | } 96 | 97 | return rule.Result(r, rule.FailedCheckResult(fmt.Sprintf("Option %s set to not allowed values.", option), target)), nil 98 | } 99 | --------------------------------------------------------------------------------