Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/workflows/kind-cluster-resource-names.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Copyright 2024 The Sigstore Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Test policy-controller with --resource-names flag

on:
pull_request:
branches: [ 'main', 'release-*' ]

defaults:
run:
shell: bash

permissions: read-all

jobs:
resource-names-flag-test:
name: Test --resource-names flag functionality
runs-on: ubuntu-latest

strategy:
fail-fast: false # Keep running if one leg fails.
matrix:
k8s-version:
- v1.31.x
- v1.32.x
- v1.33.x
- v1.34.x

env:
KO_DOCKER_REPO: "registry.local:5000/policy-controller"
SCAFFOLDING_RELEASE_VERSION: "v0.7.27"
GO111MODULE: on
GOFLAGS: -ldflags=-s -ldflags=-w
KOCACHE: ~/ko

steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version-file: './go.mod'
check-latest: true

# will use the latest release available for ko
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9

- uses: imranismail/setup-kustomize@2ba527d4d055ab63514ba50a99456fc35684947f # v2.1.0

- name: Install yq
uses: mikefarah/yq@065b200af9851db0d5132f50bc10b1406ea5c0a8 # v4.50.1

- name: Setup mirror
uses: chainguard-dev/actions/setup-mirror@main
with:
mirror: mirror.gcr.io

- uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2

- name: Install cluster + sigstore
uses: sigstore/scaffolding/actions/setup@main
with:
k8s-version: ${{ matrix.k8s-version }}
version: ${{ env.SCAFFOLDING_RELEASE_VERSION }}

- name: Install policy-controller
env:
GIT_HASH: ${{ github.sha }}
GIT_VERSION: ci
LDFLAGS: ""
POLICY_CONTROLLER_YAML: test/kustomize-resource-names/policy-controller-e2e.yaml
KO_PREFIX: registry.local:5000/policy-controller
POLICY_CONTROLLER_ARCHS: linux/amd64
run: |
make ko-policy-controller

- name: Run --resource-names flag tests
run: |
./test/e2e_test_resource_names_flag.sh

- name: Collect diagnostics
if: ${{ failure() }}
uses: chainguard-dev/actions/kind-diag@6b6aeacf5f8f4854707392e7708773a7f510b611 # main
101 changes: 87 additions & 14 deletions cmd/webhook/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"log"
"os"
"strings"
"time"

policyduckv1beta1 "github.com/sigstore/policy-controller/pkg/apis/duck/v1beta1"
Expand Down Expand Up @@ -60,7 +61,13 @@ import (
cwebhook "github.com/sigstore/policy-controller/pkg/webhook"
)

const (
defaultResourceNames = "pods,replicasets,deployments,statefulsets,daemonsets,jobs,cronjobs"
)

