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
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ NODE_DRIVER_REGISTRAR_IMAGE := calico/node-driver-registrar
GOLDMANE_IMAGE := calico/goldmane
WHISKER_IMAGE := calico/whisker
WHISKER_BACKEND_IMAGE := calico/whisker-backend
ENVOY_GATEWAY_IMAGE := calico/envoy-gateway

.PHONY: calico-node.tar
calico-node.tar:
Expand Down Expand Up @@ -444,6 +445,11 @@ calico-whisker-backend.tar:
docker pull $(FV_IMAGE_REGISTRY)/$(WHISKER_BACKEND_IMAGE):$(VERSION_TAG)
docker save --output $@ $(WHISKER_BACKEND_IMAGE):$(VERSION_TAG)

.PHONY: calico-envoy-gateway.tar
calico-envoy-gateway.tar:
docker pull $(FV_IMAGE_REGISTRY)/$(ENVOY_GATEWAY_IMAGE):$(VERSION_TAG)
docker save --output $@ $(ENVOY_GATEWAY_IMAGE):$(VERSION_TAG)

IMAGE_TARS := calico-node.tar \
calico-apiserver.tar \
calico-cni.tar \
Expand All @@ -454,7 +460,8 @@ IMAGE_TARS := calico-node.tar \
calico-node-driver-registrar.tar \
calico-goldmane.tar \
calico-whisker.tar \
calico-whisker-backend.tar
calico-whisker-backend.tar \
calico-envoy-gateway.tar

