Skip to content
Merged
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
11 changes: 9 additions & 2 deletions .github/workflows/_container-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ on:
required: false
type: boolean
default: false
image_tag:
description: 'Custom image tag (default: short git SHA or version tag)'
required: false
type: string
default: ''
outputs:
version:
description: 'Image version tag'
Expand All @@ -44,10 +49,12 @@ jobs:
- name: Determine version
id: version
run: |
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
if [[ -n "${{ inputs.image_tag }}" ]]; then
echo "version=${{ inputs.image_tag }}" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref }}" == refs/tags/v* ]]; then
echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
else
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
echo "version=${SHORT_SHA}" >> $GITHUB_OUTPUT
fi

Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ name: E2E

on:
workflow_dispatch:
inputs:
image_tag:
description: 'Custom image tag (default: short git SHA)'
required: false
type: string
default: ''

permissions:
contents: read
Expand All @@ -18,6 +24,7 @@ jobs:
dockerfile: images/openvox-operator/Containerfile
context: '.'
push: true
image_tag: ${{ inputs.image_tag }}

openvox-server:
uses: ./.github/workflows/_container-build.yaml
Expand All @@ -29,6 +36,7 @@ jobs:
dockerfile: images/openvox-server/Containerfile
context: '.'
push: true
image_tag: ${{ inputs.image_tag }}

openvox-code:
uses: ./.github/workflows/_container-build.yaml
Expand All @@ -40,6 +48,7 @@ jobs:
dockerfile: images/openvox-code/Containerfile
context: '.'
push: true
image_tag: ${{ inputs.image_tag }}

openvox-agent:
uses: ./.github/workflows/_container-build.yaml
Expand All @@ -51,6 +60,7 @@ jobs:
dockerfile: images/openvox-agent/Containerfile
context: 'images/openvox-agent'
push: true
image_tag: ${{ inputs.image_tag }}

openvox-mock:
uses: ./.github/workflows/_container-build.yaml
Expand All @@ -62,3 +72,4 @@ jobs:
dockerfile: images/openvox-mock/Containerfile
context: '.'
push: true
image_tag: ${{ inputs.image_tag }}
15 changes: 11 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ OPENVOX_CODE_IMG ?= ghcr.io/slauger/openvox-code:latest
OPENVOX_AGENT_IMG ?= ghcr.io/slauger/openvox-agent:latest
OPENVOX_MOCK_IMG ?= ghcr.io/slauger/openvox-mock:latest
NAMESPACE ?= openvox-system
IMAGE_REGISTRY ?= ghcr.io/slauger
CONTAINER_TOOL ?= $(shell which podman 2>/dev/null || which docker 2>/dev/null)
CONTROLLER_GEN = go tool controller-gen
GOVULNCHECK = go tool govulncheck
Expand Down Expand Up @@ -76,12 +77,19 @@ local-deploy: local-build local-install ## Build images and deploy operator via
@echo "Operator deployed with openvox-operator:$(LOCAL_TAG)"

STACK_NAMESPACE ?= openvox
STACK_VALUES ?= charts/openvox-stack/ci/single-node-values.yaml
STACK_VALUES ?= charts/openvox-stack/values.yaml

##@ Deployment

# When IMAGE_TAG is set (e.g. make install IMAGE_TAG=487ea36), configure
# helm to pull that specific image from the registry.
ifdef IMAGE_TAG
HELM_SET ?= --set image.repository=$(IMAGE_REGISTRY)/openvox-operator --set image.tag=$(IMAGE_TAG) --set image.pullPolicy=Always
STACK_HELM_SET ?= --set config.image.repository=$(IMAGE_REGISTRY)/openvox-server --set config.image.tag=$(IMAGE_TAG) --set config.image.pullPolicy=Always
endif

.PHONY: install
install: manifests ## Install operator via Helm with default images.
install: manifests ## Install operator via Helm (supports IMAGE_TAG=<tag>).
helm upgrade --install openvox-operator charts/openvox-operator \
--namespace $(NAMESPACE) --create-namespace $(HELM_SET)

