Files
unkin-agent 64d9b89dcd
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Scaffold ghp secrets engine modelled on vault-plugin-secrets-gitea
Mints ephemeral, scoped ghp access tokens via ghp's admin token API
(POST /api/tokens), bound to a Vault lease and revoked on lease
expiry (DELETE /api/tokens/{id}).

- config: base_url + write-only admin_token (ghpsvc_ service token),
  TLS settings; verifies the token is a ghp admin on write. No
  rotate-root: the service token is static and operator-managed.
- roles: token_type (agent/proxy), installation_id, app_record_id,
  repositories, scopes (permission:level), session_prefix, ttl/max_ttl.
- creds: mint a lease-bound token; ghp-side duration bounded by the
  lease ceiling as defence in depth.
- secret ghp_token: idempotent revoke + lease renew.
- Unit tests (config/role/creds/client/scopes/revocation), mock-ghp
  e2e on Vault + OpenBao, Woodpecker pre-commit/build/test/release,
  Makefile patch/minor/major, nfpm RPM packaging.
2026-08-15 19:13:44 +10:00

109 lines
3.0 KiB
Go

package ghp
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func testClient(t *testing.T, url, token string) *ghpClient {
t.Helper()
c, err := newClient(&ghpConfig{BaseURL: url, AdminToken: token})
if err != nil {
t.Fatalf("newClient: %v", err)
}
return c
}
func TestClientCreateAndRevokeToken(t *testing.T) {
fake := newFakeGHP("ghpsvc_seed")
srv := fake.server(t)
defer srv.Close()
c := testClient(t, srv.URL, "ghpsvc_seed")
ctx := context.Background()
out, err := c.CreateToken(ctx, createTokenRequest{
Type: tokenTypeAgent, InstallationID: 42, Scopes: "contents:read", SessionID: "vault-x",
})
if err != nil {
t.Fatalf("CreateToken: %v", err)
}
if out.Token == "" || out.ID == "" {
t.Fatalf("CreateToken returned empty value/id: %#v", out)
}
if !fake.has(out.ID) {
t.Fatal("token not stored in fake")
}
if err := c.RevokeToken(ctx, out.ID); err != nil {
t.Fatalf("RevokeToken: %v", err)
}
if fake.has(out.ID) {
t.Fatal("token still present after revoke")
}
// Revoking a non-existent token (404) is treated as success.
if err := c.RevokeToken(ctx, "tok-999999"); err != nil {
t.Fatalf("RevokeToken of missing token should succeed, got: %v", err)
}
}
func TestClientUnauthorized(t *testing.T) {
fake := newFakeGHP("ghpsvc_correct")
srv := fake.server(t)
defer srv.Close()
c := testClient(t, srv.URL, "ghpsvc_wrong")
_, err := c.CreateToken(context.Background(), createTokenRequest{Type: tokenTypeAgent, InstallationID: 1})
if err == nil {
t.Fatal("expected unauthorized error with wrong token")
}
}
func TestClientVerifyAdmin(t *testing.T) {
fake := newFakeGHP("ghpsvc_seed")
srv := fake.server(t)
defer srv.Close()
if err := testClient(t, srv.URL, "ghpsvc_seed").VerifyAdmin(context.Background()); err != nil {
t.Fatalf("VerifyAdmin with valid admin token: %v", err)
}
// Invalid token → 401 → clear message.
err := testClient(t, srv.URL, "nope").VerifyAdmin(context.Background())
if err == nil || !strings.Contains(err.Error(), "not a configured service token") {
t.Errorf("expected 401 message, got: %v", err)
}
}
func TestClientVerifyAdminNonAdmin(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/users" {
w.WriteHeader(http.StatusForbidden)
return
}
w.WriteHeader(http.StatusNotFound)
}))
defer srv.Close()
err := testClient(t, srv.URL, "ghpsvc_seed").VerifyAdmin(context.Background())
if err == nil || !strings.Contains(err.Error(), "not a ghp admin") {
t.Errorf("expected non-admin error, got: %v", err)
}
}
func TestClientRequiresConfig(t *testing.T) {
if _, err := newClient(&ghpConfig{BaseURL: "https://ghp.example.com"}); err == nil {
t.Fatal("expected error when admin_token missing")
}
if _, err := newClient(&ghpConfig{AdminToken: "t"}); err == nil {
t.Fatal("expected error when base_url missing")
}
if _, err := newClient(&ghpConfig{BaseURL: "x", AdminToken: "t", CACert: "not-a-pem"}); err == nil {
t.Fatal("expected error for invalid ca_cert")
}
}