diff --git a/servers/gcp/docuseal.tf b/servers/gcp/docuseal.tf new file mode 100644 index 0000000..268ef1a --- /dev/null +++ b/servers/gcp/docuseal.tf @@ -0,0 +1,68 @@ +# ── DocuSeal GKE service account and Workload Identity ─────────────────────── +# DocuSeal runs in the `docuseal` namespace on GKE. It accesses GCP Secret +# Manager via Workload Identity — no JSON key files. +# +# Secret flow: Vault → GCP Secret Manager → ExternalSecret → k8s Secret → pod + +resource "google_service_account" "docuseal_gke" { + account_id = "docuseal-gke" + display_name = "DocuSeal GKE" + description = "Identity for DocuSeal pod on GKE. Accesses Secret Manager via Workload Identity." + project = var.project_id +} + +# ── Secret Manager access ───────────────────────────────────────────────────── + +resource "google_secret_manager_secret_iam_member" "docuseal_secret_key_base" { + project = var.project_id + secret_id = "docuseal-secret-key-base" + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.docuseal_gke.email}" +} + +resource "google_secret_manager_secret_iam_member" "docuseal_smtp_host" { + project = var.project_id + secret_id = "docuseal-smtp-host" + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.docuseal_gke.email}" +} + +resource "google_secret_manager_secret_iam_member" "docuseal_smtp_port" { + project = var.project_id + secret_id = "docuseal-smtp-port" + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.docuseal_gke.email}" +} + +resource "google_secret_manager_secret_iam_member" "docuseal_smtp_username" { + project = var.project_id + secret_id = "docuseal-smtp-username" + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.docuseal_gke.email}" +} + +resource "google_secret_manager_secret_iam_member" "docuseal_smtp_password" { + project = var.project_id + secret_id = "docuseal-smtp-password" + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.docuseal_gke.email}" +} + +resource "google_secret_manager_secret_iam_member" "docuseal_smtp_from" { + project = var.project_id + secret_id = "docuseal-smtp-from" + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.docuseal_gke.email}" +} + +# ── Workload Identity binding ───────────────────────────────────────────────── +# The k8s SA `docuseal` in the `docuseal` namespace can impersonate this GCP SA. +# ESO uses this to authenticate to Secret Manager without a key file. + +resource "google_service_account_iam_member" "docuseal_workload_identity" { + service_account_id = google_service_account.docuseal_gke.name + role = "roles/iam.workloadIdentityUser" + member = "serviceAccount:${var.project_id}.svc.id.goog[docuseal/docuseal]" + + depends_on = [google_container_cluster.neuron_platform] +} diff --git a/servers/gcp/k8s/argocd-apps/docuseal-gke.yaml b/servers/gcp/k8s/argocd-apps/docuseal-gke.yaml new file mode 100644 index 0000000..db3c9f0 --- /dev/null +++ b/servers/gcp/k8s/argocd-apps/docuseal-gke.yaml @@ -0,0 +1,26 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: docuseal-gke + namespace: argocd +spec: + project: default + source: + repoURL: https://git.neuralplatform.ai/will/infrastructure.git + targetRevision: main + path: servers/gcp/k8s/docuseal + destination: + server: https://34.63.89.52 + namespace: docuseal + ignoreDifferences: + - group: apps + kind: Deployment + jsonPointers: + - /status + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/servers/gcp/k8s/docuseal/deployment.yaml b/servers/gcp/k8s/docuseal/deployment.yaml new file mode 100644 index 0000000..300dd6a --- /dev/null +++ b/servers/gcp/k8s/docuseal/deployment.yaml @@ -0,0 +1,101 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: docuseal + namespace: docuseal + labels: + app: docuseal +spec: + replicas: 1 + # Recreate — only one pod can hold the RWO PVC at a time. + strategy: + type: Recreate + selector: + matchLabels: + app: docuseal + template: + metadata: + labels: + app: docuseal + spec: + serviceAccountName: docuseal + containers: + - name: docuseal + image: ee.docuseal.com/lip5viwf/ds-ee:latest + ports: + - containerPort: 3000 + env: + - name: SECRET_KEY_BASE + valueFrom: + secretKeyRef: + name: docuseal-secrets + key: secret_key_base + - name: FORCE_SSL + value: "false" + - name: HOST + value: "sign.neurontechnologies.ai" + - name: SMTP_ADDRESS + valueFrom: + secretKeyRef: + name: docuseal-secrets + key: smtp_host + - name: SMTP_PORT + valueFrom: + secretKeyRef: + name: docuseal-secrets + key: smtp_port + - name: SMTP_USERNAME + valueFrom: + secretKeyRef: + name: docuseal-secrets + key: smtp_username + - name: SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: docuseal-secrets + key: smtp_password + - name: SMTP_FROM + valueFrom: + secretKeyRef: + name: docuseal-secrets + key: smtp_from + volumeMounts: + - name: data + mountPath: /data + readinessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 15 + periodSeconds: 10 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + volumes: + - name: data + persistentVolumeClaim: + claimName: docuseal-data +--- +apiVersion: v1 +kind: Service +metadata: + name: docuseal + namespace: docuseal + annotations: + # External GCP Network LB — Cloudflare proxies in front, provides TLS. + # After provisioning, get the external IP: + # kubectl -n docuseal get svc docuseal + # Update Cloudflare DNS A record for sign.neurontechnologies.ai to this IP. + cloud.google.com/load-balancer-type: "External" +spec: + selector: + app: docuseal + ports: + - name: http + port: 80 + targetPort: 3000 + type: LoadBalancer diff --git a/servers/gcp/k8s/docuseal/external-secrets.yaml b/servers/gcp/k8s/docuseal/external-secrets.yaml new file mode 100644 index 0000000..3298bf6 --- /dev/null +++ b/servers/gcp/k8s/docuseal/external-secrets.yaml @@ -0,0 +1,56 @@ +--- +# SecretStore for GKE — uses GCP Secret Manager directly via Workload Identity. +# The DocuSeal GCP SA has secretmanager.secretAccessor on its own secrets +# (see servers/gcp/docuseal.tf). +apiVersion: external-secrets.io/v1 +kind: SecretStore +metadata: + name: gcp-secretmanager + namespace: docuseal +spec: + provider: + gcpsm: + projectID: neuron-785695 + # Workload Identity — ESO impersonates the docuseal-gke GCP SA to access + # Secret Manager. No JSON key file required. + auth: + workloadIdentity: + clusterLocation: us-central1 + clusterName: neuron-platform + serviceAccountRef: + name: docuseal + namespace: docuseal +--- +# Pull all DocuSeal secrets from GCP Secret Manager into a single k8s Secret. +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: docuseal-secrets + namespace: docuseal +spec: + refreshInterval: 1h + secretStoreRef: + name: gcp-secretmanager + kind: SecretStore + target: + name: docuseal-secrets + creationPolicy: Owner + data: + - secretKey: secret_key_base + remoteRef: + key: docuseal-secret-key-base + - secretKey: smtp_host + remoteRef: + key: docuseal-smtp-host + - secretKey: smtp_port + remoteRef: + key: docuseal-smtp-port + - secretKey: smtp_username + remoteRef: + key: docuseal-smtp-username + - secretKey: smtp_password + remoteRef: + key: docuseal-smtp-password + - secretKey: smtp_from + remoteRef: + key: docuseal-smtp-from diff --git a/servers/gcp/k8s/docuseal/namespace.yaml b/servers/gcp/k8s/docuseal/namespace.yaml new file mode 100644 index 0000000..dfab9d5 --- /dev/null +++ b/servers/gcp/k8s/docuseal/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: docuseal + labels: + app.kubernetes.io/name: docuseal diff --git a/servers/gcp/k8s/docuseal/pvc.yaml b/servers/gcp/k8s/docuseal/pvc.yaml new file mode 100644 index 0000000..19966d5 --- /dev/null +++ b/servers/gcp/k8s/docuseal/pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: docuseal-data + namespace: docuseal +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: standard-rwo diff --git a/servers/gcp/k8s/docuseal/serviceaccount.yaml b/servers/gcp/k8s/docuseal/serviceaccount.yaml new file mode 100644 index 0000000..892212c --- /dev/null +++ b/servers/gcp/k8s/docuseal/serviceaccount.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: docuseal + namespace: docuseal + annotations: + # Workload Identity — allows ESO to authenticate to GCP Secret Manager + # as the docuseal-gke GCP SA without a JSON key file. + # The GCP SA binding is in servers/gcp/docuseal.tf (docuseal_workload_identity). + iam.gke.io/gcp-service-account: docuseal-gke@neuron-785695.iam.gserviceaccount.com diff --git a/servers/legion/k8s/gitea-runner/Dockerfile b/servers/legion/k8s/gitea-runner/Dockerfile index 361f71e..9e8474c 100644 --- a/servers/legion/k8s/gitea-runner/Dockerfile +++ b/servers/legion/k8s/gitea-runner/Dockerfile @@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND=noninteractive COPY --from=runner-bin /bin/forgejo-runner /usr/local/bin/act_runner -# Core system tools +# Core system tools + C build deps needed by El compiler and other CI jobs RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ @@ -27,8 +27,19 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ pkg-config \ software-properties-common \ zstd \ + sudo \ + libcurl4-openssl-dev \ + libssl-dev \ + libsqlite3-dev \ + libpq-dev \ + libffi-dev \ + zlib1g-dev \ + dpkg-dev \ && rm -rf /var/lib/apt/lists/* +# Allow any user to run sudo without a password (CI containers need apt-get etc.) +RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + # Node.js 20 LTS via binary tarball (nodesource apt repo is unreliable on Ubuntu 24.04) RUN NODE_VERSION=20.19.1 \ && curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \