import * as gcp from "@pulumi/gcp"; import * as pulumi from "@pulumi/pulumi"; import * as assets from "../assets"; interface ProcessArgs { advertisedRoutes: pulumi.Input[]>; authKey: string; network: gcp.compute.Network; project: string; subnetwork: gcp.compute.Subnetwork; tagName: string; zone: string; } export function process({ advertisedRoutes, authKey, network, project, subnetwork, tagName, zone, }: ProcessArgs) { const serviceAccount = new gcp.serviceaccount.Account( "tailscale-service-account", { accountId: "tailscale", description: "Allow tailscale access to GCP", displayName: "Tailscale Service Account", project, }, ); new gcp.compute.Firewall("tailscale-ipv4-udp-firewall", { allows: [ { ports: ["41641"], protocol: "udp", }, ], direction: "INGRESS", name: "fw-ipv4-udp-to-tailscale", network: network.selfLink, project, sourceRanges: ["0.0.0.0/0"], targetServiceAccounts: [serviceAccount.email], }); new gcp.compute.Firewall("tailscale-ipv6-udp-firewall", { allows: [ { ports: ["41641"], protocol: "udp", }, ], direction: "INGRESS", name: "fw-ipv6-udp-to-tailscale", network: network.selfLink, project, sourceRanges: ["::/0"], targetServiceAccounts: [serviceAccount.email], }); const secretId = "tailscale-auth-key"; const authKeySecret = new gcp.secretmanager.Secret( "tailscale-auth-key-secret", { labels: { managed_by: "pulumi", }, project, replication: { auto: {}, }, secretId, }, ); new gcp.secretmanager.SecretVersion("tailscale-auth-key-secret-version", { enabled: true, secret: authKeySecret.id, secretData: authKey, }); new gcp.secretmanager.SecretIamBinding( "tailscale-auth-key-secret-iam-binding", { members: [serviceAccount.member], project, role: "roles/secretmanager.secretAccessor", secretId: authKeySecret.id, }, ); const rawStartupScript = assets.readAsset("tailscale_startup.sh"); const startupScript = pulumi .all([rawStartupScript, advertisedRoutes, secretId, tagName]) .apply(([rss, ar, s, ttn]) => { return rss .replace("<>", ar.join(",")) .replace("<>", s) .replace("<>", ttn); }); const instanceTemplate = new gcp.compute.InstanceTemplate( "tailscale-subnet-router-instance-template", { canIpForward: true, disks: [ { autoDelete: true, boot: true, diskSizeGb: 10, diskType: "pd-standard", mode: "READ_WRITE", sourceImage: "projects/ubuntu-os-cloud/global/images/ubuntu-minimal-2410-oracular-amd64-v20250312", type: "PERSISTENT", }, ], machineType: "e2-micro", metadataStartupScript: startupScript, namePrefix: "tailscale-subnet-router-", networkInterfaces: [ { network: network.selfLink, stackType: "IPV4_ONLY", subnetwork: subnetwork.selfLink, }, ], project, serviceAccount: { email: serviceAccount.email, scopes: ["https://www.googleapis.com/auth/cloud-platform"], }, }, ); const healthCheck = new gcp.compute.HealthCheck( "tailscale-subnet-router-healthcheck", { checkIntervalSec: 10, description: "Tailscale healthcheck which checks that /metrics is available.", healthyThreshold: 3, httpHealthCheck: { port: 5252, requestPath: "/metrics", }, name: "tailscale-metrics-healthcheck", project, unhealthyThreshold: 3, }, ); new gcp.compute.InstanceGroupManager( "tailscale-subnet-router-instance-group-manager", { baseInstanceName: "tailscale-subnet-router", autoHealingPolicies: { healthCheck: healthCheck.selfLink, initialDelaySec: 15, }, name: "tailscale-subnet-router", project, standbyPolicy: { mode: "MANUAL", }, targetSize: 2, updatePolicy: { maxSurgeFixed: 1, maxUnavailableFixed: 1, minimalAction: "REPLACE", type: "PROACTIVE", }, versions: [ { instanceTemplate: instanceTemplate.selfLinkUnique, }, ], zone, }, ); }