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
11 changes: 6 additions & 5 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"permissions": {
"allow": [
"Bash(go test:*)",
"Bash(make lint:*)",
"Bash(make test-e2e:*)",
"Bash(go build:*)",
"Bash(make:*)",
"Bash(kubectl apply:*)",
"Bash(kubectl wait:*)",
"Bash(chmod +x /workspaces/gitops-reverser/hack/demo/setup-holland-stuff-deployment.sh)"
"Bash(chmod +x /workspaces/gitops-reverser/hack/demo/setup-holland-stuff-deployment.sh)",
"Bash(make vet:*)",
"Bash(go:*)",
"Bash(helm template:*",
"Bash(find:*"
]
}
}
10 changes: 5 additions & 5 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ RUN apt-get update \

# Tool versions - centralized for easy updates
# This devcontainer currently supports amd64 tool downloads only.
ENV KUBECTL_VERSION=v1.35.0 \
ENV KUBECTL_VERSION=v1.35.3 \
KUSTOMIZE_VERSION=5.8.1 \
KUBEBUILDER_VERSION=4.11.1 \
GOLANGCI_LINT_VERSION=v2.10.1 \
HELM_VERSION=v4.0.0 \
KUBEBUILDER_VERSION=4.13.1 \
GOLANGCI_LINT_VERSION=v2.11.4 \
HELM_VERSION=v4.1.4 \
K3D_VERSION=v5.8.3 \
FLUX_VERSION=2.8.0
FLUX_VERSION=2.8.5

# Fail early on unsupported architectures instead of producing a partial image.
RUN test "$(dpkg --print-architecture)" = "amd64" \
Expand Down
183 changes: 157 additions & 26 deletions .devcontainer/README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,181 @@
# Development Container

Quick-start development environment with all tools pre-installed.
Development environment for `gitops-reverser` with Go, Kubernetes, Docker CLI, and local SSH commit
signing support.

## Quick Start