Expand All @@ -90,7 +98,7 @@ local-install: HELM_SET := --set image.tag=$(LOCAL_TAG) --set image.pullPolicy=N
local-install: install ## Install operator via Helm with local images (no build).

.PHONY: stack
stack: ## Deploy openvox-stack via Helm with default images.
stack: ## Deploy openvox-stack via Helm (supports IMAGE_TAG=<tag>).
helm upgrade --install openvox-stack charts/openvox-stack \
--namespace $(STACK_NAMESPACE) --create-namespace \
--values $(STACK_VALUES) $(STACK_HELM_SET)
Expand Down Expand Up @@ -156,7 +164,6 @@ ci: lint vet test check-manifests vulncheck helm-lint ## Run all CI checks local

##@ E2E

IMAGE_REGISTRY ?= ghcr.io/slauger
IMAGE_TAG ?= $(LOCAL_TAG)

E2E_CHAINSAW = IMAGE_TAG=$(IMAGE_TAG) IMAGE_REGISTRY=$(IMAGE_REGISTRY) $(CHAINSAW) test --config tests/e2e/chainsaw-config.yaml
Expand Down
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,35 @@ Run unit tests:
make test
```

Run E2E tests against the current cluster (requires Docker Desktop Kubernetes or similar). This builds local images, deploys the operator, and runs [Chainsaw](https://kyverno.github.io/chainsaw/) test scenarios (single-node and multi-server):
Run E2E tests against the current cluster. E2E tests require container images in ghcr.io because they run on a kind cluster with the `ImageVolume` feature gate. Build and push images for the current branch via the E2E workflow, then run the tests locally:

```bash
# Build and push images for the current branch (tagged with short git SHA)
gh workflow run e2e.yaml --ref $(git branch --show-current)

# Or use a custom image tag
gh workflow run e2e.yaml --ref $(git branch --show-current) -f image_tag=my-feature

# Wait for the workflow to finish
gh run watch $(gh run list --workflow=e2e.yaml --limit=1 --json databaseId -q '.[0].databaseId')

# Set the image tag to match what CI built
export IMAGE_TAG=$(git describe --always) # or: export IMAGE_TAG=my-feature

# Run E2E tests (uses IMAGE_TAG automatically)
make e2e
```

Subsets of E2E tests can be run separately:

```bash
make e2e-stack # stack deployment tests (single-node, multi-server)
make e2e-agent # agent tests (basic, broken, idempotent, concurrent)
make e2e-integration # integration tests (enc, report, full)
```

See [Testing](docs/development/testing.md) for details.

### Available Targets

| Target | Description |
Expand Down
41 changes: 40 additions & 1 deletion api/v1alpha1/certificateauthority_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,33 @@ type CertificateAuthorityList struct {
Items []CertificateAuthority `json:"items"`
}

// ExternalCASpec configures an external CA running outside the cluster.
// When set, the operator delegates CSR signing and CRL fetching to the external CA URL
// instead of managing its own CA infrastructure.
type ExternalCASpec struct {
// URL is the base URL of the external Puppet/OpenVox CA (e.g. "https://puppet-ca.example.com:8140").
// +kubebuilder:validation:Pattern=`^https?://`
URL string `json:"url"`

// CASecretRef references a Secret containing the CA certificate in key "ca_crt.pem".
// Used to verify the external CA's TLS certificate.
// +optional
CASecretRef string `json:"caSecretRef,omitempty"`

// TLSSecretRef references a Secret containing "tls.crt" and "tls.key" for mTLS
// client authentication against the external CA.
// +optional
TLSSecretRef string `json:"tlsSecretRef,omitempty"`

// InsecureSkipVerify disables TLS certificate verification for the external CA.
// Only use this for testing or when the CA uses a self-signed certificate
// and no CASecretRef is provided.
// +optional
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"`
}