var (
validResourceTypes = []string{"pods", "replicasets", "deployments", "statefulsets", "daemonsets", "jobs", "cronjobs"}

// webhookName holds the name of the validating and mutating webhook
// configuration resources dispatching admission requests to policy-controller.
// It is also the name of the webhook which is injected by the controller
Expand Down Expand Up @@ -103,6 +110,10 @@ var (
// trustrootResyncPeriod holds the interval which the TrustRoot will resync
// This is essential for triggering a reconcile update for potentially stale TUF metadata.
trustrootResyncPeriod = flag.Duration("trustroot-resync-period", 24*time.Hour, "The resync period for ClusterImagePolicies. The default is 24h.")

// resourceNames holds the comma-separated list of Kubernetes resources to monitor.
// https://github.com/sigstore/policy-controller/issues/1388
resourceNames = flag.String("resource-names", defaultResourceNames, "Comma-separated list of Kubernetes resources to monitor. WARNING: If parent resources (e.g., Deployments, ReplicaSets) are not monitored, users will not receive policy violation feedback when creating them, even though the Pods they create may violate policies.")
)

func main() {
Expand All @@ -118,6 +129,13 @@ func main() {

flag.Parse()

// Parse and validate resource names
resourceList := parseResourceNames(*resourceNames)
types = buildTypesMap(resourceList, *resourceNames != defaultResourceNames)

// Update ValidResourceNames to match the configured resources
common.ValidResourceNames = sets.NewString(resourceList...)

// If TUF has been disabled do not try to set it up.
if !*disableTUF {
// If they provided an alternate TUF root file to use, read it here.
Expand All @@ -139,11 +157,6 @@ func main() {
ctx = clusterimagepolicy.ToContext(ctx, *policyResyncPeriod)
ctx = pctuf.ToContext(ctx, *trustrootResyncPeriod)

// This must match the set of resources we configure in
// cmd/webhook/main.go in the "types" map.
common.ValidResourceNames = sets.NewString("replicasets", "deployments",
"pods", "cronjobs", "jobs", "statefulsets", "daemonsets")

v := version.GetVersionInfo()
vJSON, _ := v.JSONString()
log.Printf("%v", vJSON)
Expand Down Expand Up @@ -199,17 +212,77 @@ func (c *crdEphemeralContainers) SupportedVerbs() []admissionregistrationv1.Oper
}
}

var types = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{
corev1.SchemeGroupVersion.WithKind("Pod"): &crdEphemeralContainers{GenericCRD: &duckv1.Pod{}},
// types holds the mapping of Kubernetes resource types to their CRD handlers.
// This is initialized in main() based on the --resource-names flag.
var types map[schema.GroupVersionKind]resourcesemantics.GenericCRD

// parseResourceNames parses a comma-separated list of resource names and returns a cleaned slice.
func parseResourceNames(input string) []string {
parts := strings.Split(input, ",")
result := make([]string, 0, len(parts))
for _, part := range parts {
trimmed := strings.TrimSpace(strings.ToLower(part))
if trimmed != "" {
result = append(result, trimmed)
}
}
return result
}

// buildTypesMap constructs the types map based on the provided resource names.
// Resource names must be in lowercase plural form (e.g., "pods", "deployments").
func buildTypesMap(resourceNames []string, isCustom bool) map[schema.GroupVersionKind]resourcesemantics.GenericCRD {
typesMap := make(map[schema.GroupVersionKind]resourcesemantics.GenericCRD)
var validResources, invalidResources []string

for _, name := range resourceNames {
switch name {
case "pods":
typesMap[corev1.SchemeGroupVersion.WithKind("Pod")] = &crdEphemeralContainers{GenericCRD: &duckv1.Pod{}}
validResources = append(validResources, name)
case "replicasets":
typesMap[appsv1.SchemeGroupVersion.WithKind("ReplicaSet")] = &crdNoStatusUpdatesOrDeletes{GenericCRD: &policyduckv1beta1.PodScalable{}}
validResources = append(validResources, name)
case "deployments":
typesMap[appsv1.SchemeGroupVersion.WithKind("Deployment")] = &crdNoStatusUpdatesOrDeletes{GenericCRD: &policyduckv1beta1.PodScalable{}}
validResources = append(validResources, name)
case "statefulsets":
typesMap[appsv1.SchemeGroupVersion.WithKind("StatefulSet")] = &crdNoStatusUpdatesOrDeletes{GenericCRD: &policyduckv1beta1.PodScalable{}}
validResources = append(validResources, name)
case "daemonsets":
typesMap[appsv1.SchemeGroupVersion.WithKind("DaemonSet")] = &crdNoStatusUpdatesOrDeletes{GenericCRD: &duckv1.WithPod{}}
validResources = append(validResources, name)
case "jobs":
typesMap[batchv1.SchemeGroupVersion.WithKind("Job")] = &crdNoStatusUpdatesOrDeletes{GenericCRD: &duckv1.WithPod{}}
validResources = append(validResources, name)
case "cronjobs":
typesMap[batchv1.SchemeGroupVersion.WithKind("CronJob")] = &crdNoStatusUpdatesOrDeletes{GenericCRD: &duckv1.CronJob{}}
typesMap[batchv1beta1.SchemeGroupVersion.WithKind("CronJob")] = &crdNoStatusUpdatesOrDeletes{GenericCRD: &duckv1.CronJob{}}
validResources = append(validResources, name)
default:
invalidResources = append(invalidResources, name)
}
}

appsv1.SchemeGroupVersion.WithKind("ReplicaSet"): &crdNoStatusUpdatesOrDeletes{GenericCRD: &policyduckv1beta1.PodScalable{}},
appsv1.SchemeGroupVersion.WithKind("Deployment"): &crdNoStatusUpdatesOrDeletes{GenericCRD: &policyduckv1beta1.PodScalable{}},
appsv1.SchemeGroupVersion.WithKind("StatefulSet"): &crdNoStatusUpdatesOrDeletes{GenericCRD: &policyduckv1beta1.PodScalable{}},
appsv1.SchemeGroupVersion.WithKind("DaemonSet"): &crdNoStatusUpdatesOrDeletes{GenericCRD: &duckv1.WithPod{}},
batchv1.SchemeGroupVersion.WithKind("Job"): &crdNoStatusUpdatesOrDeletes{GenericCRD: &duckv1.WithPod{}},
if len(invalidResources) > 0 {
log.Printf("WARNING: Invalid resource names will be ignored: %v. Valid options: %v", invalidResources, validResourceTypes)
}

if len(validResources) == 0 {
log.Fatalf("No valid resources specified. At least one resource type must be monitored. Valid options: %v", validResourceTypes)
}

if isCustom {
log.Printf("WARNING: Using custom resource names may result in poor user experience. "+
"If parent resources (e.g., Deployments, ReplicaSets) are not monitored, users will not receive policy violation feedback "+
"when creating those resources, even though the Pods they create may violate policies. "+
"It is recommended to monitor all resource types unless you have a specific reason to exclude some. "+
"Current monitored resources: %v", validResources)
} else {
log.Printf("Monitoring resources: %v", validResources)
}

batchv1.SchemeGroupVersion.WithKind("CronJob"): &crdNoStatusUpdatesOrDeletes{GenericCRD: &duckv1.CronJob{}},
batchv1beta1.SchemeGroupVersion.WithKind("CronJob"): &crdNoStatusUpdatesOrDeletes{GenericCRD: &duckv1.CronJob{}},
return typesMap
}

var typesCIP = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{
Expand Down
Loading
Loading