Files
Shadowbroker/.gitlab-ci.yml
T
Shadowbroker 41799f9891 feat(ci): switch GitLab mirror-to-github job to per-repo SSH deploy key (#331)
* feat(ci): switch mirror-to-github job from PAT to per-repo SSH deploy key

GitHub fine-grained PATs are capped at 366 days, classic PATs would
need 'public_repo' (broader scope than needed). Per-repo SSH deploy
keys are tighter:
- Can ONLY push to BigBodyCobain/Shadowbroker (no access to anything
  else, not even other repos owned by the same account).
- Never expire.
- Rotating == one-click delete on github.com/.../settings/keys.

Changes:
- New CI/CD variable GITHUB_MIRROR_SSH_KEY (File, Protected) holding
  the ed25519 private half. Public half lives on the repo's deploy
  keys with write access enabled.
- mirror-to-github before_script writes the key to ~/.ssh/id_ed25519,
  pins github.com host fingerprints (ed25519 + ecdsa + rsa from the
  2023-03-24 rotation) into ~/.ssh/known_hosts so we never trust a
  MITM, then pushes via git@github.com:... instead of HTTPS.
- Job rule now gates on GITHUB_MIRROR_SSH_KEY (the new var) instead
  of GITHUB_MIRROR_TOKEN (which never existed).

After this lands, every commit pushed directly to GitLab main will
mirror back to GitHub main automatically — closing the loop on
bi-directional sync.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(secret-scan): exempt SSH known_hosts entries from leaked-key detection

PR #331 introduced github.com host fingerprints pinned in
.gitlab-ci.yml's mirror-to-github before_script. The scanner flagged
them as embedded secrets and blocked CI:

  BLOCKED: Embedded secrets/tokens found in:
    .gitlab-ci.yml
      133: github.com ssh-ed25519 AAAA...
      135: github.com ssh-rsa AAAA...

These are PUBLIC host keys — the whole point of pinning known_hosts is
to publish the fingerprint widely so a MITM is detectable. They are
documented at https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints
and committing them is the correct, secure practice.

Fix: add a KNOWN_HOSTS_LINE regex to the content-scan block that
recognizes `<host-or-ip> [salt] <algo> AAAA...` shape lines (the
exact format used in ~/.ssh/known_hosts) and filters them out before
flagging the file. Bare `ssh-rsa AAAA...` lines without a host prefix
are still caught — only the host-key shape is exempt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 04:22:09 -06:00

152 lines
7.0 KiB
YAML

# GitLab CI/CD for Shadowbroker
#
# Mirror of .github/workflows/docker-publish.yml — keeps the GitLab install
# path (image registry + source) at parity with GitHub so users who prefer
# GitLab get the same experience.
#
# What this does on every push to main:
# 1. Builds multi-arch (amd64 + arm64) Docker images for the backend and
# frontend, pushes them to the project's GitLab Container Registry:
# registry.gitlab.com/bigbodycobain/shadowbroker/backend:latest
# registry.gitlab.com/bigbodycobain/shadowbroker/frontend:latest
# Both also get a :$CI_COMMIT_SHORT_SHA tag for traceability.
# 2. Reverse-mirrors main back to GitHub (only if commits land directly
# on GitLab) so the two sources stay in sync.
#
# Pipelines on this repo were instant-failing for free-tier accounts until
# identity verification was added — the May 2026 bump in this comment is
# the marker commit that confirms runner allocation after verification.
#
# Auth notes:
# - The image build/push uses $CI_JOB_TOKEN, which GitLab provides
# automatically. No credentials need to be configured.
# - The reverse mirror authenticates to GitHub via a per-repo SSH
# deploy key. The private half is stored as the File-type GitLab
# CI/CD variable GITHUB_MIRROR_SSH_KEY (Protected). The matching
# public key is added to github.com/BigBodyCobain/Shadowbroker/
# settings/keys with write access. This is a tighter-scoped
# replacement for a personal access token: it can ONLY push to
# Shadowbroker, never expires, and rotating it is a one-click
# delete on GitHub's deploy-keys page. If the variable isn't set,
# the mirror job is skipped — image builds still run.
stages:
- build
- mirror
variables:
# Use the dind service for buildx multi-arch builds.
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_DRIVER: overlay2
# QEMU is what lets a single x86 runner build arm64 images. dind doesn't
# install it by default; we install via tonistiigi/binfmt below.
BUILDX_VERSION: "v0.14.1"
# Repository-relative paths.
BACKEND_IMAGE: $CI_REGISTRY_IMAGE/backend
FRONTEND_IMAGE: $CI_REGISTRY_IMAGE/frontend
# Shared template: bootstraps buildx + QEMU on the dind service so a single
# runner can produce both amd64 and arm64 manifests in one push.
.buildx-setup: &buildx-setup
image: docker:24
services:
- name: docker:24-dind
command: ["--tls=true"]
before_script:
- docker info
- docker login -u "$CI_REGISTRY_USER" -p "$CI_JOB_TOKEN" "$CI_REGISTRY"
- docker run --privileged --rm tonistiigi/binfmt --install all
# buildx --driver docker-container can't read TLS from the env vars
# the GitLab dind service exports. Wrap them in a docker context and
# bind buildx to it. See https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-buildx
- docker context create tls-env
- docker buildx create --use --name multiarch --driver docker-container tls-env
# ── Backend image ────────────────────────────────────────────────────────
build-backend:
<<: *buildx-setup
stage: build
script:
- >
docker buildx build
--platform linux/amd64,linux/arm64
--file backend/Dockerfile
--tag $BACKEND_IMAGE:latest
--tag $BACKEND_IMAGE:$CI_COMMIT_SHORT_SHA
--push
.
rules:
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "schedule"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- backend/**/*
- .gitlab-ci.yml
# ── Frontend image ───────────────────────────────────────────────────────
build-frontend:
<<: *buildx-setup
stage: build
script:
- cd frontend
- >
docker buildx build
--platform linux/amd64,linux/arm64
--tag $FRONTEND_IMAGE:latest
--tag $FRONTEND_IMAGE:$CI_COMMIT_SHORT_SHA
--push
.
rules:
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "schedule"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- frontend/**/*
- .gitlab-ci.yml
# ── Reverse mirror to GitHub ─────────────────────────────────────────────
# Pushes refs/heads/main to github.com/BigBodyCobain/Shadowbroker via SSH
# using a per-repo deploy key. Fast-forward-only by default — if GitLab
# main and GitHub main have diverged, the push fails loudly rather than
# silently overwriting either side.
#
# Only runs if GITHUB_MIRROR_SSH_KEY is set as a File-type CI/CD variable.
# See the header comment of this file for setup instructions.
mirror-to-github:
stage: mirror
image: alpine:3.20
needs: []
before_script:
- apk add --no-cache git openssh-client ca-certificates
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
# Install the deploy key. File-type CI variable exposes the path; copy
# to ~/.ssh/id_ed25519 with restrictive perms so ssh accepts it.
- cp "$GITHUB_MIRROR_SSH_KEY" ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
# Pin github.com's current host keys so we never trust a man-in-the-
# middle. Sourced from https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints
# (rotated 2023-03-24 after the previous RSA key leak).
- |
cat > ~/.ssh/known_hosts <<'EOF'
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
EOF
- chmod 644 ~/.ssh/known_hosts
script:
- git config --global user.email "ci-mirror@gitlab.com"
- git config --global user.name "GitLab CI Mirror"
- >
git clone --depth=50 --branch main
"https://oauth2:${CI_JOB_TOKEN}@gitlab.com/${CI_PROJECT_PATH}.git"
repo
- cd repo
- >
git push
"git@github.com:BigBodyCobain/Shadowbroker.git"
"${CI_COMMIT_SHA}:refs/heads/main"
rules:
- if: $CI_COMMIT_BRANCH == "main" && $GITHUB_MIRROR_SSH_KEY