Skip to content
Draft
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
32 changes: 29 additions & 3 deletions cluster/pulumi/infra/src/cloudArmor.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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;
}
Expand All @@ -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
Expand All @@ -81,7 +107,7 @@ export function configureCloudArmorPolicy(
// Step 5: Add default deny rule
addDefaultDenyRule(securityPolicy, cac.allRulesPreviewOnly, ruleOpts);

return securityPolicy;
return { securityPolicy, backendConfig };
}

/**
Expand Down
14 changes: 11 additions & 3 deletions cluster/pulumi/infra/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand All @@ -44,8 +54,6 @@ istioMonitoring(network.ingressNs, []);

configureStorage();

configureCloudArmorPolicy(cloudArmorConfig);

installExtraCustomResources();

let configuredAuth0;
Expand Down
35 changes: 28 additions & 7 deletions cluster/pulumi/infra/src/istio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ function ingressPort(name: string, port: number): IngressPort {
function configureInternalGatewayService(
ingressNs: k8s.core.v1.Namespace,
ingressIp: pulumi.Output<string>,
istiod: k8s.helm.v3.Release
istiod: k8s.helm.v3.Release,
cloudArmorBackendConfig?: k8s.apiextensions.CustomResource
) {
const cluster = gcp.container.getCluster({
name: CLUSTER_NAME,
Expand Down Expand Up @@ -202,7 +203,8 @@ function configureInternalGatewayService(
ingressPort('sw-lg-gw', 6201),
],
istiod,
''
'',
cloudArmorBackendConfig
);
}

Expand Down Expand Up @@ -304,7 +306,8 @@ function configureGatewayService(
externalIPRangesInLB: pulumi.Output<string[]>,
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.
Expand All @@ -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<string> } = 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}`,
{
Expand Down Expand Up @@ -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.
Expand All @@ -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) {
Expand Down Expand Up @@ -631,7 +646,8 @@ function configurePublicInfo(ingressNs: k8s.core.v1.Namespace): k8s.apiextension
export function configureIstio(
ingressNs: ExactNamespace,
ingressIp: pulumi.Output<string>,
cometBftIngressIp: pulumi.Output<string>
cometBftIngressIp: pulumi.Output<string>,
cloudArmorBackendConfig?: k8s.apiextensions.CustomResource
): pulumi.Resource[] {
const nsName = 'istio-system';
const istioSystemNs = new k8s.core.v1.Namespace(nsName, {
Expand All @@ -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);
Expand Down