Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,6 @@ dmypy.json
.k8s/
test-results/
k8s/overlays/local/config.local.env
# The admin SSH key `make deploy` generates
k8s/base/ssh/

# Throwaway venv built by "make smoke"
.smoke/
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ repos:
- id: detect-aws-credentials
args: [--allow-missing-credentials]
- id: detect-private-key
# The local Phorge's dev key, public on purpose, see k8s/base/ssh/README.md
exclude: ^k8s/base/ssh/phabfive-insecure$
- id: end-of-file-fixer
exclude: |
(?x)
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@

## Other Notes

* The local Phorge's admin SSH key is a known key, committed as
`k8s/base/ssh/phabfive-insecure` the way the API token is, instead of one `make deploy`
generated into whichever worktree deployed. Every worktree can now use git over SSH,
and run `tests/k8s/test_ssh.py`, against the one shared instance, and `make reset`
keeps the key. ssh refuses a private key others can read, so `make deploy` and
`make ssh-forward` copy it to `.k8s/phabfive-insecure` with mode 0600. A running
instance picks the new key up on its next start, next to the old one. `make creds`
prints the whole `GIT_SSH_COMMAND`, with `StrictHostKeyChecking=accept-new`, since every
`make reset` brings a new host key

* The local Phorge seeds a hidden column, "Won't Do", on the Development board, and
creates the column of each milestone on its parent's board, which Phorge otherwise
creates only when the board is first opened in a browser. #588
Expand Down
24 changes: 14 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ VERSION ?= $(shell cat $(BUILD_DIR)/version 2>/dev/null || echo stable)
# PHP series to build on (8.0, 8.3, 8.4, 8.5), empty for the newest VERSION runs on
PHP ?=

# The admin's SSH key, generated on first deploy and registered by the sshkeys
# seed module. SSH_PORT is PHORGE_SSH_PORT, where Phorge says the clone URIs
# are, so it is where `make ssh-forward` listens: config.local.env overrides
# The admin's SSH key, registered by the sshkeys seed module. A known key
# committed in k8s/base/ssh/, like the API token: public on purpose, so every
# worktree reaches the shared instance with it. ssh refuses a private key
# others can read and git checks it out 0644, so ssh is given a 0600 copy.
# SSH_PORT is PHORGE_SSH_PORT, where Phorge says the clone URIs are, so it is
# where `make ssh-forward` listens: config.local.env overrides
# k8s/base/config.env, as it does in the deployment.
SSH_KEY := k8s/base/ssh/admin
SSH_KEY_SRC := k8s/base/ssh/phabfive-insecure
SSH_KEY := $(BUILD_DIR)/phabfive-insecure
SSH_PORT := $(or $(lastword $(shell sed -n 's/^PHORGE_SSH_PORT=//p' k8s/base/config.env k8s/overlays/local/config.local.env 2>/dev/null)),2222)
GIT_SSH_COMMAND = ssh -i $(CURDIR)/$(SSH_KEY) -o UserKnownHostsFile=$(CURDIR)/$(BUILD_DIR)/known_hosts -o StrictHostKeyChecking=accept-new

Expand Down Expand Up @@ -175,9 +179,9 @@ phorge-image: check-tools ## build the phorge image of VERSION (stable, master,
$(K3D) image import -c $(CLUSTER) $(PHORGE_IMAGE):$$tag; \
echo $$tag > $(BUILD_DIR)/image-tag

$(SSH_KEY):
$(SSH_KEY): $(SSH_KEY_SRC)
@mkdir -p $(@D)
ssh-keygen -q -t ed25519 -N '' -C phabfive-dev -f $@
install -m 600 $< $@

deploy: check-tools $(SSH_KEY) ## apply the manifests of OVERLAY (local or ci) with the imported image
@test -f $(BUILD_DIR)/image-tag || { echo "Error: no image imported yet, run make phorge-image"; exit 1; }
Expand All @@ -201,8 +205,8 @@ down: check-tools ## stop phorge and mariadb, keep data

reset: check-tools ## DELETE the phorge namespace with all its data and clear its completion cache
mise exec -- kubectl --context $(KUBE_CONTEXT) delete namespace $(NAMESPACE) --ignore-not-found --wait
@# The host key went with the data, and the next instance gets a new admin key
rm -f $(SSH_KEY) $(SSH_KEY).pub $(BUILD_DIR)/known_hosts
@# The host key went with the data, the admin key is the same on every instance
rm -f $(BUILD_DIR)/known_hosts
@$(MAKE) --no-print-directory clear-cache

logs: check-tools ## follow phorge logs
Expand All @@ -218,7 +222,7 @@ shell: check-tools ## open shell in the phorge pod
creds: check-tools ## print credentials of the running phorge
@$(KUBECTL) exec deploy/phorge -- /usr/local/bin/lib/banner.sh

