Files
at-container-registry/pkg/auth/scope.go
Evan Jarrett 0706132186 code clean up
2025-10-11 23:29:56 -05:00

96 lines
2.5 KiB
Go

package auth
import (
"fmt"
"strings"
)
// AccessEntry represents access permissions for a resource
type AccessEntry struct {
Type string `json:"type"` // "repository"
Name string `json:"name,omitempty"` // e.g., "alice/myapp"
Actions []string `json:"actions,omitempty"` // e.g., ["pull", "push"]
}
// ParseScope parses Docker registry scope strings into AccessEntry structures
// Scope format: "repository:alice/myapp:pull,push"
// Multiple scopes can be provided
func ParseScope(scopes []string) ([]AccessEntry, error) {
var access []AccessEntry
for _, scope := range scopes {
if scope == "" {
continue
}
parts := strings.Split(scope, ":")
if len(parts) < 2 {
return nil, fmt.Errorf("invalid scope format: %s", scope)
}
resourceType := parts[0]
var name string
var actions []string
if len(parts) == 2 {
// Format: "repository:alice/myapp" (no actions specified)
name = parts[1]
} else if len(parts) == 3 {
// Format: "repository:alice/myapp:pull,push"
name = parts[1]
if parts[2] != "" {
actions = strings.Split(parts[2], ",")
}
} else {
return nil, fmt.Errorf("invalid scope format: %s", scope)
}
access = append(access, AccessEntry{
Type: resourceType,
Name: name,
Actions: actions,
})
}
return access, nil
}
// ValidateAccess checks if the requested access is allowed for the user
// For ATCR, users can only push to repositories under their own handle/DID
func ValidateAccess(userDID, userHandle string, access []AccessEntry) error {
for _, entry := range access {
if entry.Type != "repository" {
continue
}
// Allow wildcard scope (e.g., "repository:*:pull,push")
// This is used by Docker credential helpers to request broad permissions
// Actual authorization happens later when accessing specific repositories
if entry.Name == "*" {
continue
}
// Extract the owner from repository name (e.g., "alice/myapp" -> "alice")
parts := strings.SplitN(entry.Name, "/", 2)
if len(parts) < 1 {
return fmt.Errorf("invalid repository name: %s", entry.Name)
}
repoOwner := parts[0]
// Check if user is trying to access their own repository
// They can use either their handle or DID
if repoOwner != userHandle && repoOwner != userDID {
// For push/delete operations, strict ownership check
for _, action := range entry.Actions {
if action == "push" || action == "delete" {
return fmt.Errorf("user %s cannot %s to repository %s", userHandle, action, entry.Name)
}
}
}
}
return nil
}