feat: add allow_list to resource-scoped API tokens (#19964)

# Add API key allow_list for resource-scoped tokens

This PR adds support for API key allow lists, enabling tokens to be scoped to specific resources. The implementation:

1. Adds a new `allow_list` field to the `CreateTokenRequest` struct, allowing clients to specify resource-specific scopes when creating API tokens
2. Implements `APIAllowListTarget` type to represent resource targets in the format `<type>:<id>` with support for wildcards
3. Adds validation and normalization logic for allow lists to handle wildcards and deduplication
4. Integrates with RBAC by creating an `APIKeyEffectiveScope` that merges API key scopes with allow list restrictions
5. Updates API documentation and TypeScript types to reflect the new functionality

This feature enables creating tokens that are limited to specific resources (like workspaces or templates) by ID, making it possible to create more granular API tokens with limited access.
This commit is contained in:
Thomas Kosiewski
2025-10-09 14:53:08 +02:00
committed by GitHub
parent f31e6e09ba
commit ed90ecf00e
25 changed files with 930 additions and 94 deletions
+5 -32
View File
@@ -163,9 +163,7 @@ func (m StringMapOfInt) Value() (driver.Value, error) {
type CustomRolePermissions []CustomRolePermission
// APIKeyScopes implements sql.Scanner and driver.Valuer so it can be read from
// and written to the Postgres api_key_scope[] enum array column.
func (s *APIKeyScopes) Scan(src interface{}) error {
func (s *APIKeyScopes) Scan(src any) error {
var arr []string
if err := pq.Array(&arr).Scan(src); err != nil {
return err
@@ -314,36 +312,11 @@ func ParseIP(ipStr string) pqtype.Inet {
}
}
// AllowListTarget represents a single scope allow-list entry.
// It encodes a resource tuple (type, id) and provides helpers for
// consistent string and JSON representations across the codebase.
type AllowListTarget struct {
Type string `json:"type"`
ID string `json:"id"`
}
// String returns the canonical database representation "type:id".
func (t AllowListTarget) String() string {
return t.Type + ":" + t.ID
}
// ParseAllowListTarget parses the canonical string form "type:id".
func ParseAllowListTarget(s string) (AllowListTarget, error) {
targetType, id, ok := rbac.ParseResourceAction(s)
if !ok {
return AllowListTarget{}, xerrors.Errorf("invalid allow list target: %q", s)
}
return AllowListTarget{Type: targetType, ID: id}, nil
}
// AllowListWildcard returns the wildcard allow-list entry {"*","*"}.
func AllowListWildcard() AllowListTarget { return AllowListTarget{Type: "*", ID: "*"} }
// AllowList is a typed wrapper around a list of AllowListTarget entries.
// It implements sql.Scanner and driver.Valuer so it can be stored in and
// loaded from a Postgres text[] column that stores each entry in the
// canonical form "type:id".
type AllowList []AllowListTarget
type AllowList []rbac.AllowListElement
// Scan implements sql.Scanner. It supports inputs that pq.Array can decode
// into []string, and then converts each element to an AllowListTarget.
@@ -352,13 +325,13 @@ func (a *AllowList) Scan(src any) error {
if err := pq.Array(&raw).Scan(src); err != nil {
return err
}
out := make([]AllowListTarget, len(raw))
out := make([]rbac.AllowListElement, len(raw))
for i, s := range raw {
t, err := ParseAllowListTarget(s)
e, err := rbac.ParseAllowListEntry(s)
if err != nil {
return err
}
out[i] = t
out[i] = e
}
*a = out
return nil