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
45 changes: 45 additions & 0 deletions api/v1/gatewayapi_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,51 @@ type GatewayAPISpec struct {
// does not yet have any version of those CRDs.
// +optional
CRDManagement *CRDManagement `json:"crdManagement,omitempty"`

// Extensions enables and configures Tigera-built add-ons that sit on top of the
// Gateway API data plane. Each add-on is opt-in: an unset Extensions, an unset
// add-on field, and an empty add-on object all leave the add-on disabled.
// +optional
Extensions *GatewayAPIExtensions `json:"extensions,omitempty"`
}

// GatewayAPIExtensions enables and configures Tigera-built Gateway API add-ons.
type GatewayAPIExtensions struct {
// WAF enables and configures the Tigera Web Application Firewall (Coraza WASM
// + applicationlayer reconcilers). Default-off semantics: when WAF is nil,
// when WAF.State is nil, and when WAF.State is "Disabled", the operator does
// not render the WAF env vars or RBAC on calico-kube-controllers. Set
// WAF.State = "Enabled" to turn the feature on. See design
// `tigera/designs#25` (PMREQ-384) for the full surface.
// +optional
WAF *WAFExtensionSpec `json:"waf,omitempty"`
}

// WAFExtensionSpec configures the WAF Gateway API add-on.
type WAFExtensionSpec struct {
// State turns the WAF Gateway API add-on on or off. Default (nil or
// "Disabled") means the operator does not render the WAF surface on
// calico-kube-controllers. Set to "Enabled" to opt in.
// +optional
State *WAFExtensionState `json:"state,omitempty"`
}

// WAFExtensionState is the on/off enum for the WAF Gateway API add-on.
// +kubebuilder:validation:Enum=Enabled;Disabled
type WAFExtensionState string

const (
WAFExtensionStateEnabled WAFExtensionState = "Enabled"
WAFExtensionStateDisabled WAFExtensionState = "Disabled"
)

// IsWAFGatewayExtensionEnabled returns true if spec.extensions.waf.state == Enabled.
// Unset Extensions, unset WAF, unset State, and explicit Disabled all return false.
func (s *GatewayAPISpec) IsWAFGatewayExtensionEnabled() bool {
if s == nil || s.Extensions == nil || s.Extensions.WAF == nil || s.Extensions.WAF.State == nil {
return false
}
return *s.Extensions.WAF.State == WAFExtensionStateEnabled
}