ssh-forward: check-tools ## forward localhost:PHORGE_SSH_PORT (2222) to phorge's sshd, for git over SSH (runs until interrupted)
ssh-forward: check-tools $(SSH_KEY) ## forward localhost:PHORGE_SSH_PORT (2222) to phorge's sshd, for git over SSH (runs until interrupted)
@echo "git over SSH, while this runs:"
@echo " GIT_SSH_COMMAND='$(GIT_SSH_COMMAND)' \\"
@echo " git clone ssh://git@phorge.localhost:$(SSH_PORT)/source/gunnar-firmware.git"
Expand All @@ -227,7 +231,7 @@ ssh-forward: check-tools ## forward localhost:PHORGE_SSH_PORT (2222) to phorge's

##@ Test

validate: $(SSH_KEY) ## validate the rendered manifests of all overlays
validate: ## validate the rendered manifests of all overlays
@touch k8s/overlays/local/config.local.env
@for overlay in k8s/overlays/*/; do \
mise exec -- kubectl kustomize $$overlay | mise exec -- kubeconform -strict -summary || exit 1; \
Expand Down
17 changes: 11 additions & 6 deletions docs/phorge-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,12 @@ client can read it: `passphrase show K1 -s` fails the way K2 does.
Phorge serves git over SSH as well as HTTP, from a second container in the
same pod: `web` runs Apache and the daemons, `sshd` runs OpenSSH with Phorge's
`ssh-auth` as its `AuthorizedKeysCommand`. The admin account has an SSH key,
which `make deploy` generates into `k8s/base/ssh/admin` (gitignored) and the
`sshkeys` seed module registers.
which the `sshkeys` seed module registers: `k8s/base/ssh/phabfive-insecure`, a
known key committed to the repository like the API token, so every worktree and
every instance use the same one. **It is public, never authorize it anywhere
else.** ssh refuses a private key others can read, and git checks files out
readable, so `make deploy` and `make ssh-forward` copy it to
`.k8s/phabfive-insecure` with mode 0600, and that copy is the one to use.

No port on the host reaches sshd. The cluster's published ports are shared by
every Dynamist dev app (`k8s/cluster/k3d.yaml`), so instead `make ssh-forward`
Expand All @@ -221,7 +225,7 @@ and prints the command to use:

```bash
make ssh-forward # in one terminal
GIT_SSH_COMMAND="ssh -i $PWD/k8s/base/ssh/admin -o UserKnownHostsFile=$PWD/.k8s/known_hosts -o StrictHostKeyChecking=accept-new" \
GIT_SSH_COMMAND="ssh -i $PWD/.k8s/phabfive-insecure -o UserKnownHostsFile=$PWD/.k8s/known_hosts -o StrictHostKeyChecking=accept-new" \
git clone ssh://git@phorge.localhost:2222/source/gunnar-firmware.git
```

Expand All @@ -238,8 +242,9 @@ and forces each to run `ssh-exec` as its owner, and `ssh-exec` runs git through
A push runs Phorge's commit hook, so the new commits are known at once.

The sshd host key is kept on the volume, so `known_hosts` stays valid across
`make down`/`make up`. `make reset` deletes it, the admin key and
`.k8s/known_hosts` together.
`make down`/`make up`. `make reset` deletes it and `.k8s/known_hosts`
together, and `StrictHostKeyChecking=accept-new` records the next instance's
host key on first contact. The admin key stays the same.

Seeded repositories have dangerous change protection on, so a pushed branch
cannot be deleted again over SSH. Push to a repository of your own for
Expand Down Expand Up @@ -401,7 +406,7 @@ phorge/seed/
| `tasks` | users, projects, spaces | tasks, through `maniphest.edit` |
| `credentials` | auth | Passphrase credentials, through Phorge's editors |
| `repositories` | auth, users | hosted git repositories and their history, through `diffusion.repository.edit` |
| `sshkeys` | auth | the admin's SSH key from `k8s/base/ssh/admin.pub`, through Phorge's editor |
| `sshkeys` | auth | the admin's SSH key from `k8s/base/ssh/phabfive-insecure.pub`, through Phorge's editor |

Every module is idempotent - it creates only what is missing - so restarting the
pod duplicates nothing. `PHORGE_SEED` is `all` by default; to seed only part of
Expand Down
4 changes: 2 additions & 2 deletions k8s/base/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ configMapGenerator:
secretGenerator:
- name: phorge-secret
envs: [secret.env]
# The admin's SSH public key, generated by `make deploy` (gitignored)
# The admin's SSH public key, a known dev key committed with its private half
- name: phorge-ssh-key
files: [admin.pub=ssh/admin.pub]
files: [admin.pub=ssh/phabfive-insecure.pub]
2 changes: 1 addition & 1 deletion k8s/base/phorge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ spec:
- name: ssh-key
secret:
secretName: phorge-ssh-key
# Only the public half, the private one stays in k8s/base/ssh/
# Only the public half of k8s/base/ssh/phabfive-insecure
optional: true
- name: data
persistentVolumeClaim:
Expand Down
12 changes: 12 additions & 0 deletions k8s/base/ssh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# The local Phorge's admin SSH key

`phabfive-insecure` is a **publicly known** private key, committed on purpose like the
API token `api-supersecr3tapikeyfordevelop1`. The `sshkeys` seed module registers its
public half for the admin of the disposable local Phorge, so git over SSH works from
every worktree against the one shared instance.

Never authorize it anywhere else, and never load it into an agent.

ssh refuses a private key that others can read, and git checks this one out 0644, so
use the 0600 copy `make deploy` and `make ssh-forward` write to `.k8s/phabfive-insecure`.
See docs/phorge-setup.md, "Git over SSH".
7 changes: 7 additions & 0 deletions k8s/base/ssh/phabfive-insecure
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAskuIZ+ML6PhhanBgj/1IgAXHDRQwXlvZfO6QcX6EZZwAAAJgRQXfZEUF3
2QAAAAtzc2gtZWQyNTUxOQAAACAskuIZ+ML6PhhanBgj/1IgAXHDRQwXlvZfO6QcX6EZZw
AAAEAUsSAfh+3SufWx+o56JSRbn6qiW4aVOtGi3aXuTuuseiyS4hn4wvo+GFqcGCP/UiAB
ccNFDBeW9l87pBxfoRlnAAAAEXBoYWJmaXZlLWluc2VjdXJlAQIDBA==
-----END OPENSSH PRIVATE KEY-----
1 change: 1 addition & 0 deletions k8s/base/ssh/phabfive-insecure.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICyS4hn4wvo+GFqcGCP/UiABccNFDBeW9l87pBxfoRln phabfive-insecure
5 changes: 3 additions & 2 deletions phorge/lib/banner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,9 @@ print_banner() {
echo " $PHORGE_URL"
echo ""
if [ -n "${PHORGE_SSH_USER:-}" ]; then
echo "🔗 git over SSH, after 'make ssh-forward' (key: k8s/base/ssh/admin):"
echo " ssh://${PHORGE_SSH_USER}@${PHORGE_SSH_HOST:-phorge.localhost}:${PHORGE_SSH_PORT:-22}/source/<short name>.git"
echo "🔗 git over SSH, after 'make ssh-forward', from the phabfive checkout:"
echo " GIT_SSH_COMMAND='ssh -i .k8s/phabfive-insecure -o UserKnownHostsFile=.k8s/known_hosts -o StrictHostKeyChecking=accept-new' \\"
echo " git clone ssh://${PHORGE_SSH_USER}@${PHORGE_SSH_HOST:-phorge.localhost}:${PHORGE_SSH_PORT:-22}/source/<short name>.git"
echo ""
fi
echo "💡 TIP: The API token works immediately without logging in!"
Expand Down
4 changes: 2 additions & 2 deletions phorge/seed/modules/sshkeys.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* The admin's SSH public key, so git over SSH works against the seeded
* repositories.
*
* `make deploy` generates the keypair in k8s/base/ssh/ (gitignored), and the
* A known dev keypair, committed in k8s/base/ssh/phabfive-insecure, and the
* public half reaches the pod as the Secret phorge-ssh-key. There is no
* Conduit method for SSH keys, so like credentials this goes through Phorge's
* editor, with the transactions PhabricatorAuthSSHKeyEditController applies.
Expand All @@ -15,7 +15,7 @@
final class PhabfiveSSHKeysSeedModule extends PhabfiveSeedModule {

const KEY_PATH = '/etc/phorge-seed/ssh/admin.pub';
const KEY_NAME = 'phabfive-dev';
const KEY_NAME = 'phabfive-insecure';

public function getKey() {
return 'sshkeys';
Expand Down
13 changes: 9 additions & 4 deletions tests/k8s/test_ssh.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
"""git over SSH, through the sshd container and `kubectl port-forward`.

Reached the way `make ssh-forward` reaches it, with the admin key `make deploy`
generated and the sshkeys seed module registered.
Reached the way `make ssh-forward` reaches it, with the known dev key
k8s/base/ssh/phabfive-insecure that the sshkeys seed module registered.
"""

# python std lib
import os
import shutil
import socket
import subprocess
import time
Expand All @@ -17,7 +18,7 @@

from tests.k8s.conftest import KUBE_CONTEXT, NAMESPACE, ROOT

ADMIN_KEY = ROOT / "k8s/base/ssh/admin"
ADMIN_KEY = ROOT / "k8s/base/ssh/phabfive-insecure"
SSH_HOST = "phorge.localhost"
SSH_PORT = 2222

Expand Down Expand Up @@ -61,8 +62,12 @@ def ssh_port():
def git(ssh_port, tmp_path):
"""Run git with ssh pointed at the forwarded port, as `key` (the admin's by default)."""
known_hosts = tmp_path / "known_hosts"
# ssh refuses a private key others can read, and git checks it out 0644
admin_key = tmp_path / "phabfive-insecure"
shutil.copyfile(ADMIN_KEY, admin_key)
admin_key.chmod(0o600)

def run(*args, key=ADMIN_KEY, check=True, cwd=None):
def run(*args, key=admin_key, check=True, cwd=None):
command = (
f"ssh -i {key} -o IdentitiesOnly=yes -o BatchMode=yes"
f" -o UserKnownHostsFile={known_hosts} -o StrictHostKeyChecking=accept-new"
Expand Down
Loading