diff --git a/main.ts b/main.ts index 5339f42..42d6e14 100644 --- a/main.ts +++ b/main.ts @@ -9,6 +9,7 @@ import { NetworkSecurity } from "./network-security"; import { GamingServices } from "./gaming-services/minecraft"; import { MediaServices } from "./media-services"; import { PKI } from "./pki"; +import { Netbird } from "./netbird"; dotenv.config(); @@ -46,6 +47,9 @@ mediaServices.node.addDependency(networkSecurity); const caches = new CacheInfrastructure(app, "cache-infrastructure"); caches.node.addDependency(utilityServices); +const netbird = new Netbird(app, "netbird"); +netbird.node.addDependency(utilityServices); + const deploy: (stack: TerraformStack, key: string) => S3Backend = ( stack, key, @@ -75,5 +79,6 @@ deploy(utilityServices, "utility-services"); deploy(caches, "cache-infrastructure"); deploy(gamingServices, "gaming-services"); deploy(mediaServices, "media-services"); +deploy(netbird, "netbird"); app.synth(); diff --git a/netbird/index.ts b/netbird/index.ts new file mode 100644 index 0000000..042a579 --- /dev/null +++ b/netbird/index.ts @@ -0,0 +1,95 @@ +import * as fs from "fs"; +import * as path from "path"; +import { Construct } from "constructs"; +import { TerraformStack } from "cdktf"; +import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; +import { NamespaceV1 } from "@cdktf/provider-kubernetes/lib/namespace-v1"; +import { DataKubernetesSecretV1 } from "@cdktf/provider-kubernetes/lib/data-kubernetes-secret-v1"; +import { HelmProvider } from "@cdktf/provider-helm/lib/provider"; +import { SecretV1 } from "@cdktf/provider-kubernetes/lib/secret-v1"; +import { Release } from "@cdktf/provider-helm/lib/release"; +import { CloudflareCertificate, OnePasswordSecret } from "../utils"; + +export class Netbird extends TerraformStack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const kubernetes = new KubernetesProvider(this, "kubernetes", { + configPath: "~/.kube/config", + }); + + const helm = new HelmProvider(this, "helm", { + kubernetes: { + configPath: "~/.kube/config", + }, + }); + + const namespace = "netbird"; + + // Create namespace + new NamespaceV1(this, "namespace", { + metadata: { + name: namespace, + }, + }); + + new OnePasswordSecret(this, "netbird-secret", { + name: "netbird", + namespace, + provider: kubernetes, + itemPath: "vaults/Lab/items/Netbird", + }); + + const pgClientCert = new DataKubernetesSecretV1( + this, + "netbird-client-cert", + { + provider: kubernetes, + metadata: { + name: "netbird-client-cert", + namespace: "homelab", + }, + }, + ); + + const pgCaCert = new DataKubernetesSecretV1(this, "postgres-ca-cert", { + provider: kubernetes, + metadata: { + name: "postgres-server-cert", + namespace: "homelab", + }, + }); + + const pgSslBundle = new SecretV1(this, "netbird-postgres-ssl", { + provider: kubernetes, + metadata: { + name: "netbird-postgres-ssl-bundle", + namespace, + }, + data: { + "tls.crt": pgClientCert.data.lookup("tls.crt"), + "tls.key": pgClientCert.data.lookup("tls.key"), + "ca.crt": pgCaCert.data.lookup("ca.crt"), + }, + }); + + new CloudflareCertificate(this, "netbird-cloudflare-cert", { + provider: kubernetes, + name: "netbird", + namespace, + dnsNames: ["vpn.dogar.dev"], + secretName: "netbird-tls", + }); + + new Release(this, "netbird", { + dependsOn: [pgSslBundle], + provider: helm, + namespace, + createNamespace: true, + name: "netbird", + repository: "https://netbirdio.github.io/helms", + chart: "netbird", + values: [fs.readFileSync(path.join(__dirname, "values.yaml"), "utf8")], + }).importFrom("netbird/netbird"); + } +} diff --git a/netbird/values.yaml b/netbird/values.yaml new file mode 100644 index 0000000..28ed83f --- /dev/null +++ b/netbird/values.yaml @@ -0,0 +1,218 @@ +fullnameOverride: netbird +management: + configmap: |- + { + "Stuns": [ + { + "Proto": "udp", + "URI": "{{ .STUN_SERVER }}", + "Username": "", + "Password": "" + } + ], + "TURNConfig": { + "TimeBasedCredentials": false, + "CredentialsTTL": "12h0m0s", + "Secret": "secret", + "Turns": [ + { + "Proto": "udp", + "URI": "{{ .TURN_SERVER }}", + "Username": "{{ .TURN_SERVER_USER }}", + "Password": "{{ .TURN_SERVER_PASSWORD }}" + } + ] + }, + "Relay": { + "Addresses": ["rels://vpn.dogar.dev:443/relay"], + "CredentialsTTL": "24h", + "Secret": "{{ .RELAY_PASSWORD }}" + }, + "Signal": { + "Proto": "https", + "URI": "vpn.dogar.dev:443", + "Username": "", + "Password": "" + }, + "Datadir": "/var/lib/netbird/", + "DataStoreEncryptionKey": "{{ .DATASTORE_ENCRYPTION_KEY }}", + "HttpConfig": { + "LetsEncryptDomain": "", + "CertFile": "", + "CertKey": "", + "AuthAudience": "{{ .IDP_CLIENT_ID }}", + "AuthIssuer": "https://auth.dogar.dev/application/o/netbird/", + "AuthUserIDClaim": "", + "AuthKeysLocation": "https://auth.dogar.dev/application/o/netbird/jwks/", + "OIDCConfigEndpoint": "https://auth.dogar.dev/application/o/netbird/.well-known/openid-configuration", + "IdpSignKeyRefreshEnabled": false + }, + "IdpManagerConfig": { + "ManagerType": "authentik", + "ClientConfig": { + "Issuer": "https://auth.dogar.dev/application/o/netbird", + "TokenEndpoint": "https://auth.dogar.dev/application/o/token/", + "ClientID": "{{ .IDP_CLIENT_ID }}", + "ClientSecret": "", + "GrantType": "client_credentials" + }, + "ExtraConfig": { + "Password": "{{ .IDP_SERVICE_ACCOUNT_PASSWORD }}", + "Username": "{{ .IDP_SERVICE_ACCOUNT_USER }}" + }, + "Auth0ClientCredentials": null, + "AzureClientCredentials": null, + "KeycloakClientCredentials": null, + "ZitadelClientCredentials": null + }, + "DeviceAuthorizationFlow": { + "Provider": "hosted", + "ProviderConfig": { + "ClientID": "{{ .IDP_CLIENT_ID }}", + "ClientSecret": "", + "Domain": "auth.dogar.dev", + "Audience": "{{ .IDP_CLIENT_ID }}", + "TokenEndpoint": "https://auth.dogar.dev/application/o/token/", + "DeviceAuthEndpoint": "https://auth.dogar.dev/application/o/device/", + "AuthorizationEndpoint": "", + "Scope": "openid", + "UseIDToken": false, + "RedirectURLs": null + } + }, + "PKCEAuthorizationFlow": { + "ProviderConfig": { + "ClientID": "{{ .IDP_CLIENT_ID }}", + "ClientSecret": "", + "Domain": "", + "Audience": "{{ .IDP_CLIENT_ID }}", + "TokenEndpoint": "https://auth.dogar.dev/application/o/token/", + "DeviceAuthEndpoint": "", + "AuthorizationEndpoint": "https://auth.dogar.dev/application/o/authorize/", + "Scope": "openid profile email offline_access api", + "UseIDToken": false, + "RedirectURLs": ["http://localhost:53000"] + } + }, + "StoreConfig": { + "Engine": "postgres" + }, + "ReverseProxy": { + "TrustedHTTPProxies": null, + "TrustedHTTPProxiesCount": 0, + "TrustedPeers": null + } + } + + persistentVolume: + enabled: true + storageClass: longhorn + size: 1Gi + envFromSecret: + NETBIRD_STORE_ENGINE_POSTGRES_DSN: netbird/postgresDSN + STUN_SERVER: netbird/stunServer + TURN_SERVER: netbird/turnServer + TURN_SERVER_USER: netbird/turnServerUser + TURN_SERVER_PASSWORD: netbird/turnServerPassword + RELAY_PASSWORD: netbird/relayPassword + IDP_CLIENT_ID: netbird/idpClientID + IDP_SERVICE_ACCOUNT_USER: netbird/idpServiceAccountUser + IDP_SERVICE_ACCOUNT_PASSWORD: netbird/idpServiceAccountPassword + DATASTORE_ENCRYPTION_KEY: netbird/datastoreEncryptionKey + livenessProbe: + failureThreshold: 3 + initialDelaySeconds: 180 + periodSeconds: 10 + timeoutSeconds: 3 + tcpSocket: + port: http + volumes: + - name: postgres-ssl-bundle + secret: + secretName: netbird-postgres-ssl-bundle + volumeMounts: + - name: postgres-ssl-bundle + mountPath: /etc/ssl/certs/postgres-ssl-bundle + readOnly: true + +signal: + enabled: true + +relay: + envFromSecret: + NB_AUTH_SECRET: netbird/relayPassword + env: + NB_LOG_LEVEL: info + NB_LISTEN_ADDRESS: ":33080" + NB_EXPOSED_ADDRESS: rels://vpn.dogar.dev:443/relay + +dashboard: + enabled: true + env: + # Endpoints + NETBIRD_MGMT_API_ENDPOINT: https://vpn.dogar.dev:443 + NETBIRD_MGMT_GRPC_API_ENDPOINT: https://vpn.dogar.dev:443 + # OIDC + AUTH_CLIENT_SECRET: + AUTH_AUTHORITY: https://auth.dogar.dev/application/o/netbird/ + USE_AUTH0: false + AUTH_SUPPORTED_SCOPES: openid profile email offline_access api + AUTH_REDIRECT_URI: + AUTH_SILENT_REDIRECT_URI: + NETBIRD_TOKEN_SOURCE: accessToken + NGINX_SSL_PORT: + LETSENCRYPT_DOMAIN: + LETSENCRYPT_EMAIL: + envFromSecret: + AUTH_CLIENT_ID: netbird/idpClientID + AUTH_AUDIENCE: netbird/idpClientID + +extraManifests: + - apiVersion: traefik.io/v1alpha1 + kind: IngressRoute + metadata: + name: netbird + namespace: netbird + spec: + entryPoints: + - websecure + routes: + - kind: Rule + match: Host(`vpn.dogar.dev`) && !PathPrefix(`/api`) && !PathPrefix(`/management`) && !PathPrefix(`/signalexchange`) && !PathPrefix(`/relay`) + services: + - name: netbird-dashboard + namespace: netbird + passHostHeader: true + port: 80 + - kind: Rule + match: Host(`vpn.dogar.dev`) && PathPrefix(`/api`) + services: + - name: netbird-management + namespace: netbird + passHostHeader: true + port: 80 + - kind: Rule + match: Host(`vpn.dogar.dev`) && PathPrefix(`/relay`) + services: + - name: netbird-relay + namespace: netbird + passHostHeader: true + port: 33080 + - kind: Rule + match: Host(`vpn.dogar.dev`) && PathPrefix(`/management`) + services: + - name: netbird-management + namespace: netbird + passHostHeader: true + port: 80 + scheme: h2c + - kind: Rule + match: Host(`vpn.dogar.dev`) && PathPrefix(`/signalexchange`) + services: + - name: netbird-signal + namespace: netbird + passHostHeader: true + port: 80 + scheme: h2c + tls: + secretName: netbird-tls diff --git a/utility-services/index.ts b/utility-services/index.ts index 7a21f29..ea759e8 100644 --- a/utility-services/index.ts +++ b/utility-services/index.ts @@ -78,7 +78,7 @@ export class UtilityServices extends TerraformStack { name: "postgres-cluster", namespace, provider: kubernetes, - users: ["shahab", "budget-tracker", "authentik", "gitea"], + users: ["shahab", "budget-tracker", "authentik", "gitea", "netbird"], primaryUser: "shahab", initSecretName: "postgres-password", backupR2EndpointURL: `https://${r2Endpoint}`,