96 lines
2.5 KiB
Go
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
|
|
}
|