Issue CI/CD server security #6

Open
opened 2026-06-15 16:40:51 -06:00 by Xeplosion · 0 comments
Owner

Type of chore

Security hardening

What needs doing and why?

The current server setup uses a single cicd user with a shared home directory
containing all repo clones. This means any workflow has filesystem access to every
other service's files. The planned model uses a dedicated Linux user per service,
each owning only its own files, with SSH access locked to a single forced command.

The following work is outstanding on both the VPS (node-i440fx) and hub (hub-x9scl-f).

VPS — node-i440fx

  • Create svc-nginx user (/usr/sbin/nologin, home /home/svc-nginx/)
  • Create svc-bungeecord user (/usr/sbin/nologin, home /home/svc-bungeecord/)
  • Create forgejo-runner user (/usr/sbin/nologin, no SSH, home /home/forgejo-runner/)
  • Create dev user (/usr/sbin/nologin, key-only SSH, restricted sudo)
  • Clone node-nginx into /home/svc-nginx/node-nginx/ owned by svc-nginx
  • Clone node-bungeecord into /home/svc-bungeecord/node-bungeecord/ owned by svc-bungeecord
  • Write root-owned deploy.sh for each service user (not in any repo)
  • Add authorized_keys entry per service user with command= restriction pointing at deploy.sh
  • Add sudoers.d/svc-nginxNOPASSWD: /bin/systemctl reload nginx
  • Add sudoers.d/svc-bungeecordNOPASSWD: /bin/systemctl restart bungeecord
  • Remove old cicd user and home directory once all services are migrated
  • Migrate Forgejo runner service to run as forgejo-runner user, update WireGuard URL to hub
    Hub — hub-x9scl-f
  • Create svc-nginx user
  • Create svc-forgejo user
  • Create svc-mc-survival user (ensure ownership of /opt/minecraft/survival/)
  • Create svc-mc-modded user (ensure ownership of /opt/minecraft/modded/)
  • Create svc-zulip user (ensure ownership of /opt/zulip/)
  • Create svc-uptime-kuma user
  • Create forgejo-runner user (no SSH, no sudo)
  • Create dev user (/usr/sbin/nologin, key-only SSH, restricted sudo)
  • Clone each service repo into the corresponding service user's home directory
  • Write root-owned deploy.sh for each service user (not in any repo)
  • Add authorized_keys entry per service user with command= restriction
  • Add sudoers.d/ entry for each service user — one command each, NOPASSWD
  • Remove old cicd user once all services are migrated
  • Install and register Forgejo runner on hub, label hub
    Both servers
  • Verify no service user home directory is readable by other service users (chmod 700)
  • Verify all deploy.sh files are owned by root and not writable by the service user
  • Verify command= restrictions are correct by testing with the deploy key from a local machine
  • Confirm runner labels (hub, vps) are registered correctly in Forgejo

Risk or impact

Requires migrating the runner on the VPS to point at hub-hosted Forgejo (part of the
Forgejo migration, tracked separately if not yet complete). Service users need correct
ownership of existing files before the old cicd user is removed — migrate one service
at a time and test CI/CD before removing the fallback.

No user-facing behaviour changes. This is a backend permissions restructuring.

Additional context

Full user and permissions reference: docs branch → INFRASTRUCTURE-VPS.md, INFRASTRUCTURE-HUB.md

Deploy script pattern (root-owned, minimal surface):

#!/bin/bash
set -e
git -C /home/svc-<service>/<repo-name> pull
sudo systemctl restart <service>

authorized_keys command= restriction pattern:

command="/home/svc-<service>/deploy.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA... forgejo-<repo-name>

### Checklist

- [x] I have described why this is worth doing now
- [x] I have noted any risk or impact on other work

