From e32039c02c1cdfba160476253badf9a759c339b2 Mon Sep 17 00:00:00 2001 From: Stephen Compall Date: Tue, 21 Oct 2025 19:14:20 +0000 Subject: [PATCH 1/3] make a BackendConfig alongside the CA security policy [skip ci] Signed-off-by: Stephen Compall --- cluster/pulumi/infra/src/cloudArmor.ts | 32 +++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/cluster/pulumi/infra/src/cloudArmor.ts b/cluster/pulumi/infra/src/cloudArmor.ts index 6b6b8d92c3..29c2b90510 100644 --- a/cluster/pulumi/infra/src/cloudArmor.ts +++ b/cluster/pulumi/infra/src/cloudArmor.ts @@ -1,9 +1,10 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import * as gcp from '@pulumi/gcp'; +import * as k8s from '@pulumi/kubernetes'; import * as pulumi from '@pulumi/pulumi'; import * as _ from 'lodash'; -import { CLUSTER_BASENAME } from '@lfdecentralizedtrust/splice-pulumi-common'; +import { CLUSTER_BASENAME, ExactNamespace } from '@lfdecentralizedtrust/splice-pulumi-common'; import * as config from './config'; @@ -42,8 +43,14 @@ export interface PredefinedWafRule { */ export function configureCloudArmorPolicy( cac: CloudArmorConfig, + ingressNs: ExactNamespace, opts?: pulumi.ComponentResourceOptions -): gcp.compute.SecurityPolicy | undefined { +): + | { + securityPolicy: gcp.compute.SecurityPolicy; + backendConfig: k8s.apiextensions.CustomResource; + } + | undefined { if (!cac.enabled) { return undefined; } @@ -63,6 +70,25 @@ export function configureCloudArmorPolicy( opts ); + const configName = `waf-ca-backend-config-${CLUSTER_BASENAME}`; + const backendConfig = new k8s.apiextensions.CustomResource( + configName, + { + apiVersion: 'cloud.google.com/v1', + kind: 'BackendConfig', + metadata: { + name: configName, + namespace: ingressNs.ns.metadata.name, + }, + spec: { + securityPolicy: { + name: securityPolicy.name, + }, + }, + }, + { parent: securityPolicy, dependsOn: [securityPolicy] } + ); + const ruleOpts = { ...opts, parent: securityPolicy, deletedWith: securityPolicy }; // Step 2: Add predefined WAF rules @@ -81,7 +107,7 @@ export function configureCloudArmorPolicy( // Step 5: Add default deny rule addDefaultDenyRule(securityPolicy, cac.allRulesPreviewOnly, ruleOpts); - return securityPolicy; + return { securityPolicy, backendConfig }; } /** From 70fa29f5a512d4cb012e6de48e53bcadc2c4f2c3 Mon Sep 17 00:00:00 2001 From: Stephen Compall Date: Tue, 21 Oct 2025 19:14:54 +0000 Subject: [PATCH 2/3] pass the BackendConfig to configureIstio [skip ci] Signed-off-by: Stephen Compall --- cluster/pulumi/infra/src/index.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/cluster/pulumi/infra/src/index.ts b/cluster/pulumi/infra/src/index.ts index b8f5d9a787..cd338e2793 100644 --- a/cluster/pulumi/infra/src/index.ts +++ b/cluster/pulumi/infra/src/index.ts @@ -25,7 +25,17 @@ export const ingressIp = network.ingressIp.address; export const ingressNs = network.ingressNs.ns.metadata.name; export const egressIp = network.egressIp.address; -const istio = configureIstio(network.ingressNs, ingressIp, network.cometbftIngressIp.address); +const cloudArmorBackendConfig = configureCloudArmorPolicy( + cloudArmorConfig, + network.ingressNs +)?.backendConfig; + +const istio = configureIstio( + network.ingressNs, + ingressIp, + network.cometbftIngressIp.address, + cloudArmorBackendConfig +); // Ensures that images required from Quay for observability can be pulled const observabilityDependsOn = istio.concat([network]); @@ -44,8 +54,6 @@ istioMonitoring(network.ingressNs, []); configureStorage(); -configureCloudArmorPolicy(cloudArmorConfig); - installExtraCustomResources(); let configuredAuth0; From 863ca1c02c3607a1dc941a0ffa348964e7d29d22 Mon Sep 17 00:00:00 2001 From: Stephen Compall Date: Tue, 21 Oct 2025 19:15:57 +0000 Subject: [PATCH 3/3] annotate the istio gateway service with the BackendConfig [skip ci] Signed-off-by: Stephen Compall --- cluster/pulumi/infra/src/istio.ts | 35 ++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/cluster/pulumi/infra/src/istio.ts b/cluster/pulumi/infra/src/istio.ts index 923276cd0c..f532be5a2d 100644 --- a/cluster/pulumi/infra/src/istio.ts +++ b/cluster/pulumi/infra/src/istio.ts @@ -164,7 +164,8 @@ function ingressPort(name: string, port: number): IngressPort { function configureInternalGatewayService( ingressNs: k8s.core.v1.Namespace, ingressIp: pulumi.Output, - istiod: k8s.helm.v3.Release + istiod: k8s.helm.v3.Release, + cloudArmorBackendConfig?: k8s.apiextensions.CustomResource ) { const cluster = gcp.container.getCluster({ name: CLUSTER_NAME, @@ -202,7 +203,8 @@ function configureInternalGatewayService( ingressPort('sw-lg-gw', 6201), ], istiod, - '' + '', + cloudArmorBackendConfig ); } @@ -304,7 +306,8 @@ function configureGatewayService( externalIPRangesInLB: pulumi.Output, ingressPorts: IngressPort[], istiod: k8s.helm.v3.Release, - suffix: string + suffix: string, + cloudArmorBackendConfig?: k8s.apiextensions.CustomResource ) { // We limit source IPs in two ways: // - For most traffic, we use istio instead of through loadBalancerSourceRanges as the latter has a size limit. @@ -315,6 +318,17 @@ function configureGatewayService( // These IPs should be provided in externalIPRangesInLB. const istioPolicies = istioAccessPolicies(ingressNs, externalIPRangesInIstio, suffix); + + // Additional annotations to apply the BackendConfig for Cloud Armor if provided + const backendConfigAnnotation: { [key: string]: pulumi.Input } = cloudArmorBackendConfig + ? { + 'cloud.google.com/backend-config': cloudArmorBackendConfig.metadata.name.apply(bcName => + JSON.stringify({ default: bcName }) + ), + } + : {}; + const backendConfigDeps = cloudArmorBackendConfig ? [cloudArmorBackendConfig] : []; + const gateway = new k8s.helm.v3.Release( `istio-ingress${suffix}`, { @@ -355,6 +369,7 @@ function configureGatewayService( ingressPort('http2', 80), ingressPort('https', 443), ].concat(ingressPorts), + annotations: backendConfigAnnotation, }, ...infraAffinityAndTolerations, // The httpLoadBalancing addon needs to be enabled to use backend service-based network load balancers. @@ -369,10 +384,10 @@ function configureGatewayService( deleteBeforeReplace: true, dependsOn: istioPolicies ? istioPolicies.apply(policies => { - const base: pulumi.Resource[] = [ingressNs, istiod]; + const base: pulumi.Resource[] = [ingressNs, istiod, ...backendConfigDeps]; return base.concat(policies); }) - : [ingressNs, istiod], + : [ingressNs, istiod, ...backendConfigDeps], } ); if (infraConfig.istio.enableIngressAccessLogging) { @@ -631,7 +646,8 @@ function configurePublicInfo(ingressNs: k8s.core.v1.Namespace): k8s.apiextension export function configureIstio( ingressNs: ExactNamespace, ingressIp: pulumi.Output, - cometBftIngressIp: pulumi.Output + cometBftIngressIp: pulumi.Output, + cloudArmorBackendConfig?: k8s.apiextensions.CustomResource ): pulumi.Resource[] { const nsName = 'istio-system'; const istioSystemNs = new k8s.core.v1.Namespace(nsName, { @@ -641,7 +657,12 @@ export function configureIstio( }); const base = configureIstioBase(istioSystemNs, ingressNs.ns); const istiod = configureIstiod(ingressNs.ns, base); - const gwSvc = configureInternalGatewayService(ingressNs.ns, ingressIp, istiod); + const gwSvc = configureInternalGatewayService( + ingressNs.ns, + ingressIp, + istiod, + cloudArmorBackendConfig + ); const cometBftSvc = configureCometBFTGatewayService(ingressNs.ns, cometBftIngressIp, istiod); const gateways = configureGateway(ingressNs, gwSvc, cometBftSvc); const docsAndReleases = configureDocsAndReleases(true, gateways);