load-container-images: ./test/load_images_on_kind_cluster.sh $(IMAGE_TARS)
# Load the latest tar files onto the currently running kind cluster.
Expand Down
3 changes: 3 additions & 0 deletions pkg/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ const (
KubeControllersDeploymentName = "calico-kube-controllers"
WindowsDaemonSetName = "calico-node-windows"

// Gateway API related const
TigeraGatewayNamespace = "tigera-gateway"

// Monitor + Prometheus related const
TigeraPrometheusNamespace = "tigera-prometheus"

Expand Down
37 changes: 35 additions & 2 deletions pkg/controller/gatewayapi/gatewayapi_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
Expand All @@ -40,12 +41,14 @@ import (
envoyapi "github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/go-logr/logr"
operatorv1 "github.com/tigera/operator/api/v1"
"github.com/tigera/operator/pkg/common"
"github.com/tigera/operator/pkg/controller/options"
"github.com/tigera/operator/pkg/controller/status"
"github.com/tigera/operator/pkg/controller/utils"
"github.com/tigera/operator/pkg/controller/utils/imageset"
"github.com/tigera/operator/pkg/ctrlruntime"
"github.com/tigera/operator/pkg/render"
"github.com/tigera/operator/pkg/render/common/networkpolicy"
"github.com/tigera/operator/pkg/render/gatewayapi"
)

Expand All @@ -61,6 +64,8 @@ var log = logf.Log.WithName("controller_gatewayapi")
// Start Watches within the Add function for any resources that this controller creates or monitors. This will trigger
// calls to Reconcile() when an instance of one of the watched resources is modified.
func Add(mgr manager.Manager, opts options.ControllerOptions) error {
tierWatchReady := &utils.ReadyFlag{}

r := &ReconcileGatewayAPI{
client: mgr.GetClient(),
scheme: mgr.GetScheme(),
Expand Down Expand Up @@ -89,6 +94,12 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error {
return fmt.Errorf("gatewayapi-controller failed to watch Installation resource: %w", err)
}

go utils.WaitToAddTierWatch(networkpolicy.CalicoTierName, c, opts.K8sClientset, log, tierWatchReady)
go utils.WaitToAddNetworkPolicyWatches(c, opts.K8sClientset, log, []types.NamespacedName{
{Name: gatewayapi.GatewayControllerPolicyName, Namespace: common.TigeraGatewayNamespace},
{Name: gatewayapi.GatewayCertgenPolicyName, Namespace: common.TigeraGatewayNamespace},
})

// Perform periodic reconciliation. This acts as a backstop to catch reconcile issues,
// and also makes sure we spot when things change that might not trigger a reconciliation.
if err = utils.AddPeriodicReconcile(c, utils.PeriodicReconcileTime, &handler.EnqueueRequestForObject{}); err != nil {
Expand Down Expand Up @@ -416,12 +427,34 @@ func (r *ReconcileGatewayAPI) Reconcile(ctx context.Context, request reconcile.R
return reconcile.Result{}, err
}

err = r.newComponentHandler(log, r.client, r.scheme, gatewayAPI).CreateOrUpdateOrDelete(ctx, nonCRDComponent, r.status)
hdler := r.newComponentHandler(log, r.client, r.scheme, gatewayAPI)
err = hdler.CreateOrUpdateOrDelete(ctx, nonCRDComponent, r.status)
if err != nil {
r.status.SetDegraded(operatorv1.ResourceCreateError, "Error rendering GatewayAPI resources", err, log)
return reconcile.Result{}, err
}

// v3 NetworkPolicy will fail to reconcile if the calico-system Tier does not exist or if
// the v3 API is not yet available. Render policies separately so this does not block the
// rest of the gateway resources.
includeV3NetworkPolicy := false
if err := r.client.Get(ctx, client.ObjectKey{Name: networkpolicy.CalicoTierName}, &v3.Tier{}); err != nil {
if !errors.IsNotFound(err) && !meta.IsNoMatchError(err) {
r.status.SetDegraded(operatorv1.ResourceReadError, "Error querying calico-system tier", err, reqLogger)
return reconcile.Result{}, err
}
} else {
includeV3NetworkPolicy = true
}

if includeV3NetworkPolicy {
policyComponent := gatewayapi.GatewayPolicy(gatewayConfig)
if err = hdler.CreateOrUpdateOrDelete(ctx, policyComponent, r.status); err != nil {
r.status.SetDegraded(operatorv1.ResourceCreateError, "Error rendering GatewayAPI network policies", err, log)
return reconcile.Result{}, err
}
}

// Clear the degraded bit if we've reached this far.
r.status.ClearDegraded()

Expand Down Expand Up @@ -496,6 +529,6 @@ func (r *ReconcileGatewayAPI) getPolicySyncPathPrefix(fcSpec *v3.FelixConfigurat
// The bool return value indicates if the finalizer is Set
func (r *ReconcileGatewayAPI) maintainFinalizer(ctx context.Context, gatewayAPI client.Object) (bool, error) {
// These objects require graceful termination before the CNI plugin is torn down.
gatewayAPIDeployment := v1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "envoy-gateway", Namespace: "tigera-gateway"}}
gatewayAPIDeployment := v1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "envoy-gateway", Namespace: common.TigeraGatewayNamespace}}
return utils.MaintainInstallationFinalizer(ctx, r.client, gatewayAPI, render.GatewayAPIFinalizer, &gatewayAPIDeployment)
}
102 changes: 97 additions & 5 deletions pkg/render/gatewayapi/gateway_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ import (
"sync"

envoyapi "github.com/envoyproxy/gateway/api/v1alpha1"
v3 "github.com/tigera/api/pkg/apis/projectcalico/v3"
operatorv1 "github.com/tigera/operator/api/v1"
"github.com/tigera/operator/pkg/common"
"github.com/tigera/operator/pkg/components"
"github.com/tigera/operator/pkg/render"
rcomp "github.com/tigera/operator/pkg/render/common/components"
rmeta "github.com/tigera/operator/pkg/render/common/meta"
networkpolicy "github.com/tigera/operator/pkg/render/common/networkpolicy"
"github.com/tigera/operator/pkg/render/common/secret"
"github.com/tigera/operator/pkg/render/common/securitycontext"
admissionregv1 "k8s.io/api/admissionregistration/v1"
Expand Down Expand Up @@ -90,7 +92,16 @@ const (
EnvoyGatewayConfigKey = "envoy-gateway.yaml"
EnvoyGatewayDeploymentContainerName = "envoy-gateway"
EnvoyGatewayJobContainerName = "envoy-gateway-certgen"
GatewayControllerPolicyName = networkpolicy.CalicoComponentPolicyPrefix + "gateway-api-controller-access"
GatewayCertgenPolicyName = networkpolicy.CalicoComponentPolicyPrefix + "gateway-api-certgen-access"
wafFilterName = "waf-http-filter"

// Envoy gateway controller serving ports.
EnvoyGatewayPortGRPC = 18000
EnvoyGatewayPortRateLimit = 18001
EnvoyGatewayPortWasm = 18002
EnvoyGatewayPortMetrics = 19001
EnvoyGatewayPortWebhook = 9443
)

var (
Expand Down Expand Up @@ -424,6 +435,23 @@ func GatewayAPIImplementationComponent(cfg *GatewayAPIImplementationConfig) rend
}
}

// GatewayPolicy returns a Component that renders the calico-system tier network policies
// for the gateway control plane. This is rendered separately from the main gateway component
// so that a failure to create v3 NetworkPolicy resources does not block the rest of the
// gateway resources. We intentionally do not include a namespace-scoped default deny here
// because Gateway resources dynamically create Envoy proxy pods whose traffic patterns
// (listener ports, backend destinations) are determined by the customer's Gateway and
// HTTPRoute configuration.
func GatewayPolicy(cfg *GatewayAPIImplementationConfig) render.Component {
return render.NewPassthrough(
[]client.Object{
gatewayCertgenAllowCalicoSystemPolicy(cfg.Installation.KubernetesProvider.IsOpenShift()),
gatewayControllerAllowCalicoSystemPolicy(cfg.Installation.KubernetesProvider.IsOpenShift()),
},
nil,
)
}

func (pr *gatewayAPIImplementationComponent) ResolveImages(is *operatorv1.ImageSet) error {
reg := pr.cfg.Installation.Registry
path := pr.cfg.Installation.ImagePath
Expand Down Expand Up @@ -671,7 +699,7 @@ func (pr *gatewayAPIImplementationComponent) Objects() ([]client.Object, []clien
},
ObjectMeta: metav1.ObjectMeta{
Name: gcName,
Namespace: "tigera-gateway",
Namespace: common.TigeraGatewayNamespace,
},
},
)
Expand Down Expand Up @@ -706,7 +734,7 @@ func (pr *gatewayAPIImplementationComponent) envoyProxyConfig(className string,
envoyProxy.APIVersion = "gateway.envoyproxy.io/v1alpha1"
}
envoyProxy.Name = className
envoyProxy.Namespace = "tigera-gateway"
envoyProxy.Namespace = common.TigeraGatewayNamespace
if envoyProxy.Spec.Provider == nil {
envoyProxy.Spec.Provider = &envoyapi.EnvoyProxyProvider{}
}
Expand Down Expand Up @@ -1052,7 +1080,7 @@ func (pr *gatewayAPIImplementationComponent) gatewayClass(className, controllerN
TypeMeta: metav1.TypeMeta{Kind: "GatewayClass", APIVersion: "gateway.networking.k8s.io/v1"},
ObjectMeta: metav1.ObjectMeta{
Name: className,
Namespace: "tigera-gateway",
Namespace: common.TigeraGatewayNamespace,
},
Spec: gapi.GatewayClassSpec{
ControllerName: gapi.GatewayController(controllerName),
Expand Down Expand Up @@ -1114,7 +1142,7 @@ func (pr *gatewayAPIImplementationComponent) wafHttpFilterServiceAccount() *core
TypeMeta: metav1.TypeMeta{Kind: "ServiceAccount", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{
Name: wafFilterName,
Namespace: "tigera-gateway",
Namespace: common.TigeraGatewayNamespace,
},
}
}
Expand Down Expand Up @@ -1165,8 +1193,72 @@ func (pr *gatewayAPIImplementationComponent) wafHttpFilterClusterRoleBinding() *
{
Kind: "ServiceAccount",
Name: wafFilterName,
Namespace: "tigera-gateway",
Namespace: common.TigeraGatewayNamespace,
},
},
}
}

