fde942d831
Replace the nfpm/build-rpm.sh release path with a single goreleaser config producing the RPM, the raw sshsignhost-<os>-<arch> binaries the puppetserver-compiler initContainer fetches, tarballs and checksums; the tag pipeline runs goreleaser then PUTs the RPM to artifactapi. Add an integration suite behind the 'integration' build tag that provisions an ssh CA mount, host role and approle on a dev-mode Vault and signs a host key through the built binary with Puppet's exact argv, asserting an empty stderr and a decoded host certificate.
64 lines
2.8 KiB
Markdown
64 lines
2.8 KiB
Markdown
# AGENTS.md
|
|
|
|
## Project Overview
|
|
|
|
`sshsignhost` signs SSH host certificates with Vault. It is a drop-in
|
|
replacement for the Python script Puppet used to render at
|
|
`/opt/sshsignhost/sshsignhost`, and is invoked by
|
|
`profiles::ssh::sign` (puppet-prod) through `generate()` at
|
|
`/usr/local/bin/sshsignhost`.
|
|
|
|
## Structure
|
|
|
|
```
|
|
main.go # cobra command, flag surface, stdout rendering
|
|
config.go # YAML config loading, defaults, validation
|
|
vault.go # Vault HTTP client: approle/kubernetes login + sign
|
|
main_test.go # end-to-end tests against an httptest TLS stand-in for Vault
|
|
integration_test.go # -tags=integration: signs against a real dev-mode Vault
|
|
Makefile # build / test / lint / integration-test / snapshot / version bump
|
|
.goreleaser.yaml # binaries + RPM + Gitea release assets, one driver
|
|
scripts/ # integration-test.sh (dev-mode Vault in a container)
|
|
.woodpecker/ # build, test, pre-commit, integration (PR) + release (tag)
|
|
```
|
|
|
|
## Contract with Puppet
|
|
|
|
- Flags use underscores (`--public_key`, `--valid_principals`), matching the
|
|
argv the manifest builds.
|
|
- `--valid_principals` is forwarded to Vault as the comma-separated string it
|
|
was given; it is never split and rejoined.
|
|
- `generate()` uses `failonfail: true, combine: true`: stderr is duped onto
|
|
stdout at spawn. On success nothing may reach stderr, or `parsejson()` fails
|
|
and the catalog compile breaks. `TestSuccessWritesNothingToStderr` runs the
|
|
real binary in a subprocess to hold that line.
|
|
- A 200 response without `data.signed_key` is a failure, never an empty result.
|
|
|
|
## Vault
|
|
|
|
- AppRole: `POST {addr}/v1/auth/{approle_path}/login` with `role_id` only; the
|
|
Vault role sets `bind_secret_id: false`.
|
|
- Kubernetes: `POST {addr}/v1/auth/{k8s_mount}/login` with `role` and the JWT
|
|
read from `jwt_path`. There is no fallback between methods -- a rejected
|
|
login or an unknown `auth_method` fails with the cause named.
|
|
- Sign: `POST {addr}/v1/{mount_point}/sign/{role_name}` with `X-Vault-Token`
|
|
and `cert_type: host`.
|
|
|
|
## Known blockers
|
|
|
|
The Vault ACL policy grants `sshca/sign/host`, but the only role that exists is
|
|
`signhost`, so production signs 403 on `sshca/sign/signhost` even though auth
|
|
succeeds. That is a terraform-vault fix, not a bug here -- do not change the
|
|
requested path.
|
|
|
|
The `signhost` role sets `allowed_domains: main.unkin.net,consul` with
|
|
`allow_bare_domains: false`, so the bare hostname and the IP the manifest sends
|
|
alongside the FQDN are refused. `TestIntegrationRejectsPrincipalsOutsideAllowedDomains`
|
|
pins that behaviour; fixing it is another terraform-vault change.
|
|
|
|
## TLS
|
|
|
|
`tls_skip_verify` defaults to `true`, preserving the Python helper's
|
|
`verify=False` behaviour, and is a config key so it can be flipped without a
|
|
code change once certificates are trusted everywhere.
|