### VS Code
1. Install [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
2. Open project in VS Code: `code .`
3. Press `F1` → `Dev Containers: Reopen in Container`
4. Wait for initial build (~5-10 min first time)

1. Install the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
2. Open the project in VS Code: `code .`
3. Press `F1` and run `Dev Containers: Reopen in Container`
4. Wait for the initial build to finish

### Before Reopening In Container

This repo expects SSH agent forwarding for commit signing.

On your host machine:

```bash
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
ssh-add -L
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
```

If `ssh-add -L` shows no keys, commit signing inside the devcontainer will fail.

### Verify

```bash
go version # 1.26.1
kind version # v0.30.0
kubectl version # v1.32.3
golangci-lint version # v2.4.0
go version
kubectl version --client
golangci-lint version
docker version
git config --get gpg.format
git config --get user.signingkey
ssh-add -L
```

### Run Tests
Expected Git signing values:

```bash
make test # Unit tests
make lint # Linting
make test-e2e # E2E tests (creates Kind cluster)
git config --get gpg.format # ssh
git config --get commit.gpgsign # true
git config --get user.signingkey # /home/vscode/.ssh/devcontainer_signing_key.pub
```

## Architecture
## How SSH Signing Works Here

Multi-stage Dockerfile:
- **`ci` stage** - CI base (Go tools, no Docker) - Used in GitHub Actions
- **`dev` stage** - Full dev (extends ci stage, adds Docker+Kind) - Local only
The devcontainer uses SSH commit signing, not GPG keyring-based signing.

Local dev builds directly from dev stage, no separate initialization needed.
- [`post-create.sh`](./post-create.sh) bootstraps Git identity and calls the signing sync helper
- [`sync-signing-key.sh`](./sync-signing-key.sh) reads the forwarded SSH agent and refreshes:
- `${HOME}/.ssh/devcontainer_signing_key.pub`
- `${HOME}/.config/git/allowed_signers`
- [`devcontainer.json`](./devcontainer.json) runs the helper on:
- `postCreateCommand`
- `postStartCommand`

## Files
This keeps the configured public key file aligned with the currently forwarded agent key.

## Best Practices

### SSH Signing

1. Treat forwarded SSH agent keys as ephemeral
Key order and loaded keys can change between sessions.

- `Dockerfile` - Multi-stage container (ci and dev stages)
- `devcontainer.json` - VS Code configuration
- `README.md` - This file
2. Refresh signing config on create and on start
`postCreate` is not enough when the host agent changes later.

3. Keep signing setup in one small script
This repo uses [`sync-signing-key.sh`](./sync-signing-key.sh) for that reason.

4. Prefer deterministic key selection
The helper first looks for an SSH key whose comment matches `git user.email`.
If none matches, it falls back to the first key from `ssh-add -L`.

5. Keep signing and verification data in sync
`user.signingkey` and `gpg.ssh.allowedSignersFile` are regenerated together.

6. Store only public key material in the container
The private key stays on the host and is accessed through the forwarded SSH agent.

7. Fail early with actionable messages
Missing agent, missing keys, or missing Git identity should stop setup immediately.

### General Devcontainer Maintenance

1. Keep lifecycle hooks small and explicit
Bootstrap in `post-create.sh`, refresh in `sync-signing-key.sh`.

2. Prefer reusable helpers over duplicated shell snippets
That makes drift easier to spot and fixes easier to apply.

3. Make runtime state easy to inspect
Useful checks:

```bash
echo "$SSH_AUTH_SOCK"
ssh-add -L
cat ~/.ssh/devcontainer_signing_key.pub
cat ~/.config/git/allowed_signers
git config --show-origin --get user.signingkey
```

## Troubleshooting

**Container won't build** → Ensure Docker is running
**Slow rebuild** → Normal, only rebuilds when tools/deps change
### Commit Signing Fails

Check:

```bash
ssh-add -L
cat ~/.ssh/devcontainer_signing_key.pub
git config --get user.signingkey
```

Then refresh the signing setup manually:

```bash
bash .devcontainer/sync-signing-key.sh
```

Common causes:

- no host `ssh-agent` running
- no key loaded into the host agent
- the forwarded agent key changed after the container was created
- `user.name` or `user.email` is missing

Typical signing error when the configured public key is stale:

```text
error: No private key found for public key "/home/vscode/.ssh/devcontainer_signing_key.pub"?
```

### Push Fails But Commit Signing Works

Commit signing and Git push authentication are separate concerns.

This repo currently uses an HTTPS remote, so push failures are often caused by:

- missing upstream branch
- non-fast-forward push rejection
- GitHub HTTPS credential issues

Check:

```bash
git remote -v
git branch -vv
git push --dry-run -u origin HEAD
```

### Container Won't Build

Ensure Docker is running on the host.

### Slow Rebuild

Usually normal. Rebuild time mostly depends on tool installation and cache reuse.

## Files

- [`Dockerfile`](./Dockerfile) - Multi-stage container image
- [`devcontainer.json`](./devcontainer.json) - VS Code devcontainer configuration
- [`post-create.sh`](./post-create.sh) - Initial bootstrap
- [`sync-signing-key.sh`](./sync-signing-key.sh) - SSH signing refresh helper
- [`README.md`](./README.md) - This document

## References

Kind/DOOD failure findings + DinD migration plan:
Kind/DOOD failure findings + DinD migration plan:
[`KIND_DOOD_FINDINGS_AND_DIND_TRANSITION_PLAN.md`](./KIND_DOOD_FINDINGS_AND_DIND_TRANSITION_PLAN.md)

See [`docs/COMPLETE_SOLUTION.md`](../docs/COMPLETE_SOLUTION.md) for details.
Additional project details:
[`docs/COMPLETE_SOLUTION.md`](../docs/COMPLETE_SOLUTION.md)
1 change: 1 addition & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
}
},
"postCreateCommand": "bash .devcontainer/post-create.sh '${containerWorkspaceFolder}'",
"postStartCommand": "bash .devcontainer/sync-signing-key.sh",
"remoteUser": "vscode",
"mounts": [
"source=gomodcache,target=/go/pkg/mod,type=volume",
Expand Down
35 changes: 2 additions & 33 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,39 +39,8 @@ if ! git config --global --get user.email >/dev/null 2>&1; then
git config --global user.email "${git_email}"
fi

# Require SSH agent forwarding for signing.
if [ -z "${SSH_AUTH_SOCK:-}" ]; then
fail "SSH agent not available in the devcontainer. Start ssh-agent on your machine, load your key with ssh-add, then reopen the devcontainer."
fi

agent_keys_file="$(mktemp)"
trap 'rm -f "${agent_keys_file}"' EXIT

if ! ssh-add -L >"${agent_keys_file}" 2>/dev/null; then
fail "Could not read SSH keys from agent. Make sure your key is loaded on your machine with ssh-add, then reopen the devcontainer."
fi

if ! grep -qE '^ssh-' "${agent_keys_file}"; then
fail "SSH agent is running but has no keys loaded. Run ssh-add ~/.ssh/id_ed25519 on your machine, then reopen the devcontainer."
fi

first_pubkey="$(head -n 1 "${agent_keys_file}")"

# Enforce SSH commit signing.
git config --global gpg.format ssh
git config --global commit.gpgsign true

mkdir -p /home/vscode/.config/git /home/vscode/.ssh
printf '%s\n' "${first_pubkey}" > /home/vscode/.ssh/devcontainer_signing_key.pub
chmod 600 /home/vscode/.ssh/devcontainer_signing_key.pub
git config --global user.signingkey /home/vscode/.ssh/devcontainer_signing_key.pub

# Useful for local verification output.
printf '%s <%s> %s\n' "${git_name}" "${git_email}" "${first_pubkey}" > /home/vscode/.config/git/allowed_signers
chmod 600 /home/vscode/.config/git/allowed_signers
git config --global gpg.ssh.allowedSignersFile /home/vscode/.config/git/allowed_signers

log "Git identity and SSH signing configured"
log "Refreshing Git SSH signing configuration"
bash "${workspace_dir}/.devcontainer/sync-signing-key.sh"

log "Ensuring Go cache directories exist"
sudo mkdir -p \
Expand Down
59 changes: 59 additions & 0 deletions .devcontainer/sync-signing-key.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env bash

set -euo pipefail

log() {
echo "[sync-signing-key] $*"
}

fail() {
echo "[sync-signing-key] ERROR: $*" >&2
exit 1
}

git_name="$(git config --get user.name || true)"
git_email="$(git config --get user.email || true)"

if [ -z "${git_name}" ] || [ -z "${git_email}" ]; then
fail "Missing Git identity. Configure user.name and user.email before syncing SSH signing."
fi

if [ -z "${SSH_AUTH_SOCK:-}" ]; then
fail "SSH agent not available in the devcontainer. Start ssh-agent on your machine, load your key with ssh-add, then reopen the devcontainer."
fi

agent_keys_file="$(mktemp)"
trap 'rm -f "${agent_keys_file}"' EXIT

if ! ssh-add -L >"${agent_keys_file}" 2>/dev/null; then
fail "Could not read SSH keys from agent. Make sure your key is loaded on your machine with ssh-add, then reopen the devcontainer."
fi

if ! grep -qE '^ssh-' "${agent_keys_file}"; then
fail "SSH agent is running but has no keys loaded. Run ssh-add ~/.ssh/id_ed25519 on your machine, then reopen the devcontainer."
fi

selected_pubkey="$(awk -v email="${git_email}" '$0 ~ email { print; exit }' "${agent_keys_file}")"
if [ -z "${selected_pubkey}" ]; then
selected_pubkey="$(head -n 1 "${agent_keys_file}")"
log "No SSH key comment matched ${git_email}; using the first agent key"
else
log "Using SSH key whose comment matches ${git_email}"
fi

signing_key_path="${HOME}/.ssh/devcontainer_signing_key.pub"
allowed_signers_path="${HOME}/.config/git/allowed_signers"

git config --global gpg.format ssh
git config --global commit.gpgsign true

mkdir -p "${HOME}/.config/git" "${HOME}/.ssh"
printf '%s\n' "${selected_pubkey}" > "${signing_key_path}"
chmod 600 "${signing_key_path}"
git config --global user.signingkey "${signing_key_path}"

printf '%s <%s> %s\n' "${git_name}" "${git_email}" "${selected_pubkey}" > "${allowed_signers_path}"
chmod 600 "${allowed_signers_path}"
git config --global gpg.ssh.allowedSignersFile "${allowed_signers_path}"

log "Git SSH signing configuration refreshed"
6 changes: 3 additions & 3 deletions .github/actions/docker-build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@ runs:
using: 'composite'
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4

- name: Log in to Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ inputs.registry }}
username: ${{ inputs.username }}
password: ${{ inputs.password }}

- name: Build and push Docker image
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
platforms: ${{ inputs.platforms }}
Expand Down
Loading