// gatewayCertgenAllowCalicoSystemPolicy creates a NetworkPolicy that allows the certgen job
// to access DNS and the Kubernetes API server, which it needs to create TLS secrets.
func gatewayCertgenAllowCalicoSystemPolicy(openShift bool) *v3.NetworkPolicy {
egressRules := []v3.Rule{}
egressRules = networkpolicy.AppendDNSEgressRules(egressRules, openShift)
egressRules = append(egressRules, v3.Rule{
Action: v3.Allow,
Protocol: &networkpolicy.TCPProtocol,
Destination: networkpolicy.KubeAPIServerEntityRule,
})

return &v3.NetworkPolicy{
TypeMeta: metav1.TypeMeta{Kind: "NetworkPolicy", APIVersion: "projectcalico.org/v3"},
ObjectMeta: metav1.ObjectMeta{
Name: GatewayCertgenPolicyName,
Namespace: common.TigeraGatewayNamespace,
},
Spec: v3.NetworkPolicySpec{
Tier: networkpolicy.CalicoTierName,
Selector: "app == 'certgen'",
Types: []v3.PolicyType{v3.PolicyTypeEgress},
Egress: egressRules,
},
}
}

// gatewayControllerAllowCalicoSystemPolicy creates a NetworkPolicy that allows the envoy-gateway
// controller to access DNS and the Kubernetes API server, and to receive ingress on its
// serving ports (xDS gRPC, ratelimit, wasm, metrics, webhook).
func gatewayControllerAllowCalicoSystemPolicy(openShift bool) *v3.NetworkPolicy {
egressRules := []v3.Rule{}
egressRules = networkpolicy.AppendDNSEgressRules(egressRules, openShift)
egressRules = append(egressRules, v3.Rule{
Action: v3.Allow,
Protocol: &networkpolicy.TCPProtocol,
Destination: networkpolicy.KubeAPIServerEntityRule,
})

ingressRules := []v3.Rule{
{
Action: v3.Allow,
Protocol: &networkpolicy.TCPProtocol,
Destination: v3.EntityRule{
Ports: networkpolicy.Ports(EnvoyGatewayPortGRPC, EnvoyGatewayPortRateLimit, EnvoyGatewayPortWasm, EnvoyGatewayPortMetrics, EnvoyGatewayPortWebhook),
},
},
}

return &v3.NetworkPolicy{
TypeMeta: metav1.TypeMeta{Kind: "NetworkPolicy", APIVersion: "projectcalico.org/v3"},
ObjectMeta: metav1.ObjectMeta{
Name: GatewayControllerPolicyName,
Namespace: common.TigeraGatewayNamespace,
},
Spec: v3.NetworkPolicySpec{
Tier: networkpolicy.CalicoTierName,
Selector: "k8s-app == '" + GatewayControllerLabel + "'",
Types: []v3.PolicyType{v3.PolicyTypeIngress, v3.PolicyTypeEgress},
Ingress: ingressRules,
Egress: egressRules,
},
}
}
49 changes: 49 additions & 0 deletions pkg/render/gatewayapi/gateway_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1295,4 +1295,53 @@ var _ = Describe("Gateway API rendering tests", func() {
Expect(serviceAccount.Name).To(Equal("waf-http-filter"))
Expect(serviceAccount.Namespace).To(Equal("tigera-gateway"))
})

