Initial vault-plugin-secrets-gitea engine
Add a Vault/OpenBao secrets engine that mints ephemeral, scoped Gitea access tokens on demand. The engine holds a single seeded Gitea site-admin Basic-Auth credential and, per role, mints a fresh per-user token via the admin API, bound to a Vault lease and deleted from Gitea on revocation. Gitea requires Basic Auth for token management (token auth is rejected), and reqSelfOrAdmin lets a site admin manage any user's tokens, which is the mechanism this relies on. Gitea tokens never expire server-side, so the Vault lease is the sole expiry mechanism. - add backend wiring, config (+ rotate-root), roles, creds paths - add the gitea client (Basic Auth create/delete token, admin password change) - add scope validation against Gitea's access-token scope set - add unit tests (fake Gitea API) and a Vault+OpenBao e2e harness - add Makefile, nfpm RPM packaging, and Woodpecker build/test/release pipelines Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// validScopes is the authoritative set of Gitea access-token scopes, matching
|
||||
// go-gitea/gitea models/auth/access_token_scope.go. "all" grants every
|
||||
// permission; "public-only" restricts a token to public resources. Every
|
||||
// category has read: and write: forms (write implies read).
|
||||
var validScopes = map[string]struct{}{
|
||||
"all": {},
|
||||
"public-only": {},
|
||||
"read:activitypub": {},
|
||||
"write:activitypub": {},
|
||||
"read:admin": {},
|
||||
"write:admin": {},
|
||||
"read:misc": {},
|
||||
"write:misc": {},
|
||||
"read:notification": {},
|
||||
"write:notification": {},
|
||||
"read:organization": {},
|
||||
"write:organization": {},
|
||||
"read:package": {},
|
||||
"write:package": {},
|
||||
"read:issue": {},
|
||||
"write:issue": {},
|
||||
"read:repository": {},
|
||||
"write:repository": {},
|
||||
"read:user": {},
|
||||
"write:user": {},
|
||||
}
|
||||
|
||||
// knownScopes returns the sorted list of valid scopes, for error messages.
|
||||
func knownScopes() []string {
|
||||
out := make([]string, 0, len(validScopes))
|
||||
for s := range validScopes {
|
||||
out = append(out, s)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// normalizeScopes trims, lower-cases and de-duplicates the requested scopes,
|
||||
// rejecting any that Gitea would not recognise. Order is preserved (first
|
||||
// occurrence wins) so the stored role reads back predictably.
|
||||
func normalizeScopes(scopes []string) ([]string, error) {
|
||||
if len(scopes) == 0 {
|
||||
return nil, fmt.Errorf("at least one scope is required; valid scopes: %s", strings.Join(knownScopes(), ", "))
|
||||
}
|
||||
seen := make(map[string]struct{}, len(scopes))
|
||||
out := make([]string, 0, len(scopes))
|
||||
for _, raw := range scopes {
|
||||
s := strings.ToLower(strings.TrimSpace(raw))
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := validScopes[s]; !ok {
|
||||
return nil, fmt.Errorf("invalid scope %q; valid scopes: %s", raw, strings.Join(knownScopes(), ", "))
|
||||
}
|
||||
if _, dup := seen[s]; dup {
|
||||
continue
|
||||
}
|
||||
seen[s] = struct{}{}
|
||||
out = append(out, s)
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil, fmt.Errorf("at least one scope is required; valid scopes: %s", strings.Join(knownScopes(), ", "))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user