// CertificateAuthoritySpec defines the desired state of CertificateAuthority.
// +kubebuilder:validation:XValidation:rule="!(has(self.external) && has(self.storage) && size(self.storage.size) > 0 && self.storage.size != '1Gi')",message="external and custom storage are mutually exclusive"
type CertificateAuthoritySpec struct {
// TTL is the CA certificate TTL as a duration string.
// Supported units: s (seconds), m (minutes), h (hours), d (days), y (years).
Expand Down Expand Up @@ -87,16 +113,24 @@ type CertificateAuthoritySpec struct {
// IntermediateCA configures an intermediate CA setup.
// +optional
IntermediateCA IntermediateCASpec `json:"intermediateCA,omitempty"`

// External configures an external CA running outside the cluster.
// When set, the operator skips PVC/Job-based CA setup and delegates
// CSR signing and CRL fetching to the external CA URL.
// Mutually exclusive with custom storage settings.
// +optional
External *ExternalCASpec `json:"external,omitempty"`
}

// CertificateAuthorityPhase represents the current lifecycle phase of a CertificateAuthority.
// +kubebuilder:validation:Enum=Pending;Initializing;Ready;Error
// +kubebuilder:validation:Enum=Pending;Initializing;Ready;External;Error
type CertificateAuthorityPhase string

const (
CertificateAuthorityPhasePending CertificateAuthorityPhase = "Pending"
CertificateAuthorityPhaseInitializing CertificateAuthorityPhase = "Initializing"
CertificateAuthorityPhaseReady CertificateAuthorityPhase = "Ready"
CertificateAuthorityPhaseExternal CertificateAuthorityPhase = "External"
CertificateAuthorityPhaseError CertificateAuthorityPhase = "Error"
)

Expand All @@ -111,6 +145,11 @@ type CertificateAuthorityStatus struct {
// +optional
CASecretName string `json:"caSecretName,omitempty"`

// ServiceName is the name of the ClusterIP Service created for internal operator
// communication with the CA (CSR signing, CRL refresh).
// +optional
ServiceName string `json:"serviceName,omitempty"`

// NotAfter is the expiration time of the CA certificate.
// +optional
NotAfter *metav1.Time `json:"notAfter,omitempty"`
Expand Down
20 changes: 20 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,37 @@ spec:
description: EnableInfraCRL enables infrastructure CRL for compile
server revocation.
type: boolean
external:
description: |-
External configures an external CA running outside the cluster.
When set, the operator skips PVC/Job-based CA setup and delegates
CSR signing and CRL fetching to the external CA URL.
Mutually exclusive with custom storage settings.
properties:
caSecretRef:
description: |-
CASecretRef references a Secret containing the CA certificate in key "ca_crt.pem".
Used to verify the external CA's TLS certificate.
type: string
insecureSkipVerify:
description: |-
InsecureSkipVerify disables TLS certificate verification for the external CA.
Only use this for testing or when the CA uses a self-signed certificate
and no CASecretRef is provided.
type: boolean
tlsSecretRef:
description: |-
TLSSecretRef references a Secret containing "tls.crt" and "tls.key" for mTLS
client authentication against the external CA.
type: string
url:
description: URL is the base URL of the external Puppet/OpenVox
CA (e.g. "https://puppet-ca.example.com:8140").
pattern: ^https?://
type: string
required:
- url
type: object
intermediateCA:
description: IntermediateCA configures an intermediate CA setup.
properties:
Expand Down Expand Up @@ -183,6 +214,10 @@ spec:
Plain numbers are interpreted as seconds for backwards compatibility.
type: string
type: object
x-kubernetes-validations:
- message: external and custom storage are mutually exclusive
rule: '!(has(self.external) && has(self.storage) && size(self.storage.size)
> 0 && self.storage.size != ''1Gi'')'
status:
description: CertificateAuthorityStatus defines the observed state of
CertificateAuthority.
Expand Down Expand Up @@ -259,8 +294,14 @@ spec:
- Pending
- Initializing
- Ready
- External
- Error
type: string
serviceName:
description: |-
ServiceName is the name of the ClusterIP Service created for internal operator
communication with the CA (CSR signing, CRL refresh).
type: string
type: object
type: object
served: true
Expand Down
14 changes: 14 additions & 0 deletions charts/openvox-stack/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ servers:
memory: 1Gi
limits:
memory: 2Gi
- name: server
ca: false
server: true
poolRefs: [server]
certificate:
certname: server
dnsAltNames: []
replicas: 2
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
memory: 2Gi

gateway:
name: "" # shared Gateway resource name
Expand Down
Loading
Loading