### Type of chore Security hardening ### What needs doing and why? The current server setup uses a single `cicd` user with a shared home directory containing all repo clones. This means any workflow has filesystem access to every other service's files. The planned model uses a dedicated Linux user per service, each owning only its own files, with SSH access locked to a single forced command. The following work is outstanding on both the VPS (`node-i440fx`) and hub (`hub-x9scl-f`). **VPS — `node-i440fx`** - [x] Create `svc-nginx` user (`/usr/sbin/nologin`, home `/home/svc-nginx/`) - [x] Create `svc-bungeecord` user (`/usr/sbin/nologin`, home `/home/svc-bungeecord/`) - [x] Create `forgejo-runner` user (`/usr/sbin/nologin`, no SSH, home `/home/forgejo-runner/`) - [x] Create `dev` user (`/usr/sbin/nologin`, key-only SSH, restricted sudo) - [x] Clone `node-nginx` into `/home/svc-nginx/node-nginx/` owned by `svc-nginx` - [x] Clone `node-bungeecord` into `/home/svc-bungeecord/node-bungeecord/` owned by `svc-bungeecord` - [ ] Write root-owned `deploy.sh` for each service user (not in any repo) - [ ] Add `authorized_keys` entry per service user with `command=` restriction pointing at `deploy.sh` - [x] Add `sudoers.d/svc-nginx` → `NOPASSWD: /bin/systemctl reload nginx` - [x] Add `sudoers.d/svc-bungeecord` → `NOPASSWD: /bin/systemctl restart bungeecord` - [ ] Remove old `cicd` user and home directory once all services are migrated - [ ] Migrate Forgejo runner service to run as `forgejo-runner` user, update WireGuard URL to hub **Hub — `hub-x9scl-f`** - [x] Create `svc-nginx` user - [x] Create `svc-forgejo` user - [x] Create `svc-mc-survival` user (ensure ownership of `/opt/minecraft/survival/`) - [x] Create `svc-mc-modded` user (ensure ownership of `/opt/minecraft/modded/`) - [x] Create `svc-zulip` user (ensure ownership of `/opt/zulip/`) - [x] Create `svc-uptime-kuma` user - [ ] Create `forgejo-runner` user (no SSH, no sudo) - [x] Create `dev` user (`/usr/sbin/nologin`, key-only SSH, restricted sudo) - [ ] Clone each service repo into the corresponding service user's home directory - [ ] Write root-owned `deploy.sh` for each service user (not in any repo) - [ ] Add `authorized_keys` entry per service user with `command=` restriction - [x] Add `sudoers.d/` entry for each service user — one command each, NOPASSWD - [ ] Remove old `cicd` user once all services are migrated - [x] Install and register Forgejo runner on hub, label `hub` **Both servers** - [ ] Verify no service user home directory is readable by other service users (`chmod 700`) - [ ] Verify all `deploy.sh` files are owned by root and not writable by the service user - [ ] Verify `command=` restrictions are correct by testing with the deploy key from a local machine - [ ] Confirm runner labels (`hub`, `vps`) are registered correctly in Forgejo ### Risk or impact Requires migrating the runner on the VPS to point at hub-hosted Forgejo (part of the Forgejo migration, tracked separately if not yet complete). Service users need correct ownership of existing files before the old `cicd` user is removed — migrate one service at a time and test CI/CD before removing the fallback. No user-facing behaviour changes. This is a backend permissions restructuring. ### Additional context Full user and permissions reference: `docs` branch → `INFRASTRUCTURE-VPS.md`, `INFRASTRUCTURE-HUB.md` Deploy script pattern (root-owned, minimal surface): ```bash #!/bin/bash set -e git -C /home/svc-<service>/<repo-name> pull sudo systemctl restart <service> ``` authorized_keys command= restriction pattern: ``` command="/home/svc-<service>/deploy.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA... forgejo-<repo-name> ### Checklist - [x] I have described why this is worth doing now - [x] I have noted any risk or impact on other work
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
XeplosionGames/.forgejo#6
No description provided.