Context("GatewayPolicy component", func() {
It("should render the calico-system tier allow policies", func() {
installation := &operatorv1.InstallationSpec{
Variant: operatorv1.Calico,
}
gatewayAPI := &operatorv1.GatewayAPI{}
policyComp := GatewayPolicy(&GatewayAPIImplementationConfig{
Installation: installation,
GatewayAPI: gatewayAPI,
})

objsToCreate, objsToDelete := policyComp.Objects()
Expect(objsToDelete).To(BeEmpty())
Expect(objsToCreate).To(HaveLen(2))

expectedPolicies := []struct {
name string
namespace string
}{
{name: "calico-system.gateway-api-certgen-access", namespace: "tigera-gateway"},
{name: "calico-system.gateway-api-controller-access", namespace: "tigera-gateway"},
}
for _, expected := range expectedPolicies {
Expect(objsToCreate).To(ContainElement(&matchObject{name: expected.name}),
fmt.Sprintf("expected NetworkPolicy %q in namespace %q", expected.name, expected.namespace))
}

// Spot-check that the rendered objects are Calico v3 NetworkPolicies in the calico-system tier.
for _, obj := range objsToCreate {
Expect(obj.GetObjectKind().GroupVersionKind().Kind).To(Equal("NetworkPolicy"))
Expect(obj.GetObjectKind().GroupVersionKind().GroupVersion().String()).To(Equal("projectcalico.org/v3"))
Expect(obj.GetNamespace()).To(Equal("tigera-gateway"))
}
})

It("should render OpenShift-appropriate DNS egress rules when on OpenShift", func() {
installation := &operatorv1.InstallationSpec{
KubernetesProvider: operatorv1.ProviderOpenShift,
}
policyComp := GatewayPolicy(&GatewayAPIImplementationConfig{
Installation: installation,
GatewayAPI: &operatorv1.GatewayAPI{},
})

objsToCreate, _ := policyComp.Objects()
Expect(objsToCreate).To(HaveLen(2))
})
})
})
Loading
Loading