Skip to content

Commit d122430

Browse files
committed
feat: add end to end tests for the model validation operator, including status and metrics checks
Signed-off-by: Kevin Conner <[email protected]>
1 parent 5b800aa commit d122430

26 files changed

+1736
-538
lines changed

.github/workflows/test-e2e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@ jobs:
4848
- name: Running Test e2e
4949
run: |
5050
go mod tidy
51-
make test-e2e
51+
make test-e2e-ci

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,10 @@ cover.out
1515
manifests/*.yaml
1616
# Don't include generated bundles (will be built in CI/CD at some point)
1717
bundle/
18+
19+
# Generated test keys
20+
testdata/docker/test_*.pub
21+
testdata/docker/test_*.priv
22+
23+
# Generated model signature
24+
testdata/tensorflow_saved_model/model.sig

Makefile

Lines changed: 158 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
3131
# ghcr.io/sigstore/model-validation-operator-bundle:$VERSION and ghcr.io/sigstore/model-validation-operator-catalog:$VERSION.
3232
IMAGE_TAG_BASE ?= ghcr.io/sigstore/model-validation-operator
3333

34+
# IMG defines the image:tag used for the operator.
35+
IMG ?= $(IMAGE_TAG_BASE):v$(VERSION)
36+
3437
# BUNDLE_IMG defines the image:tag used for the bundle.
3538
# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=<some-registry>/<project-name-bundle>:<tag>)
3639
BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION)
@@ -52,8 +55,6 @@ endif
5255
# Set the Operator SDK version to use. By default, what is installed on the system is used.
5356
# This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit.
5457
OPERATOR_SDK_VERSION ?= v1.41.1
55-
# Image URL to use all building/pushing image targets
56-
IMG ?= controller:latest
5758

5859
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
5960
ifeq (,$(shell go env GOBIN))
@@ -118,14 +119,6 @@ vet: ## Run go vet against code.
118119
test: manifests generate fmt vet setup-envtest ## Run tests.
119120
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out
120121

121-
# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.
122-
# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.
123-
# CertManager is installed by default; skip with:
124-
# - CERT_MANAGER_INSTALL_SKIP=true
125-
.PHONY: test-e2e
126-
test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.
127-
go test ./test/e2e/ -v -ginkgo.v
128-
129122
.PHONY: lint
130123
lint: golangci-lint ## Run golangci-lint linter
131124
$(GOLANGCI_LINT) run
@@ -419,3 +412,158 @@ generate-manifests: manifests ## Generate manifests for all environments using g
419412
echo "Generating manifests for $$env environment..."; \
420413
./scripts/generate-manifests.sh $$env manifests; \
421414
done
415+
416+
##@ E2E Test Infrastructure
417+
418+
# E2E Test Variables
419+
E2E_OPERATOR_NAMESPACE ?= model-validation-operator-system
420+
E2E_TEST_NAMESPACE ?= e2e-webhook-test-ns
421+
E2E_TEST_MODEL ?= model-validation-test-model:latest
422+
MODEL_TRANSPARENCY_IMG ?= ghcr.io/sigstore/model-transparency-cli:v1.0.1
423+
CERTMANAGER_VERSION ?= v1.18.2
424+
CERT_MANAGER_YAML ?= https://github.com/cert-manager/cert-manager/releases/download/$(CERTMANAGER_VERSION)/cert-manager.yaml
425+
KIND_CLUSTER ?= kind
426+
427+
# Build and sign test model
428+
.PHONY: e2e-generate-test-keys
429+
e2e-generate-test-keys:
430+
@echo "Generating ECDSA P-256 test keys for model signing..."
431+
@if [ ! -f testdata/docker/test_private_key.priv ]; then \
432+
echo "Generating private key..."; \
433+
openssl ecparam -name prime256v1 -genkey -noout -out testdata/docker/test_private_key.priv; \
434+
fi
435+
@if [ ! -f testdata/docker/test_public_key.pub ]; then \
436+
echo "Generating public key..."; \
437+
openssl ec -in testdata/docker/test_private_key.priv -pubout -out testdata/docker/test_public_key.pub; \
438+
fi
439+
@if [ ! -f testdata/docker/test_invalid_private_key.priv ]; then \
440+
echo "Generating invalid private key for failure tests..."; \
441+
openssl ecparam -name prime256v1 -genkey -noout -out testdata/docker/test_invalid_private_key.priv; \
442+
fi
443+
@if [ ! -f testdata/docker/test_invalid_public_key.pub ]; then \
444+
echo "Generating invalid public key for failure tests..."; \
445+
openssl ec -in testdata/docker/test_invalid_private_key.priv -pubout -out testdata/docker/test_invalid_public_key.pub; \
446+
fi
447+
448+
.PHONY: e2e-sign-test-model
449+
e2e-sign-test-model: e2e-generate-test-keys
450+
@echo "Signing test model with private key..."
451+
@# Remove public key from model directory before signing to avoid including it in signature
452+
@rm -f testdata/tensorflow_saved_model/test_public_key.pub
453+
$(CONTAINER_TOOL) run --rm \
454+
-v $(PWD)/testdata/tensorflow_saved_model:/model \
455+
-v $(PWD)/testdata/docker/test_private_key.priv:/test_private_key.priv \
456+
--entrypoint="" \
457+
ghcr.io/sigstore/model-transparency-cli:v1.0.1 \
458+
/usr/local/bin/model_signing sign key /model \
459+
--private_key /test_private_key.priv \
460+
--signature /model/model.sig
461+
462+
.PHONY: e2e-build-test-model
463+
e2e-build-test-model: e2e-sign-test-model
464+
@echo "Building test model image..."
465+
cd testdata && $(CONTAINER_TOOL) build --no-cache -t $(E2E_TEST_MODEL) -f docker/test-model.Dockerfile .
466+
467+
# install and uninstall cert-manager for tests
468+
469+
.PHONY: e2e-install-certmanager
470+
e2e-install-certmanager:
471+
@echo "Installing cert-manager..."
472+
$(KUBECTL) apply -f $(CERT_MANAGER_YAML)
473+
@echo "Waiting for cert-manager to be ready..."
474+
$(KUBECTL) wait --for=condition=Available deployment -n cert-manager --all --timeout=120s
475+
476+
.PHONY: e2e-uninstall-certmanager
477+
e2e-uninstall-certmanager: ## Uninstall cert-manager
478+
@echo "Uninstalling cert-manager..."
479+
-$(KUBECTL) delete -f $(CERT_MANAGER_YAML)
480+
481+
# Load test images into the kind cluster
482+
483+
.PHONY: e2e-build-image
484+
e2e-build-image:
485+
$(CONTAINER_TOOL) build -t $(IMG) -f $(CONTAINER_FILE) .
486+
487+
.PHONY: e2e-load-images
488+
e2e-load-images: e2e-build-image e2e-build-test-model
489+
@echo "Pulling model-transparency-cli image..."
490+
$(CONTAINER_TOOL) pull $(MODEL_TRANSPARENCY_IMG)
491+
@echo "Loading manager image into Kind cluster..."
492+
$(KIND) load docker-image -n $(KIND_CLUSTER) $(IMG)
493+
@echo "Loading model-transparency-cli image into Kind cluster..."
494+
$(KIND) load docker-image -n $(KIND_CLUSTER) $(MODEL_TRANSPARENCY_IMG)
495+
@echo "Loading test model image into Kind cluster..."
496+
$(KIND) load docker-image -n $(KIND_CLUSTER) $(E2E_TEST_MODEL)
497+
498+
# Setup test environment (namespaces, local models on kind cluster, operator)
499+
500+
.PHONY: e2e-setup-namespaces
501+
e2e-setup-namespaces:
502+
@echo "Creating operator namespace..."
503+
$(KUBECTL) create ns $(E2E_OPERATOR_NAMESPACE) || true
504+
@echo "Labeling operator namespace with restricted security policy..."
505+
$(KUBECTL) label --overwrite ns $(E2E_OPERATOR_NAMESPACE) pod-security.kubernetes.io/enforce=restricted
506+
@echo "Labeling operator namespace to be ignored by webhook..."
507+
$(KUBECTL) label --overwrite ns $(E2E_OPERATOR_NAMESPACE) validation.ml.sigstore.dev/ignore=true
508+
@echo "Creating test namespace..."
509+
$(KUBECTL) create ns $(E2E_TEST_NAMESPACE) || true
510+
511+
.PHONY: e2e-setup-model-data
512+
e2e-setup-model-data: e2e-load-images e2e-setup-namespaces
513+
@echo "Cleaning up any existing model data DaemonSet..."
514+
-$(KUBECTL) delete daemonset model-data-setup -n $(E2E_TEST_NAMESPACE) 2>/dev/null || true
515+
@echo "Waiting for cleanup to complete..."
516+
@sleep 5
517+
@echo "Deploying model data setup DaemonSet..."
518+
$(KUBECTL) apply -f test/e2e/testdata/model-data-daemonset.yaml
519+
@echo "Waiting for model data to be available on all nodes..."
520+
$(KUBECTL) rollout status daemonset/model-data-setup -n $(E2E_TEST_NAMESPACE) --timeout=120s
521+
522+
.PHONY: e2e-deploy-operator
523+
e2e-deploy-operator: e2e-setup-namespaces deploy
524+
@echo "E2E operator deployment complete"
525+
526+
.PHONY: e2e-wait-operator
527+
e2e-wait-operator: ## Wait for operator pod to be ready
528+
@echo "Waiting for controller pod to be ready..."
529+
$(KUBECTL) wait --for=condition=Ready pod -l control-plane=controller-manager -n $(E2E_OPERATOR_NAMESPACE) --timeout=120s
530+
531+
# test environment setup and teardown - certmanager, operator and test model for testing
532+
533+
.PHONY: e2e-setup
534+
e2e-setup: e2e-install-certmanager e2e-setup-model-data e2e-deploy-operator e2e-wait-operator ## Complete e2e test setup
535+
@echo "E2E test environment setup complete"
536+
537+
.PHONY: e2e-cleanup-resources
538+
e2e-cleanup-resources: ## Clean up test resources before removing operator
539+
@echo "Cleaning up test resources..."
540+
-$(KUBECTL) delete pods --all -n $(E2E_TEST_NAMESPACE) --timeout=30s
541+
-$(KUBECTL) delete modelvalidations --all -n $(E2E_TEST_NAMESPACE) --timeout=30s
542+
-$(KUBECTL) delete daemonset model-data-setup -n $(E2E_TEST_NAMESPACE) --timeout=30s
543+
544+
.PHONY: e2e-teardown
545+
e2e-teardown: e2e-cleanup-resources undeploy e2e-uninstall-certmanager
546+
@echo "Tearing down e2e test environment..."
547+
-$(KUBECTL) delete ns $(E2E_OPERATOR_NAMESPACE) --timeout=60s
548+
-$(KUBECTL) delete ns $(E2E_TEST_NAMESPACE) --timeout=60s
549+
550+
# run e2e tests
551+
552+
.PHONY: test-e2e
553+
test-e2e: manifests generate fmt vet ## Run the e2e tests, no setup and teardown. Expects the operator to be deployed.
554+
@echo "Running e2e tests (assumes infrastructure is already set up)..."
555+
go test ./test/e2e/ -v -ginkgo.v
556+
557+
.PHONY: test-e2e-full
558+
test-e2e-full: manifests generate fmt vet e2e-setup ## Run e2e tests with setup and teardown
559+
@echo "Running e2e tests with full infrastructure setup..."
560+
go test ./test/e2e/ -v -ginkgo.v; \
561+
TEST_RESULT=$$?; \
562+
$(MAKE) e2e-teardown; \
563+
exit $$TEST_RESULT
564+
565+
.PHONY: test-e2e-ci
566+
test-e2e-ci: manifests generate fmt vet e2e-setup ## Run the e2e tests, with setup. No teardown as the CI workflow will throw away kind
567+
@echo "Running e2e tests with infrastructure setup for CI..."
568+
go test ./test/e2e/ -v -ginkgo.v
569+

api/v1alpha1/modelvalidation_types.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,19 @@ type PkiConfig struct {
4646
CertificateAuthority string `json:"certificateAuthority,omitempty"`
4747
}
4848

49-
// PrivateKeyConfig defines the private key verification configuration
50-
// for validating model signatures using a local private key
51-
type PrivateKeyConfig struct {
52-
// Path to the private key.
49+
// PublicKeyConfig defines the public key verification configuration
50+
// for validating model signatures using a local public key
51+
type PublicKeyConfig struct {
52+
// Path to the public key.
5353
KeyPath string `json:"keyPath,omitempty"`
5454
}
5555

5656
// ValidationConfig defines the various methods available for validating model signatures.
5757
// At least one validation method must be specified.
5858
type ValidationConfig struct {
59-
SigstoreConfig *SigstoreConfig `json:"sigstoreConfig,omitempty"`
60-
PkiConfig *PkiConfig `json:"pkiConfig,omitempty"`
61-
PrivateKeyConfig *PrivateKeyConfig `json:"privateKeyConfig,omitempty"`
59+
SigstoreConfig *SigstoreConfig `json:"sigstoreConfig,omitempty"`
60+
PkiConfig *PkiConfig `json:"pkiConfig,omitempty"`
61+
PublicKeyConfig *PublicKeyConfig `json:"publicKeyConfig,omitempty"`
6262
}
6363

6464
// ModelValidationSpec defines the desired state of ModelValidation
@@ -145,8 +145,8 @@ func (mv *ModelValidation) GetAuthMethod() string {
145145
return "sigstore"
146146
} else if mv.Spec.Config.PkiConfig != nil {
147147
return "pki"
148-
} else if mv.Spec.Config.PrivateKeyConfig != nil {
149-
return "private-key"
148+
} else if mv.Spec.Config.PublicKeyConfig != nil {
149+
return "public-key"
150150
}
151151
return "unknown"
152152
}
@@ -167,9 +167,9 @@ func (vc *ValidationConfig) GetConfigHash() string {
167167
} else if vc.PkiConfig != nil {
168168
hasher.Write([]byte("pki"))
169169
hasher.Write([]byte(vc.PkiConfig.CertificateAuthority))
170-
} else if vc.PrivateKeyConfig != nil {
171-
hasher.Write([]byte("privatekey"))
172-
hasher.Write([]byte(vc.PrivateKeyConfig.KeyPath))
170+
} else if vc.PublicKeyConfig != nil {
171+
hasher.Write([]byte("publickey"))
172+
hasher.Write([]byte(vc.PublicKeyConfig.KeyPath))
173173
}
174174

175175
return fmt.Sprintf("%x", hasher.Sum(nil))[:16] // Use first 16 chars for brevity

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/ml.sigstore.dev_modelvalidations.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@ spec:
6767
description: Path to the certificate authority for PKI.
6868
type: string
6969
type: object
70-
privateKeyConfig:
70+
publicKeyConfig:
7171
description: |-
72-
PrivateKeyConfig defines the private key verification configuration
73-
for validating model signatures using a local private key
72+
PublicKeyConfig defines the public key verification configuration
73+
for validating model signatures using a local public key
7474
properties:
7575
keyPath:
76-
description: Path to the private key.
76+
description: Path to the public key.
7777
type: string
7878
type: object
7979
sigstoreConfig:

examples/unsigned.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ spec:
77
config:
88
# pkiConfig:
99
# certificateAuthority: /path/to/ca.crt
10-
privateKeyConfig:
10+
publicKeyConfig:
1111
keyPath: /root/pub.key
1212
# sigstoreConfig:
1313
# certificateIdentity: "https://sigstore.example.com/certificate"

examples/verify.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ spec:
99
config:
1010
# pkiConfig:
1111
# certificateAuthority: /path/to/ca.crt
12-
# privateKeyConfig:
12+
# publicKeyConfig:
1313
# keyPath: /root/pub.key
1414
sigstoreConfig:
1515
certificateIdentity: "https://github.com/sigstore/model-validation-operator/.github/workflows/sign-model.yaml@refs/tags/v0.0.2"

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/onsi/gomega v1.36.1
1111
github.com/prometheus/client_golang v1.19.1
1212
github.com/prometheus/client_model v0.6.1
13+
github.com/prometheus/common v0.55.0
1314
github.com/stretchr/testify v1.9.0
1415
golang.org/x/time v0.7.0
1516
k8s.io/api v0.32.1
@@ -58,7 +59,6 @@ require (
5859
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
5960
github.com/pkg/errors v0.9.1 // indirect
6061
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
61-
github.com/prometheus/common v0.55.0 // indirect
6262
github.com/prometheus/procfs v0.15.1 // indirect
6363
github.com/spf13/cobra v1.8.1 // indirect
6464
github.com/spf13/pflag v1.0.5 // indirect

internal/webhooks/pod_webhook.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,12 @@ func validationConfigToArgs(logger logr.Logger, cfg v1alpha1.ValidationConfig, s
133133
return res
134134
}
135135

136-
if cfg.PrivateKeyConfig != nil {
137-
logger.Info("found private-key config")
136+
if cfg.PublicKeyConfig != nil {
137+
logger.Info("found public-key config")
138138
res = append(res,
139139
"key",
140140
fmt.Sprintf("--signature=%s", signaturePath),
141-
"--public_key", cfg.PrivateKeyConfig.KeyPath,
141+
"--public_key", cfg.PublicKeyConfig.KeyPath,
142142
)
143143
return res
144144
}

0 commit comments

Comments
 (0)