Compare commits

...

10 Commits

26 changed files with 452 additions and 219 deletions

View File

@@ -1,7 +1,7 @@
import { Construct } from "constructs";
import { TerraformStack } from "cdktf";
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
import { NixCache } from "./nixcache";
import { NixCache } from "./nix";
export class CacheInfrastructure extends TerraformStack {
constructor(scope: Construct, id: string) {

View File

@@ -6,7 +6,7 @@ import { DeploymentV1 } from "@cdktf/provider-kubernetes/lib/deployment-v1";
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
import { ServiceV1 } from "@cdktf/provider-kubernetes/lib/service-v1";
import { IngressRoute, LonghornPvc } from "../../utils";
import { PublicIngressRoute, LonghornPvc } from "../../utils";
export class NixCache extends Construct {
constructor(scope: Construct, id: string, provider: KubernetesProvider) {
@@ -119,14 +119,13 @@ export class NixCache extends Construct {
},
});
new IngressRoute(this, "ingress-route", {
new PublicIngressRoute(this, "ingress-route", {
provider,
name: "nix-cache",
namespace: "homelab",
host: "nix.dogar.dev",
serviceName: "nix-cache",
servicePort: 80,
entryPoints: ["websecure"],
tlsSecretName: "nix-cache-tls",
});
}
}

View File

@@ -39,8 +39,6 @@ export class CertManager extends Construct {
],
});
// "apiVersion=v1,kind=Secret,namespace=default,name=sample"
// Self-signed ClusterIssuer for initial CA
new Manifest(this, "ca-issuer", {
provider: kubernetes,
@@ -54,9 +52,7 @@ export class CertManager extends Construct {
selfSigned: {},
},
},
}).importFrom(
`apiVersion=${certManagerApiVersion},kind=ClusterIssuer,name=ca-issuer`,
);
});
// Self-signed CA Certificate
new Manifest(this, "selfsigned-ca", {
@@ -83,9 +79,7 @@ export class CertManager extends Construct {
},
},
},
}).importFrom(
`apiVersion=${certManagerApiVersion},kind=Certificate,name=selfsigned-ca,namespace=${options.namespace}`,
);
});
// CA-based ClusterIssuer
new Manifest(this, "cluster-issuer", {
@@ -102,9 +96,7 @@ export class CertManager extends Construct {
},
},
},
}).importFrom(
`apiVersion=${certManagerApiVersion},kind=ClusterIssuer,name=cluster-issuer`,
);
});
// Cloudflare ACME ClusterIssuer
new Manifest(this, "cloudflare-issuer", {
@@ -137,8 +129,6 @@ export class CertManager extends Construct {
},
},
},
}).importFrom(
`apiVersion=${certManagerApiVersion},kind=ClusterIssuer,name=cloudflare-issuer`,
);
});
}
}

View File

@@ -5,7 +5,6 @@ import { Release } from "@cdktf/provider-helm/lib/release";
import { Construct } from "constructs";
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
import { IngressRoute } from "../../utils";
type LonghornOptions = {
providers: {
@@ -53,16 +52,5 @@ export class Longhorn extends Construct {
},
},
});
new IngressRoute(this, "ingress", {
provider: kubernetes,
name: "longhorn",
namespace,
serviceName: "longhorn-frontend",
servicePort: 80,
host: "longhorn.dogar.dev",
tlsSecretName: "longhorn-tls",
entryPoints: ["websecure"],
});
}
}

View File

@@ -1,66 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: action-runner
name: action-runner
namespace: homelab
spec:
replicas: 3
selector:
matchLabels:
app: action-runner
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: action-runner
spec:
nodeSelector:
nodepool: worker
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: action-runner
restartPolicy: Always
volumes:
- name: runner-data
persistentVolumeClaim:
claimName: action-runner
securityContext:
fsGroup: 1000
containers:
- name: runner
image: gitea/act_runner:nightly-dind-rootless
imagePullPolicy: Always
env:
- name: DOCKER_HOST
value: unix:///run/user/1000/docker.sock
- name: GITEA_INSTANCE_URL
value: https://git.dogar.dev
- name: GITEA_RUNNER_REGISTRATION_TOKEN
valueFrom:
secretKeyRef:
name: runner-secret
key: runner-token
securityContext:
privileged: true
volumeMounts:
- name: runner-data
mountPath: /data
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: action-runner-pdb
namespace: homelab
spec:
minAvailable: 6
selector:
matchLabels:
app: action-runner

