From c53fe7b2d11f82f08777489b22ce2aed05a8fd4c Mon Sep 17 00:00:00 2001 From: Shahab Dogar Date: Mon, 24 Nov 2025 09:27:48 +0500 Subject: [PATCH] feat: Network | enable internal TLS --- core-services/cert-manager/index.ts | 111 +--------- core-services/index.ts | 6 +- main.ts | 13 +- pki/index.ts | 70 ++++++ pki/issuers/index.ts | 2 + pki/issuers/private.ts | 86 ++++++++ pki/issuers/public.ts | 59 ++++++ utils/cert-manager/base.ts | 281 +++++++++++++++++++++++++ utils/cert-manager/cloudflare.ts | 32 +++ utils/cert-manager/index.ts | 87 +------- utils/cert-manager/internal.ts | 36 ++++ utils/index.ts | 2 +- utils/traefik/ingress/ingress.ts | 39 +++- utils/traefik/ingress/publicIngress.ts | 1 + 14 files changed, 621 insertions(+), 204 deletions(-) create mode 100644 pki/index.ts create mode 100644 pki/issuers/index.ts create mode 100644 pki/issuers/private.ts create mode 100644 pki/issuers/public.ts create mode 100644 utils/cert-manager/base.ts create mode 100644 utils/cert-manager/cloudflare.ts create mode 100644 utils/cert-manager/internal.ts diff --git a/core-services/cert-manager/index.ts b/core-services/cert-manager/index.ts index 87686fd..cd88c5d 100644 --- a/core-services/cert-manager/index.ts +++ b/core-services/cert-manager/index.ts @@ -3,32 +3,25 @@ import * as path from "path"; import { HelmProvider } from "@cdktf/provider-helm/lib/provider"; import { Release } from "@cdktf/provider-helm/lib/release"; import { Construct } from "constructs"; -import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; -import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest"; type CertManagerOptions = { - providers: { - kubernetes: KubernetesProvider; - helm: HelmProvider; - }; + provider: HelmProvider; version: string; name: string; namespace: string; - certManagerApiVersion: string; }; export class CertManager extends Construct { constructor(scope: Construct, id: string, options: CertManagerOptions) { super(scope, id); - const { helm, kubernetes } = options.providers; - const { certManagerApiVersion } = options; + const { namespace, name, version, provider } = options; new Release(this, id, { - provider: helm, - name: options.name, - namespace: options.namespace, - version: options.version, + provider, + name, + namespace, + version, repository: "https://charts.jetstack.io", chart: "cert-manager", createNamespace: true, @@ -38,97 +31,5 @@ export class CertManager extends Construct { }), ], }); - - // Self-signed ClusterIssuer for initial CA - new Manifest(this, "ca-issuer", { - provider: kubernetes, - manifest: { - apiVersion: certManagerApiVersion, - kind: "ClusterIssuer", - metadata: { - name: "ca-issuer", - }, - spec: { - selfSigned: {}, - }, - }, - }); - - // Self-signed CA Certificate - new Manifest(this, "selfsigned-ca", { - provider: kubernetes, - manifest: { - apiVersion: certManagerApiVersion, - kind: "Certificate", - metadata: { - name: "selfsigned-ca", - namespace: options.namespace, - }, - spec: { - isCA: true, - commonName: "Shahab Dogar", - secretName: "root-secret", - privateKey: { - algorithm: "ECDSA", - size: 256, - }, - issuerRef: { - name: "ca-issuer", - kind: "ClusterIssuer", - group: "cert-manager.io", - }, - }, - }, - }); - - // CA-based ClusterIssuer - new Manifest(this, "cluster-issuer", { - provider: kubernetes, - manifest: { - apiVersion: certManagerApiVersion, - kind: "ClusterIssuer", - metadata: { - name: "cluster-issuer", - }, - spec: { - ca: { - secretName: "root-secret", - }, - }, - }, - }); - - // Cloudflare ACME ClusterIssuer - new Manifest(this, "cloudflare-issuer", { - provider: kubernetes, - manifest: { - apiVersion: certManagerApiVersion, - kind: "ClusterIssuer", - metadata: { - name: "cloudflare-issuer", - }, - spec: { - acme: { - email: "shahab@dogar.dev", - server: "https://acme-v02.api.letsencrypt.org/directory", - privateKeySecretRef: { - name: "cloudflare-cluster-issuer-account-key", - }, - solvers: [ - { - dns01: { - cloudflare: { - apiTokenSecretRef: { - name: "cloudflare-token", - key: "token", - }, - }, - }, - }, - ], - }, - }, - }, - }); } } diff --git a/core-services/index.ts b/core-services/index.ts index 1b4ef68..b80e576 100644 --- a/core-services/index.ts +++ b/core-services/index.ts @@ -56,14 +56,10 @@ export class CoreServices extends TerraformStack { }); new CertManager(this, "cert-manager", { - certManagerApiVersion: "cert-manager.io/v1", + provider: helm, name: "cert-manager", namespace, version: "1.18.2", - providers: { - kubernetes, - helm, - }, }); } } diff --git a/main.ts b/main.ts index c1b88f1..334a1bf 100644 --- a/main.ts +++ b/main.ts @@ -7,6 +7,7 @@ import { K8SOperators } from "./k8s-operators"; import { CoreServices } from "./core-services"; import { NetworkSecurity } from "./network-security"; import { GamingServices } from "./gaming-services/minecraft"; +import { PKI } from "./pki"; dotenv.config(); @@ -26,18 +27,21 @@ const coreServices = new CoreServices(app, "core-services"); const k8sOperators = new K8SOperators(app, "k8s-operators"); k8sOperators.node.addDependency(coreServices); +const pki = new PKI(app, "pki"); +pki.node.addDependency(k8sOperators); + const networkSecurity = new NetworkSecurity(app, "network-security"); -networkSecurity.node.addDependency(k8sOperators); +networkSecurity.node.addDependency(pki); const utilityServices = new UtilityServices(app, "utility-services"); utilityServices.node.addDependency(networkSecurity); -const caches = new CacheInfrastructure(app, "cache-infrastructure"); -caches.node.addDependency(utilityServices); - const gamingServices = new GamingServices(app, "gaming-services"); gamingServices.node.addDependency(networkSecurity); +const caches = new CacheInfrastructure(app, "cache-infrastructure"); +caches.node.addDependency(utilityServices); + const deploy: (stack: TerraformStack, key: string) => S3Backend = ( stack, key, @@ -61,6 +65,7 @@ const deploy: (stack: TerraformStack, key: string) => S3Backend = ( deploy(coreServices, "core-services"); deploy(k8sOperators, "k8s-operators"); +deploy(pki, "pki"); deploy(networkSecurity, "network-security"); deploy(utilityServices, "utility-services"); deploy(caches, "cache-infrastructure"); diff --git a/pki/index.ts b/pki/index.ts new file mode 100644 index 0000000..adb9299 --- /dev/null +++ b/pki/index.ts @@ -0,0 +1,70 @@ +import { DataKubernetesNamespaceV1 } from "@cdktf/provider-kubernetes/lib/data-kubernetes-namespace-v1"; +import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; +import { DataTerraformRemoteStateS3, TerraformStack } from "cdktf"; +import { Construct } from "constructs"; +import { PrivateIssuer, PublicIssuer } from "./issuers"; + +export class PKI extends TerraformStack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const kubernetes = new KubernetesProvider(this, "kubernetes", { + configPath: "~/.kube/config", + }); + + const r2Endpoint = `${process.env.ACCOUNT_ID!}.r2.cloudflarestorage.com`; + + const coreServicesState = new DataTerraformRemoteStateS3( + this, + "core-services-state", + { + usePathStyle: true, + skipRegionValidation: true, + skipCredentialsValidation: true, + skipRequestingAccountId: true, + skipS3Checksum: true, + encrypt: true, + bucket: "terraform-state", + key: "core-services/terraform.tfstate", + endpoints: { + s3: `https://${r2Endpoint}`, + }, + region: "auto", + accessKey: process.env.ACCESS_KEY, + secretKey: process.env.SECRET_KEY, + }, + ); + + const namespaceName = coreServicesState.getString("namespace-output"); + const namespaceResource = new DataKubernetesNamespaceV1( + this, + "homelab-namespace", + { + provider: kubernetes, + metadata: { + name: namespaceName, + }, + }, + ); + const namespace = namespaceResource.metadata.name; + + new PrivateIssuer(this, "private-issuer", { + provider: kubernetes, + namespace, + apiVersion: "cert-manager.io/v1", + secretName: "root-secret", + commonName: "Homelab Root CA", + privateKey: { + algorithm: "Ed25519", + size: 256, + }, + }); + + new PublicIssuer(this, "public-issuer", { + provider: kubernetes, + namespace, + apiVersion: "cert-manager.io/v1", + server: "https://acme-v02.api.letsencrypt.org/directory", + }); + } +} diff --git a/pki/issuers/index.ts b/pki/issuers/index.ts new file mode 100644 index 0000000..7e3afa1 --- /dev/null +++ b/pki/issuers/index.ts @@ -0,0 +1,2 @@ +export { PrivateIssuer } from "./private"; +export { PublicIssuer } from "./public"; diff --git a/pki/issuers/private.ts b/pki/issuers/private.ts new file mode 100644 index 0000000..989d2e0 --- /dev/null +++ b/pki/issuers/private.ts @@ -0,0 +1,86 @@ +import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest"; +import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; +import { Construct } from "constructs"; + +type PrivateIssuerOptions = { + provider: KubernetesProvider; + namespace: string; + apiVersion: string; + commonName: string; + secretName: string; + privateKey: { + algorithm: "RSA" | "ECDSA" | "Ed25519"; + size: number; + }; +}; + +export class PrivateIssuer extends Construct { + constructor(scope: Construct, id: string, options: PrivateIssuerOptions) { + super(scope, id); + + const { + provider, + namespace, + commonName, + privateKey, + secretName, + apiVersion, + } = options; + + // Self-signed ClusterIssuer for initial CA + new Manifest(this, "ca-issuer", { + provider, + manifest: { + apiVersion, + kind: "ClusterIssuer", + metadata: { + name: "ca-issuer", + }, + spec: { + selfSigned: {}, + }, + }, + }); + + // Self-signed CA Certificate + new Manifest(this, "selfsigned-ca", { + provider, + manifest: { + apiVersion, + kind: "Certificate", + metadata: { + name: "selfsigned-ca", + namespace, + }, + spec: { + isCA: true, + commonName, + secretName, + privateKey, + issuerRef: { + name: "ca-issuer", + kind: "ClusterIssuer", + group: "cert-manager.io", + }, + }, + }, + }); + + // CA-based ClusterIssuer + new Manifest(this, "cluster-issuer", { + provider, + manifest: { + apiVersion, + kind: "ClusterIssuer", + metadata: { + name: "cluster-issuer", + }, + spec: { + ca: { + secretName, + }, + }, + }, + }); + } +} diff --git a/pki/issuers/public.ts b/pki/issuers/public.ts new file mode 100644 index 0000000..65d3061 --- /dev/null +++ b/pki/issuers/public.ts @@ -0,0 +1,59 @@ +import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest"; +import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; +import { Construct } from "constructs"; +import { OnePasswordSecret } from "../../utils"; + +type PublicIssuerOptions = { + provider: KubernetesProvider; + apiVersion: string; + namespace: string; + server: string; +}; + +export class PublicIssuer extends Construct { + constructor(scope: Construct, id: string, options: PublicIssuerOptions) { + super(scope, id); + + const { apiVersion, provider, namespace, server } = options; + + new OnePasswordSecret(this, "cloudflare-token", { + provider, + namespace, + name: "public-issuer-cloudflare-token", + itemPath: "vaults/Lab/items/cloudflare", + }); + + // Cloudflare ACME ClusterIssuer + new Manifest(this, "cloudflare-issuer", { + provider, + manifest: { + apiVersion, + kind: "ClusterIssuer", + metadata: { + name: "cloudflare-issuer", + }, + spec: { + acme: { + email: "shahab@dogar.dev", + server, + privateKeySecretRef: { + name: "cloudflare-cluster-issuer-account-key", + }, + solvers: [ + { + dns01: { + cloudflare: { + apiTokenSecretRef: { + name: "public-issuer-cloudflare-token", + key: "token", + }, + }, + }, + }, + ], + }, + }, + }, + }); + } +} diff --git a/utils/cert-manager/base.ts b/utils/cert-manager/base.ts new file mode 100644 index 0000000..69cc6b5 --- /dev/null +++ b/utils/cert-manager/base.ts @@ -0,0 +1,281 @@ +import { Construct } from "constructs"; +import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest"; +import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; + +/** + * Options passed to the Certificate construct for generating + * cert-manager.io/v1 Certificate resources. + * + * This type supports both public certificates (Cloudflare/ACME) + * and private internal certificates (internal CA), making it usable + * across all cluster security contexts (Ingress TLS, internal mTLS, etc.). + */ +export type CertificateOptions = { + /** + * Kubernetes provider instance used by the underlying Manifest resource. + * + * This should typically be the cluster's primary Kubernetes provider. + * + * Required. + */ + provider: KubernetesProvider; + + /** + * Kubernetes namespace where the Certificate resource and the + * corresponding Secret will be created. + * + * Required. + */ + namespace: string; + + /** + * Name of the Certificate resource (metadata.name). + * + * This should be unique within the namespace. + * + * Required. + */ + name: string; + + /** + * Name of the Kubernetes Secret that cert-manager will populate with + * `tls.crt`, `tls.key`, and optionally `ca.crt`. + * + * This secret is automatically created and updated by cert-manager. + * + * Required. + */ + secretName: string; + + /** + * List of DNS Subject Alternative Names that the certificate must cover. + * + * cert-manager requires at least one entry. + * + * For internal certificates: service FQDNs (svc.cluster.local). + * For public certificates: external domain names. + * + * Required. + */ + dnsNames: string[]; + + /** + * Reference to the cert-manager Issuer or ClusterIssuer used to sign the certificate. + * + * - For public certs: Cloudflare ACME ClusterIssuer + * - For private certs: Internal CA ClusterIssuer + * + * This field is usually injected automatically by subclasses + * (e.g., PublicCertificate / PrivateCertificate). + * + * Required internally — not intended to be set by user code directly. + */ + issuerRef?: { + /** + * Name of the Issuer or ClusterIssuer. + */ + name: string; + + /** + * Type of issuer ("Issuer" or "ClusterIssuer"). + * + * Defaults to "ClusterIssuer" when omitted. + */ + kind?: string; + }; + + /** + * The certificate's validity duration (e.g. "2160h" for 90 days). + * + * If omitted, cert-manager applies its own default (90 days for ACME). + * + * Optional. + */ + duration?: string; + + /** + * How long before expiry cert-manager should attempt early renewal. + * + * Example: "360h" (15 days before expiration). + * + * Optional. + */ + renewBefore?: string; + + /** + * Optional Common Name for the certificate's subject. + * + * SAN-only certificates are recommended, but CN is still required for + * compatibility with some older libraries (Java, ClickHouse, OpenSSL tooling). + * + * Optional. + */ + commonName?: string; + + /** + * Key Usage extension — determines what the certificate may be used for. + * + * Common values: + * + * - "digital signature" + * - "key encipherment" + * - "server auth" + * - "client auth" + * + * Example for mTLS server certificates: + * usages: ["digital signature", "key encipherment", "server auth"] + * + * Example for mTLS client certificates: + * usages: ["digital signature", "client auth"] + * + * Optional — cert-manager applies sensible defaults when omitted. + */ + usages?: string[]; + + /** + * Options controlling the generated private key. + * + * Useful for: + * - Choosing RSA vs ECDSA vs Ed25519 + * - Increasing RSA key strength (2048 → 4096) + * - Optimizing performance for internal services (ECDSA P-256) + * + * Optional. + */ + privateKey?: { + /** + * Private key algorithm. + * + * - "RSA" (default) + * - "ECDSA" (great for internal TLS) + * - "Ed25519" (fast and modern, but not universally supported) + * + * Optional. + */ + algorithm?: "RSA" | "ECDSA" | "Ed25519"; + + /** + * Key size in bits. + * + * Only applies to algorithms that support length: + * - RSA: 2048, 3072, 4096 + * - ECDSA: 256, 384 + * + * Optional. + */ + size?: number; + }; + + /** + * IP address SAN entries (rarely needed, but sometimes required + * for services bound directly to cluster node IPs or StatefulSet pod IPs). + * + * Using IP SANs is generally discouraged unless explicitly required. + * + * Optional. + */ + ipAddresses?: string[]; + + /** + * Subject information for the certificate (Organization, OrgUnit, etc.) + * + * Example: + * + * subject: { + * organizations: ["Internal Systems"], + * organizationalUnits: ["Platform"] + * } + * + * Optional. + */ + subject?: { + organizations?: string[]; + organizationalUnits?: string[]; + countries?: string[]; + provinces?: string[]; + localities?: string[]; + streetAddresses?: string[]; + postalCodes?: string[]; + }; +}; + +export class Certificate extends Construct { + /** The underlying Kubernetes manifest */ + public readonly manifest: Manifest; + + constructor(scope: Construct, id: string, opts: CertificateOptions) { + super(scope, id); + + // --- Validation --------------------------------------------------------- + if (!opts.issuerRef) { + throw new Error( + `Certificate '${opts.name}' must specify issuerRef (usually provided by a subclass).`, + ); + } + if (!opts.dnsNames || opts.dnsNames.length === 0) { + throw new Error( + `Certificate '${opts.name}' must include at least one DNS name in dnsNames[].`, + ); + } + + // --- Base manifest ------------------------------------------------------ + const manifest: any = { + apiVersion: "cert-manager.io/v1", + kind: "Certificate", + metadata: { + name: opts.name, + namespace: opts.namespace, + }, + spec: { + secretName: opts.secretName, + dnsNames: opts.dnsNames, + issuerRef: { + name: opts.issuerRef.name, + kind: opts.issuerRef.kind ?? "ClusterIssuer", + }, + }, + }; + + // --- Optional: duration & renewBefore --------------------------------- + if (opts.duration) { + manifest.spec.duration = opts.duration; + } + + if (opts.renewBefore) { + manifest.spec.renewBefore = opts.renewBefore; + } + + // --- Optional: commonName ---------------------------------------------- + if (opts.commonName) { + manifest.spec.commonName = opts.commonName; + } + + // --- Optional: key usages ---------------------------------------------- + if (opts.usages?.length) { + manifest.spec.usages = opts.usages; + } + + // --- Optional: private key settings ------------------------------------ + if (opts.privateKey) { + manifest.spec.privateKey = { + ...opts.privateKey, + }; + } + + // --- Optional: IP SAN entries ------------------------------------------ + if (opts.ipAddresses?.length) { + manifest.spec.ipAddresses = opts.ipAddresses; + } + + // --- Optional: subject fields ------------------------------------------ + if (opts.subject) { + manifest.spec.subject = opts.subject; + } + + // --- Create manifest resource ------------------------------------------ + this.manifest = new Manifest(this, id, { + provider: opts.provider, + manifest, + }); + } +} diff --git a/utils/cert-manager/cloudflare.ts b/utils/cert-manager/cloudflare.ts new file mode 100644 index 0000000..2fa21e3 --- /dev/null +++ b/utils/cert-manager/cloudflare.ts @@ -0,0 +1,32 @@ +import { Construct } from "constructs"; +import { Certificate, CertificateOptions } from "./base"; + +/** + * Public certificate issued via the Cloudflare ACME ClusterIssuer. + * + * This subclass automatically injects: + * + * issuerRef: + * name: "cloudflare-issuer" + * kind: "ClusterIssuer" + * + * It is intended for generating publicly trusted HTTPS certificates + * (e.g., *.dogar.dev) using Cloudflare DNS-01 validation. + * + * Users of this class should *not* specify issuerRef manually. + */ +export class CloudflareCertificate extends Certificate { + constructor( + scope: Construct, + id: string, + opts: Omit, + ) { + super(scope, id, { + ...opts, + issuerRef: { + name: "cloudflare-issuer", + kind: "ClusterIssuer", + }, + }); + } +} diff --git a/utils/cert-manager/index.ts b/utils/cert-manager/index.ts index ee23f25..b40d845 100644 --- a/utils/cert-manager/index.ts +++ b/utils/cert-manager/index.ts @@ -1,85 +1,2 @@ -import { Construct } from "constructs"; -import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest"; -import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; - -type CertificateOptions = { - provider: KubernetesProvider; - - /** Namespace to create the Certificate in */ - namespace: string; - - /** Required name of the certificate (and CRD name) */ - name: string; - - /** Secret name for storing the issued TLS cert */ - secretName: string; - - /** One or more DNS names the certificate should cover */ - dnsNames: string[]; - - /** Reference to the cert-manager issuer */ - issuerRef: { - name: string; - kind?: string; // ClusterIssuer or Issuer - }; - - /** Optional duration (default: cert-manager default) */ - duration?: string; - - /** Optional renewBefore (default: cert-manager default) */ - renewBefore?: string; -}; - -class Certificate extends Construct { - public readonly manifest: Manifest; - - constructor(scope: Construct, id: string, opts: CertificateOptions) { - super(scope, id); - - const manifest: any = { - apiVersion: "cert-manager.io/v1", - kind: "Certificate", - metadata: { - name: opts.name, - namespace: opts.namespace, - }, - spec: { - secretName: opts.secretName, - dnsNames: opts.dnsNames, - issuerRef: { - name: opts.issuerRef.name, - kind: opts.issuerRef.kind ?? "ClusterIssuer", - }, - }, - }; - - if (opts.duration) { - manifest.spec.duration = opts.duration; - } - - if (opts.renewBefore) { - manifest.spec.renewBefore = opts.renewBefore; - } - - this.manifest = new Manifest(this, id, { - provider: opts.provider, - manifest, - }); - } -} - -export class CloudflareCertificate extends Certificate { - constructor( - scope: Construct, - id: string, - opts: Omit, - ) { - super(scope, id, { - ...opts, - issuerRef: { - name: "cloudflare-issuer", - kind: "ClusterIssuer", - }, - }); - } -} +export { CloudflareCertificate } from "./cloudflare"; +export { PrivateCertificate } from "./internal"; diff --git a/utils/cert-manager/internal.ts b/utils/cert-manager/internal.ts new file mode 100644 index 0000000..0767283 --- /dev/null +++ b/utils/cert-manager/internal.ts @@ -0,0 +1,36 @@ +import { Construct } from "constructs"; +import { Certificate, CertificateOptions } from "./base"; + +/** + * Private TLS certificate issued by the internal cluster CA. + * + * This subclass automatically injects: + * + * issuerRef: + * name: "cluster-issuer" + * kind: "ClusterIssuer" + * + * Use this for: + * - Internal service-to-service TLS (HTTP, gRPC, Webhooks) + * - mTLS server certificates + * - mTLS client certificates + * - Internal wildcard certificates + * - Databases, queues, operators, controllers, etc. + * + * Users of this class should NOT specify issuerRef manually. + */ +export class PrivateCertificate extends Certificate { + constructor( + scope: Construct, + id: string, + opts: Omit, + ) { + super(scope, id, { + ...opts, + issuerRef: { + name: "cluster-issuer", // internal CA + kind: "ClusterIssuer", + }, + }); + } +} diff --git a/utils/index.ts b/utils/index.ts index 9d2dacd..f5875b8 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -1,4 +1,4 @@ -export { CloudflareCertificate } from "./cert-manager"; +export { CloudflareCertificate, PrivateCertificate } from "./cert-manager"; export { OnePasswordSecret } from "./1password-secret"; export { PublicIngressRoute, diff --git a/utils/traefik/ingress/ingress.ts b/utils/traefik/ingress/ingress.ts index 3f51d6e..d7e7712 100644 --- a/utils/traefik/ingress/ingress.ts +++ b/utils/traefik/ingress/ingress.ts @@ -18,6 +18,7 @@ export type IngressRouteOptions = { /** Backend K8s Service */ serviceName: string; servicePort: number; + serviceProtocol?: "http" | "https"; /** EntryPoints (default: ["websecure"]) */ entryPoints?: string[]; @@ -39,6 +40,31 @@ export class IngressRoute extends Construct { const path = opts.path ?? "/"; const entryPoints = opts.entryPoints ?? ["websecure"]; + const { provider, namespace } = opts; + + if (opts.serviceProtocol === "https") { + new Manifest(this, `${name}-https-transport`, { + provider, + manifest: { + apiVersion: "traefik.io/v1alpha1", + kind: "ServersTransport", + metadata: { + name: `${name}-https-transport`, + namespace, + }, + spec: { + serverName: `${opts.name}.${opts.namespace}.svc.cluster.local`, + rootCAs: [ + { + secret: "root-secret", + }, + ], + insecureSkipVerify: false, + }, + }, + }); + } + const route: any = { match: `Host(\`${opts.host}\`) && PathPrefix(\`${path}\`)`, kind: "Rule", @@ -46,6 +72,11 @@ export class IngressRoute extends Construct { { name: opts.serviceName, port: opts.servicePort, + scheme: opts.serviceProtocol ?? "http", + serversTransport: + opts.serviceProtocol === "https" + ? `${name}-https-transport` + : undefined, }, ], }; @@ -68,8 +99,8 @@ export class IngressRoute extends Construct { }; new CloudflareCertificate(this, `${name}-cert`, { - provider: opts.provider, - namespace: opts.namespace, + provider, + namespace, name: opts.host, secretName: opts.tlsSecretName, dnsNames: [opts.host], @@ -77,13 +108,13 @@ export class IngressRoute extends Construct { } this.manifest = new Manifest(this, name, { - provider: opts.provider, + provider, manifest: { apiVersion: "traefik.io/v1alpha1", kind: "IngressRoute", metadata: { name, - namespace: opts.namespace, + namespace, }, spec, }, diff --git a/utils/traefik/ingress/publicIngress.ts b/utils/traefik/ingress/publicIngress.ts index eb06c25..ad28769 100644 --- a/utils/traefik/ingress/publicIngress.ts +++ b/utils/traefik/ingress/publicIngress.ts @@ -54,6 +54,7 @@ export class PublicIngressRoute extends Construct { path: opts.path ?? "/", serviceName: opts.serviceName, servicePort: opts.servicePort, + serviceProtocol: opts.serviceProtocol, entryPoints: ["websecure"], tlsSecretName: `${opts.name}-tls`, middlewares: [`${namespace}/rate-limit`],