From c47ac189c982cefb38c12ef55437907d7f8cf1c7 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 19 Jan 2026 07:12:51 -0800 Subject: [PATCH] Migrate Tailscale operator to ArgoCD management (Phase 1 Step 5) Adds ArgoCD Application to manage Tailscale operator from forge: - ArgoCD Application sourced from internal Forgejo via SSH - DNS config for cluster-to-tailnet name resolution - Egress proxy for accessing forge on indri - ACL grants for k8s workloads to reach forge (ports 3001, 2200) - Template for repository secret with 1Password SSH key reference Key discovery: 1Password op read requires ?ssh-format=openssh parameter to get keys in OpenSSH format that ArgoCD's SSH client can read. Co-Authored-By: Claude Opus 4.5 --- argocd/apps/tailscale-operator.yaml | 22 +++++++++++++++ .../argocd/repo-forge-secret.yaml.tpl | 27 +++++++++++++++++++ .../tailscale-operator/dnsconfig.yaml | 16 +++++++++++ .../tailscale-operator/egress-forge.yaml | 20 ++++++++++++++ pulumi/policy.hujson | 11 ++++++-- 5 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 argocd/apps/tailscale-operator.yaml create mode 100644 argocd/manifests/argocd/repo-forge-secret.yaml.tpl create mode 100644 argocd/manifests/tailscale-operator/dnsconfig.yaml create mode 100644 argocd/manifests/tailscale-operator/egress-forge.yaml diff --git a/argocd/apps/tailscale-operator.yaml b/argocd/apps/tailscale-operator.yaml new file mode 100644 index 0000000..041f676 --- /dev/null +++ b/argocd/apps/tailscale-operator.yaml @@ -0,0 +1,22 @@ +# ArgoCD Application for Tailscale Kubernetes Operator +# Note: OAuth secret is managed separately (not in git) +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: tailscale-operator + namespace: argocd +spec: + project: default + source: + repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + targetRevision: feature/k8s-phase1-kickoff + path: argocd/manifests/tailscale-operator + destination: + server: https://kubernetes.default.svc + namespace: tailscale + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/argocd/manifests/argocd/repo-forge-secret.yaml.tpl b/argocd/manifests/argocd/repo-forge-secret.yaml.tpl new file mode 100644 index 0000000..f4a2d53 --- /dev/null +++ b/argocd/manifests/argocd/repo-forge-secret.yaml.tpl @@ -0,0 +1,27 @@ +# ArgoCD repository secret for forge SSH access +# +# IMPORTANT: Use ?ssh-format=openssh to get OpenSSH format (required by ArgoCD) +# +# Create the secret with: +# +# PRIV_KEY=$(op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/csjncynh6htjvnh2l2da65y32q/private key?ssh-format=openssh")$'\n' && \ +# kubectl create secret generic repo-forge -n argocd \ +# --from-literal=type=git \ +# --from-literal=url='ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git' \ +# --from-literal=insecure=true \ +# --from-literal=sshPrivateKey="$PRIV_KEY" && \ +# kubectl label secret repo-forge -n argocd argocd.argoproj.io/secret-type=repository +# +apiVersion: v1 +kind: Secret +metadata: + name: repo-forge + namespace: argocd + labels: + argocd.argoproj.io/secret-type: repository +stringData: + type: git + url: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + insecure: "true" + sshPrivateKey: | + # Key from 1Password: op://vg6xf6vvfmoh5hqjjhlhbeoaie/csjncynh6htjvnh2l2da65y32q/private key diff --git a/argocd/manifests/tailscale-operator/dnsconfig.yaml b/argocd/manifests/tailscale-operator/dnsconfig.yaml new file mode 100644 index 0000000..867d3dd --- /dev/null +++ b/argocd/manifests/tailscale-operator/dnsconfig.yaml @@ -0,0 +1,16 @@ +# DNSConfig for resolving MagicDNS names from within the cluster +# Deploys a nameserver that resolves ts.net names to egress proxy IPs +# +# Requires CoreDNS/kube-dns configuration to forward ts.net queries. +# See: https://tailscale.com/kb/1438/kubernetes-operator-cluster-egress +--- +apiVersion: tailscale.com/v1alpha1 +kind: DNSConfig +metadata: + name: ts-dns + namespace: tailscale +spec: + nameserver: + image: + repo: docker.io/tailscale/k8s-nameserver + tag: stable diff --git a/argocd/manifests/tailscale-operator/egress-forge.yaml b/argocd/manifests/tailscale-operator/egress-forge.yaml new file mode 100644 index 0000000..8705eea --- /dev/null +++ b/argocd/manifests/tailscale-operator/egress-forge.yaml @@ -0,0 +1,20 @@ +# Egress proxy to expose Forgejo (forge) to the cluster +# Forge runs on indri:3001, exposed via Tailscale Serve as forge.tail8d86e.ts.net +# We target indri directly since egress can't reach Tailscale Serve hostnames +# +# See: https://tailscale.com/kb/1438/kubernetes-operator-cluster-egress +--- +apiVersion: v1 +kind: Service +metadata: + name: forge + namespace: tailscale + annotations: + tailscale.com/tailnet-fqdn: indri.tail8d86e.ts.net + tailscale.com/proxy-class: "default" +spec: + type: ExternalName + externalName: placeholder + ports: + - port: 3001 + targetPort: 3001 diff --git a/pulumi/policy.hujson b/pulumi/policy.hujson index 10ded63..c575037 100644 --- a/pulumi/policy.hujson +++ b/pulumi/policy.hujson @@ -67,6 +67,13 @@ "dst": ["tag:registry"], "ip": ["tcp:443"], }, + // k8s workloads (e.g., ArgoCD) can access forge on indri for GitOps + // HTTP on 3001, SSH on 2200 + { + "src": ["tag:k8s"], + "dst": ["tag:homelab"], + "ip": ["tcp:3001", "tcp:2200"], + }, ], // ============== SSH Access ============== @@ -133,10 +140,10 @@ "src": "tag:homelab", "accept": ["tag:homelab:22", "tag:nas:445"], }, - // K8s workloads can reach registry + // K8s workloads can reach registry and forge (on indri:3001 HTTP, :2200 SSH) { "src": "tag:k8s", - "accept": ["tag:registry:443"], + "accept": ["tag:registry:443", "tag:homelab:3001", "tag:homelab:2200"], }, ], }