View File

@@ -0,0 +1,48 @@
import * as fs from "fs";
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";
type OnePasswordOptions = {
provider: HelmProvider;
name: string;
};
export class OnePassword extends Construct {
constructor(scope: Construct, id: string, options: OnePasswordOptions) {
super(scope, id);
const { provider } = options;
new Release(this, "onepassword-operator", {
provider,
name: "onepassword-operator",
chart: "connect",
repository: "https://1password.github.io/connect-helm-charts/",
namespace: "1password",
createNamespace: true,
set: [
{
name: "operator.create",
value: "true",
},
],
setSensitive: [
{
name: "operator.token.value",
value: process.env.OP_CONNECT_TOKEN!,
},
{
name: "connect.credentials_base64",
value: btoa(
fs.readFileSync(
path.join(__dirname, "1password-credentials.json"),
"utf-8",
),
),
},
],
});
}
}

View File

@@ -1,12 +1,10 @@
import * as fs from "fs";
import * as path from "path";
import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
import { Release } from "@cdktf/provider-helm/lib/release";
import { TerraformStack } from "cdktf";
import { Construct } from "constructs";
import { BarmanCloudPluginInstall } from "./barman";
import { Prometheus } from "./prometheus";
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
import { OnePassword } from "./1password";
export class K8SOperators extends TerraformStack {
constructor(scope: Construct, id: string) {
@@ -18,48 +16,16 @@ export class K8SOperators extends TerraformStack {
},
});
const kubernetes = new KubernetesProvider(this, "kubernetes", {
configPath: "~/.kube/config",
});
new Prometheus(this, "prometheus", {
providers: {
helm,
kubernetes,
},
provider: helm,
namespace: "monitoring",
name: "prometheus-operator",
version: "75.10.0",
});
new Release(this, "onepassword-operator", {
new OnePassword(this, "onepassword", {
provider: helm,
name: "onepassword-operator",
chart: "connect",
repository: "https://1password.github.io/connect-helm-charts/",
namespace: "1password",
createNamespace: true,
set: [
{
name: "operator.create",
value: "true",
},
],
setSensitive: [
{
name: "operator.token.value",
value: process.env.OP_CONNECT_TOKEN!,
},
{
name: "connect.credentials_base64",
value: btoa(
fs.readFileSync(
path.join(__dirname, "1password-credentials.json"),
"utf-8",
),
),
},
],
name: "onepassword",
});
const cnpg = new Release(this, "cnpg-operator", {

View File

@@ -2,11 +2,10 @@ import * as fs from "fs";
import * as path from "path";
import { Release } from "@cdktf/provider-helm/lib/release";
import { Construct } from "constructs";
import { IngressRoute } from "../../utils";
import { Providers } from "../../types";
import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
type PrometheusOptions = {
providers: Providers;
provider: HelmProvider;
name: string;
namespace: string;
version: string;
@@ -16,22 +15,8 @@ export class Prometheus extends Construct {
constructor(scope: Construct, id: string, options: PrometheusOptions) {
super(scope, id);
const { helm, kubernetes } = options.providers;
new IngressRoute(this, "ingress", {
provider: kubernetes,
name: "grafana",
namespace: options.namespace,
entryPoints: ["websecure"],
serviceName: "prometheus-operator-grafana",
servicePort: 80,
tlsSecretName: "grafana-tls",
host: "grafana.dogar.dev",
});
new Release(this, id, {
...options,
provider: helm,
repository: "https://prometheus-community.github.io/helm-charts",
chart: "kube-prometheus-stack",
createNamespace: true,

10
main.ts
View File

@@ -5,6 +5,7 @@ import { CacheInfrastructure } from "./cache-infrastructure";
import { UtilityServices } from "./utility-services";
import { K8SOperators } from "./k8s-operators";
import { CoreServices } from "./core-services";
import { NetworkSecurity } from "./network-security";
dotenv.config();
@@ -13,6 +14,7 @@ const env = cleanEnv(process.env, {
OP_CONNECT_TOKEN: str({ desc: "1Password Connect token." }),
ACCESS_KEY: str({ desc: "Access key ID for R2 storage." }),
SECRET_KEY: str({ desc: "Secret access key for R2 storage." }),
VALKEY_PASSWORD: str({ desc: "Password for Valkey database." }),
});
const r2Endpoint = `https://${env.ACCOUNT_ID}.r2.cloudflarestorage.com`;
@@ -23,8 +25,11 @@ const coreServices = new CoreServices(app, "core-services");
const k8sOperators = new K8SOperators(app, "k8s-operators");
k8sOperators.node.addDependency(coreServices);
const networkSecurity = new NetworkSecurity(app, "network-security");
networkSecurity.node.addDependency(k8sOperators);
const utilityServices = new UtilityServices(app, "utility-services");
utilityServices.node.addDependency(k8sOperators);
utilityServices.node.addDependency(networkSecurity);
const caches = new CacheInfrastructure(app, "cache-infrastructure");
caches.node.addDependency(utilityServices);
@@ -51,8 +56,9 @@ const deploy: (stack: TerraformStack, key: string) => S3Backend = (
});
deploy(coreServices, "core-services");
deploy(utilityServices, "utility-services");
deploy(k8sOperators, "k8s-operators");
deploy(networkSecurity, "network-security");
deploy(utilityServices, "utility-services");
deploy(caches, "cache-infrastructure");
app.synth();

83
network-security/index.ts Normal file
View File

@@ -0,0 +1,83 @@
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 {
RateLimitMiddleware,
IpAllowListMiddleware,
IpAllowListMiddlewareTCP,
} from "./traefik";
import { ValkeyCluster } from "./valkey";
export class NetworkSecurity 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 ValkeyCluster(this, "valkey-cluster", {
provider: kubernetes,
name: "valkey",
namespace,
});
new RateLimitMiddleware(this, "rate-limit", {
provider: kubernetes,
namespace,
name: "rate-limit",
});
new IpAllowListMiddleware(this, "internal-ip-allow-list", {
provider: kubernetes,
namespace,
name: "ip-allow-list",
sourceRanges: ["192.168.18.0/24", "10.43.0.0/16"],
});
new IpAllowListMiddlewareTCP(this, "tcp-internal-ip-allow-list", {
provider: kubernetes,
namespace,
name: "tcp-ip-allow-list",
sourceRanges: ["192.168.18.0/24", "10.42.0.0/16"],
});
}
}

View File

@@ -0,0 +1,2 @@
export { RateLimitMiddleware } from "./rateLimit";
export { IpAllowListMiddleware, IpAllowListMiddlewareTCP } from "./ipAllowList";

View File

@@ -0,0 +1,64 @@
import { Construct } from "constructs";
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
type IpAllowListMiddlewareOptions = {
provider: KubernetesProvider;
namespace: string;
name: string;
sourceRanges: string[];
};
export class IpAllowListMiddleware extends Construct {
constructor(
scope: Construct,
id: string,
opts: IpAllowListMiddlewareOptions,
) {
super(scope, id);
new Manifest(this, opts.name, {
provider: opts.provider,
manifest: {
apiVersion: "traefik.io/v1alpha1",
kind: "Middleware",
metadata: {
name: opts.name,
namespace: opts.namespace,
},
spec: {
ipAllowList: {
sourceRange: opts.sourceRanges,
},
},
},
});
}
}
export class IpAllowListMiddlewareTCP extends Construct {
constructor(
scope: Construct,
id: string,
opts: IpAllowListMiddlewareOptions,
) {
super(scope, id);
new Manifest(this, opts.name, {
provider: opts.provider,
manifest: {
apiVersion: "traefik.io/v1alpha1",
kind: "MiddlewareTCP",
metadata: {
name: opts.name,
namespace: opts.namespace,
},
spec: {
ipAllowList: {
sourceRange: opts.sourceRanges,
},
},
},
});
}
}

View File

@@ -0,0 +1,51 @@
import { Construct } from "constructs";
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
type RateLimitMiddlewareOptions = {
provider: KubernetesProvider;
namespace: string;
name: string;
average?: number; // default 10
burst?: number; // default 50
period?: string; // default "1s"
};
export class RateLimitMiddleware extends Construct {
public readonly ref: string;
constructor(scope: Construct, id: string, opts: RateLimitMiddlewareOptions) {
super(scope, id);
const average = opts.average ?? 10;
const burst = opts.burst ?? 50;
const period = opts.period ?? "1s";
this.ref = `${opts.namespace}/${opts.name}`;
new Manifest(this, opts.name, {
provider: opts.provider,
manifest: {
apiVersion: "traefik.io/v1alpha1",
kind: "Middleware",
metadata: {
name: opts.name,
namespace: opts.namespace,
},
spec: {
rateLimit: {
average,
burst,
period,
redis: {
endpoints: [`valkey.${opts.namespace}.svc.cluster.local:6379`],
secret: "valkey",
db: 5,
},
},
},
},
});
}
}

View File

@@ -18,14 +18,14 @@ export class ValkeyCluster extends Construct {
const labels = { app: "valkey" };
const { provider, name, namespace } = options;
new OnePasswordSecret(this, "valkey-secret", {
new OnePasswordSecret(this, "secret", {
provider,
name: "valkey",
namespace,
itemPath: "vaults/Lab/items/valkey",
});
new DeploymentV1(this, "valkey-deployment", {
new DeploymentV1(this, "deployment", {
provider,
metadata: {
name,
@@ -107,13 +107,9 @@ export class ValkeyCluster extends Construct {
name,
namespace,
labels,
annotations: {
"external-dns.alpha.kubernetes.io/hostname": "valkey.dogar.dev",
"metallb.io/ip-allocated-from-pool": "pool",
},
},
spec: {
type: "LoadBalancer",
type: "ClusterIP",
selector: labels,
port: [
{

View File

@@ -2,7 +2,7 @@ import * as fs from "fs";
import * as path from "path";
import { Release } from "@cdktf/provider-helm/lib/release";
import { Construct } from "constructs";
import { IngressRoute, OnePasswordSecret } from "../../utils";
import { PublicIngressRoute, OnePasswordSecret } from "../../utils";
import { Providers } from "../../types";
type AuthentikServerOptions = {
@@ -44,14 +44,13 @@ export class AuthentikServer extends Construct {
],
});
new IngressRoute(this, "ingress", {
new PublicIngressRoute(this, "ingress", {
provider: kubernetes,
name: options.name,
namespace: options.namespace,
host: "auth.dogar.dev",
serviceName: `authentik-server`,
servicePort: 80,
tlsSecretName: "authentik-tls",
});
}
}

View File

@@ -3,7 +3,11 @@ import * as path from "path";
import { Release } from "@cdktf/provider-helm/lib/release";
import { Construct } from "constructs";
import { OnePasswordSecret, IngressRoute, IngressRouteTcp } from "../../utils";
import {
OnePasswordSecret,
PublicIngressRoute,
IngressRouteTcp,
} from "../../utils";
import type { Providers } from "../../types";
type GiteaServerOptions = {
@@ -69,20 +73,20 @@ export class GiteaServer extends Construct {
new IngressRouteTcp(this, "ssh-ingress", {
provider: kubernetes,
namespace: options.namespace,
name: options.name,
match: "HostSNI(`*`)",
entryPoint: "ssh",
serviceName: `${options.name}-ssh`,
servicePort: 22,
});
new IngressRoute(this, "http-ingress", {
new PublicIngressRoute(this, "http-ingress", {
provider: kubernetes,
namespace: options.namespace,
name: options.name,
entryPoints: ["websecure"],
host: "git.dogar.dev",
serviceName: `${options.name}-http`,
servicePort: 3000,
tlsSecretName: `${options.name}-tls`,
});
}
}

View File

@@ -4,7 +4,6 @@ import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
import { DataTerraformRemoteStateS3, TerraformStack } from "cdktf";
import { Construct } from "constructs";
import { ValkeyCluster } from "./valkey";
import { GiteaRunner, GiteaServer } from "./gitea";
import { AuthentikServer } from "./authentik";
import { PostgresCluster } from "./postgres";
@@ -26,24 +25,28 @@ export class UtilityServices extends TerraformStack {
const r2Endpoint = `${process.env.ACCOUNT_ID!}.r2.cloudflarestorage.com`;
const homelabState = new DataTerraformRemoteStateS3(this, "homelab-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}`,
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,
},
region: "auto",
accessKey: process.env.ACCESS_KEY,
secretKey: process.env.SECRET_KEY,
});
);
const namespaceName = homelabState.getString("namespace-output");
const namespaceName = coreServicesState.getString("namespace-output");
const namespaceResource = new DataKubernetesNamespaceV1(
this,
"homelab-namespace",
@@ -70,12 +73,6 @@ export class UtilityServices extends TerraformStack {
],
});
const valkeyCluster = new ValkeyCluster(this, "valkey-cluster", {
namespace,
provider: kubernetes,
name: "valkey",
});
const postgres = new PostgresCluster(this, "postgres-cluster", {
certManagerApiVersion: "cert-manager.io/v1",
name: "postgres-cluster",
@@ -96,7 +93,6 @@ export class UtilityServices extends TerraformStack {
namespace,
});
authentik.node.addDependency(valkeyCluster);
authentik.node.addDependency(postgres);
const gitea = new GiteaServer(this, "gitea-server", {

View File

@@ -1,4 +1,8 @@
export { CloudflareCertificate } from "./cert-manager";
export { OnePasswordSecret } from "./1password-secret";
export { IngressRoute, IngressRouteTcp } from "./traefik";
export {
PublicIngressRoute,
InternalIngressRoute,
IngressRouteTcp,
} from "./traefik";
export { LonghornPvc } from "./longhorn";

View File

@@ -1,2 +1,2 @@
export { IngressRoute } from "./ingress";
export { IngressRouteTcp } from "./ingress-tcp";
export { PublicIngressRoute, InternalIngressRoute } from "./ingress";
export { IngressRouteTcp } from "./ingressTCP";

View File

@@ -0,0 +1,2 @@
export { PublicIngressRoute } from "./publicIngress";
export { InternalIngressRoute } from "./internalIngress";

View File

@@ -2,11 +2,12 @@ import { Construct } from "constructs";
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
import { CloudflareCertificate } from "../cert-manager";
import { CloudflareCertificate } from "../../cert-manager";
type IngressRouteOptions = {
export type IngressRouteOptions = {
provider: KubernetesProvider;
namespace: string;
name: string;
/** Hostname for this route (e.g. npm.dogar.dev) */
host: string;
@@ -26,9 +27,6 @@ type IngressRouteOptions = {
/** Extra middlewares (traefik format: namespace/name) */
middlewares?: string[];
/** Name override (otherwise auto) */
name?: string;
};
export class IngressRoute extends Construct {
@@ -37,7 +35,7 @@ export class IngressRoute extends Construct {
constructor(scope: Construct, id: string, opts: IngressRouteOptions) {
super(scope, id);
const name = opts.name ?? `route-${opts.host.replace(/\./g, "-")}`;
const name = opts.name;
const path = opts.path ?? "/";
const entryPoints = opts.entryPoints ?? ["websecure"];

View File

@@ -0,0 +1,63 @@
import { Construct } from "constructs";
import { IngressRoute, IngressRouteOptions } from "./ingress";
import { DataTerraformRemoteStateS3 } from "cdktf";
import { DataKubernetesNamespaceV1 } from "@cdktf/provider-kubernetes/lib/data-kubernetes-namespace-v1";
type InternalIngressRouteOptions = Omit<
IngressRouteOptions,
"entryPoints" | "tlsSecretName" | "middlewares"
>;
export class InternalIngressRoute extends Construct {
constructor(scope: Construct, id: string, opts: InternalIngressRouteOptions) {
super(scope, id);
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,
"core-services-namespace",
{
provider: opts.provider,
metadata: {
name: namespaceName,
},
},
);
const namespace = namespaceResource.metadata.name;
new IngressRoute(this, opts.name, {
provider: opts.provider,
namespace: opts.namespace,
host: opts.host,
path: opts.path ?? "/",
serviceName: opts.serviceName,
servicePort: opts.servicePort,
entryPoints: ["websecure"],
tlsSecretName: `${opts.name}-tls`,
middlewares: [`${namespace}/ip-allow-list`],
name: opts.name,
});
}
}

View File

@@ -0,0 +1,63 @@
import { Construct } from "constructs";
import { IngressRoute, IngressRouteOptions } from "./ingress";
import { DataTerraformRemoteStateS3 } from "cdktf";
import { DataKubernetesNamespaceV1 } from "@cdktf/provider-kubernetes/lib/data-kubernetes-namespace-v1";
type PublicIngressRouteOptions = Omit<
IngressRouteOptions,
"entryPoints" | "tlsSecretName" | "middlewares"
>;
export class PublicIngressRoute extends Construct {
constructor(scope: Construct, id: string, opts: PublicIngressRouteOptions) {
super(scope, id);
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,
"core-services-namespace",
{
provider: opts.provider,
metadata: {
name: namespaceName,
},
},
);
const namespace = namespaceResource.metadata.name;
new IngressRoute(this, opts.name, {
provider: opts.provider,
namespace: opts.namespace,
host: opts.host,
path: opts.path ?? "/",
serviceName: opts.serviceName,
servicePort: opts.servicePort,
entryPoints: ["websecure"],
tlsSecretName: `${opts.name}-tls`,
middlewares: [`${namespace}/rate-limit`],
name: opts.name,
});
}
}

View File

@@ -0,0 +1 @@
export { IngressRouteTcp } from "./ingress-tcp";

View File

@@ -4,6 +4,13 @@ import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
type IngressRouteTcpOptions = {
provider: KubernetesProvider;
name: string;
/**
* Match rule.
* Default is `HostSNI(\`*\`)` which is correct for most TCP services.
*/
match: string;
/** Namespace where the IngressRouteTCP will be created */
namespace: string;
@@ -16,15 +23,6 @@ type IngressRouteTcpOptions = {
/** Backend service port */
servicePort: number;
/**
* Match rule.
* Default is `HostSNI(\`*\`)` which is correct for most TCP services.
*/
match?: string;
/** Name override (CR name) */
name?: string;
};
export class IngressRouteTcp extends Construct {
@@ -33,14 +31,7 @@ export class IngressRouteTcp extends Construct {
constructor(scope: Construct, id: string, opts: IngressRouteTcpOptions) {
super(scope, id);
const name =
opts.name ??
`tcp-${opts.entryPoint}-${opts.serviceName}`.replace(
/[^a-zA-Z0-9-]/g,
"",
);
const matchRule = opts.match ?? "HostSNI(`*`)";
const { name, match } = opts;
this.manifest = new Manifest(this, name, {
provider: opts.provider,
@@ -55,7 +46,7 @@ export class IngressRouteTcp extends Construct {
entryPoints: [opts.entryPoint],
routes: [
{
match: matchRule,
match,
services: [
{
name: opts.serviceName,