type GatewayClassSpec struct {
Expand Down
45 changes: 45 additions & 0 deletions api/v1/zz_generated.deepcopy.go

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

99 changes: 91 additions & 8 deletions pkg/controller/installation/core_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import (
"github.com/tigera/operator/pkg/common/discovery"
"github.com/tigera/operator/pkg/components"
"github.com/tigera/operator/pkg/controller/certificatemanager"
"github.com/tigera/operator/pkg/controller/gatewayapi"
"github.com/tigera/operator/pkg/controller/ippool"
"github.com/tigera/operator/pkg/controller/k8sapi"
"github.com/tigera/operator/pkg/controller/migration"
Expand All @@ -78,6 +79,7 @@ import (
"github.com/tigera/operator/pkg/imports/admission"
"github.com/tigera/operator/pkg/imports/crds"
"github.com/tigera/operator/pkg/render"
"github.com/tigera/operator/pkg/render/applicationlayer"
rcertificatemanagement "github.com/tigera/operator/pkg/render/certificatemanagement"
relasticsearch "github.com/tigera/operator/pkg/render/common/elasticsearch"
"github.com/tigera/operator/pkg/render/common/networkpolicy"
Expand Down Expand Up @@ -213,6 +215,13 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error {
}

// Watch for changes to KubeControllersConfiguration.
// Watch GatewayAPI: spec.extensions.waf.state gates the WAF v3 surface on
// calico-kube-controllers. See design tigera/designs#25 (PMREQ-384) §Gating.
if err := c.WatchObject(&operatorv1.GatewayAPI{}, &handler.EnqueueRequestForObject{}); err != nil {
log.V(5).Info("Failed to create GatewayAPI watch", "err", err)
return fmt.Errorf("core-controller failed to watch operator GatewayAPI resource: %w", err)
}

err = c.WatchObject(&v3.KubeControllersConfiguration{}, &handler.EnqueueRequestForObject{})
if err != nil {
return fmt.Errorf("tigera-installation-controller failed to watch KubeControllersConfiguration resource: %w", err)
Expand Down Expand Up @@ -1361,18 +1370,57 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile

}

// Read the GatewayAPI CR (if present) to decide whether to render the WAF
// v3 (Gateway API add-on) surface — env vars, RBAC, applicationlayer
// reconciler, and the in-process admission webhook — on
// calico-kube-controllers. Default-off: if no GatewayAPI CR exists or
// spec.extensions.waf.state != Enabled, the WAF surface is not rendered.
// See design tigera/designs#25 (PMREQ-384) §Gating.
wafGatewayExtensionEnabled := false
if gatewayAPI, msg, err := gatewayapi.GetGatewayAPI(ctx, r.client); err == nil {
wafGatewayExtensionEnabled = gatewayAPI.Spec.IsWAFGatewayExtensionEnabled()
} else if !apierrors.IsNotFound(err) {
// Mirrors the GatewayAPI controller's handling: a read error or a
// duplicate default/tigera-secure pair degrades rather than guessing.
r.status.SetDegraded(operatorv1.ResourceReadError, msg, err, reqLogger)
return reconcile.Result{}, err
}

// When the WAF v3 surface is enabled, issue the serving cert for the
// in-process WAF admission webhook (hosted by calico-kube-controllers,
// fronted by the tigera-waf-webhook Service). It is materialized into
// calico-system alongside the other kube-controllers certs below and mounted
// into the Pod by the kube-controllers render.
var wafWebhookTLS certificatemanagement.KeyPairInterface
Comment thread
rene-dekker marked this conversation as resolved.
if wafGatewayExtensionEnabled {
wafWebhookTLS, err = certificateManager.GetOrCreateKeyPair(
r.client,
applicationlayer.WAFWebhookServerTLSSecretName,
common.OperatorNamespace(),
dns.GetServiceDNSNames(applicationlayer.WAFWebhookServiceName, common.CalicoNamespace, r.clusterDomain))
if err != nil {
r.status.SetDegraded(operatorv1.ResourceCreateError, "Error creating WAF admission webhook TLS certificate", err, reqLogger)
return reconcile.Result{}, err
}
}

keyPairOptions := []rcertificatemanagement.KeyPairOption{
rcertificatemanagement.NewKeyPairOption(typhaNodeTLS.NodeSecret, true, true),
rcertificatemanagement.NewKeyPairOption(nodePrometheusTLS, true, true),
rcertificatemanagement.NewKeyPairOption(typhaNodeTLS.TyphaSecret, true, true),
rcertificatemanagement.NewKeyPairOption(typhaNodeTLS.TyphaSecretNonClusterHost, true, true),
rcertificatemanagement.NewKeyPairOption(kubeControllerTLS, true, true),
// Nil when the WAF v3 surface is disabled; the certificate-management
// render skips nil key pairs.
rcertificatemanagement.NewKeyPairOption(wafWebhookTLS, true, true),
}

components = append(components,
rcertificatemanagement.CertificateManagement(&rcertificatemanagement.Config{
Namespace: common.CalicoNamespace,
ServiceAccounts: []string{render.CalicoNodeObjectName, render.TyphaServiceAccountName, kubecontrollers.KubeControllerServiceAccount},
KeyPairOptions: []rcertificatemanagement.KeyPairOption{
rcertificatemanagement.NewKeyPairOption(typhaNodeTLS.NodeSecret, true, true),
rcertificatemanagement.NewKeyPairOption(nodePrometheusTLS, true, true),
rcertificatemanagement.NewKeyPairOption(typhaNodeTLS.TyphaSecret, true, true),
rcertificatemanagement.NewKeyPairOption(typhaNodeTLS.TyphaSecretNonClusterHost, true, true),
rcertificatemanagement.NewKeyPairOption(kubeControllerTLS, true, true),
},
TrustedBundle: typhaNodeTLS.TrustedBundle,
KeyPairOptions: keyPairOptions,
TrustedBundle: typhaNodeTLS.TrustedBundle,
}))

// Check if non-cluster host feature is enabled.
Expand Down Expand Up @@ -1606,6 +1654,32 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile
components = append(components, render.CSI(&csiCfg))

// Build a configuration for rendering calico/kube-controllers.
// Provision a dedicated WAF wasm pull secret so the WAF reconciler
// replicates it into tenant namespaces without clashing with the
// operator-managed tigera-pull-secret the GatewayAPI render also copies
// there (EV-6386). The EnvoyExtensionPolicy image source takes a single
// pullSecretRef, so the registry auths of all Installation pull secrets
// are merged into it rather than picking one.
var wasmPullSecret *corev1.Secret
if wafGatewayExtensionEnabled && len(pullSecrets) > 0 {
var skipped []string
wasmPullSecret, skipped = kubecontrollers.MergeWAFPullSecret(pullSecrets)
if len(skipped) > 0 {
reqLogger.Info("Skipped unparseable imagePullSecrets when building the WAF wasm pull secret", "skipped", skipped)
}
}
// Provision the dedicated WAF wasm CA-bundle ConfigMap as a renamed copy of
// the trusted CA bundle, so the WAF reconciler replicates it into tenant
// namespaces for the Coraza wasm OCI registry TLS check without clashing with
// the operator-managed tigera-ca-bundle the GatewayAPI render also copies
// there (EV-6386). The dedicated source was previously a TODO; the full
// TrustedBundle (not the RO interface the kube-controllers render sees) is
// available here, so build it in the core controller.
var wasmCACert *corev1.ConfigMap
if wafGatewayExtensionEnabled {
wasmCACert = typhaNodeTLS.TrustedBundle.ConfigMap(common.CalicoNamespace)
wasmCACert.Name = kubecontrollers.WASMCACertName
}
kubeControllersCfg := kubecontrollers.KubeControllersConfiguration{
K8sServiceEp: k8sapi.Endpoint,
K8sServiceEpPodNetwork: k8sapi.PodNetworkEndpoint,
Expand All @@ -1619,6 +1693,15 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile
TrustedBundle: typhaNodeTLS.TrustedBundle,
Namespace: common.CalicoNamespace,
BindingNamespaces: []string{common.CalicoNamespace},
WAFGatewayExtensionEnabled: wafGatewayExtensionEnabled,
WAFWebhookServerTLS: wafWebhookTLS,
WASMPullSecret: wasmPullSecret,
WASMCACert: wasmCACert,
// The webhook Service + ValidatingWebhookConfiguration are rendered by
// the kube-controllers component (and deleted when the WAF extension is
// disabled); the caBundle is the operator CA that issued the serving
// cert above.
WAFWebhookCABundle: certificateManager.KeyPair().GetCertificatePEM(),
}
components = append(components, kubecontrollers.NewCalicoKubeControllers(&kubeControllersCfg))

Expand Down
25 changes: 25 additions & 0 deletions pkg/imports/crds/operator/operator.tigera.io_gatewayapis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,31 @@ spec:
- name
- namespace
type: object
extensions:
description: |-
Extensions enables and configures Tigera-built add-ons that sit on top of the
Gateway API data plane. Each add-on is opt-in: an unset Extensions, an unset
add-on field, and an empty add-on object all leave the add-on disabled.
properties:
waf:
description: |-
WAF enables and configures the Tigera Web Application Firewall (Coraza WASM
when WAF.State is nil, and when WAF.State is "Disabled", the operator does
not render the WAF env vars or RBAC on calico-kube-controllers. Set
WAF.State = "Enabled" to turn the feature on. See design
`tigera/designs#25` (PMREQ-384) for the full surface.
properties:
state:
description: |-
State turns the WAF Gateway API add-on on or off. Default (nil or
"Disabled") means the operator does not render the WAF surface on
calico-kube-controllers. Set to "Enabled" to opt in.
enum:
- Enabled
- Disabled
type: string
type: object
type: object
gatewayCertgenJob:
description: Allows customization of the gateway certgen job.
properties:
Expand Down
Loading
Loading