64d9b89dcd
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.
109 lines
3.0 KiB
Go
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")
|
|
}
|
|
}
|