A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
at vulnerability-scans 95 lines 2.6 kB view raw
1package auth 2 3import ( 4 "fmt" 5 "strings" 6) 7 8// AccessEntry represents access permissions for a resource 9type AccessEntry struct { 10 Type string `json:"type"` // "repository" 11 Name string `json:"name,omitempty"` // e.g., "alice/myapp" 12 Actions []string `json:"actions,omitempty"` // e.g., ["pull", "push"] 13} 14 15// ParseScope parses Docker registry scope strings into AccessEntry structures 16// Scope format: "repository:alice/myapp:pull,push" 17// Multiple scopes can be provided 18func ParseScope(scopes []string) ([]AccessEntry, error) { 19 var access []AccessEntry 20 21 for _, scope := range scopes { 22 if scope == "" { 23 continue 24 } 25 26 parts := strings.Split(scope, ":") 27 if len(parts) < 2 { 28 return nil, fmt.Errorf("invalid scope format: %s", scope) 29 } 30 31 resourceType := parts[0] 32 33 var name string 34 var actions []string 35 36 if len(parts) == 2 { 37 // Format: "repository:alice/myapp" (no actions specified) 38 name = parts[1] 39 } else if len(parts) == 3 { 40 // Format: "repository:alice/myapp:pull,push" 41 name = parts[1] 42 if parts[2] != "" { 43 actions = strings.Split(parts[2], ",") 44 } 45 } else { 46 return nil, fmt.Errorf("invalid scope format: %s", scope) 47 } 48 49 access = append(access, AccessEntry{ 50 Type: resourceType, 51 Name: name, 52 Actions: actions, 53 }) 54 } 55 56 return access, nil 57} 58 59// ValidateAccess checks if the requested access is allowed for the user 60// For ATCR, users can only push to repositories under their own handle/DID 61func ValidateAccess(userDID, userHandle string, access []AccessEntry) error { 62 for _, entry := range access { 63 if entry.Type != "repository" { 64 continue 65 } 66 67 // Allow wildcard scope (e.g., "repository:*:pull,push") 68 // This is used by Docker credential helpers to request broad permissions 69 // Actual authorization happens later when accessing specific repositories 70 if entry.Name == "*" { 71 continue 72 } 73 74 // Extract the owner from repository name (e.g., "alice/myapp" -> "alice") 75 parts := strings.SplitN(entry.Name, "/", 2) 76 if len(parts) < 1 { 77 return fmt.Errorf("invalid repository name: %s", entry.Name) 78 } 79 80 repoOwner := parts[0] 81 82 // Check if user is trying to access their own repository 83 // They can use either their handle or DID 84 if repoOwner != userHandle && repoOwner != userDID { 85 // For push/delete operations, strict ownership check 86 for _, action := range entry.Actions { 87 if action == "push" || action == "delete" { 88 return fmt.Errorf("user %s cannot %s to repository %s", userHandle, action, entry.Name) 89 } 90 } 91 } 92 } 93 94 return nil 95}