package s3tables import ( "encoding/json" "strings" ) // Permission represents a specific action permission type Permission string // IAM Policy structures for evaluation type PolicyDocument struct { Version string `json:"Version"` Statement []Statement `json:"Statement"` } type Statement struct { Effect string `json:"Effect"` // "Allow" or "Deny" Principal interface{} `json:"Principal"` // Can be string, []string, or map Action interface{} `json:"Action"` // Can be string or []string Resource interface{} `json:"Resource"` // Can be string or []string } // CheckPermission checks if a principal has permission to perform an operation func CheckPermission(operation, principal, owner, resourcePolicy string) bool { // Deny access if identities are empty if principal == "" || owner == "" { return false } // Owner always has permission if principal == owner { return true } // If no policy is provided, deny access (default deny) if resourcePolicy == "" { return false } // Parse and evaluate policy var policy PolicyDocument if err := json.Unmarshal([]byte(resourcePolicy), &policy); err != nil { return false } // Evaluate policy statements // Default is deny, so we need an explicit allow hasAllow := false for _, stmt := range policy.Statement { // Check if principal matches if !matchesPrincipal(stmt.Principal, principal) { continue } // Check if action matches if !matchesAction(stmt.Action, operation) { continue } // Statement matches - check effect if stmt.Effect == "Allow" { hasAllow = true } else if stmt.Effect == "Deny" { // Explicit deny always wins return false } } return hasAllow } // matchesPrincipal checks if the principal matches the statement's principal func matchesPrincipal(principalSpec interface{}, principal string) bool { if principalSpec == nil { return false } switch p := principalSpec.(type) { case string: // Direct string match or wildcard return p == "*" || p == principal case []interface{}: // Array of principals for _, item := range p { if str, ok := item.(string); ok { if str == "*" || str == principal { return true } } } case map[string]interface{}: // AWS-style principal with service prefix, e.g., {"AWS": "arn:aws:iam::..."} // For S3 Tables, we primarily care about the AWS key if aws, ok := p["AWS"]; ok { return matchesPrincipal(aws, principal) } } return false } // matchesAction checks if the action matches the statement's action func matchesAction(actionSpec interface{}, action string) bool { if actionSpec == nil { return false } switch a := actionSpec.(type) { case string: // Direct match or wildcard return matchesActionPattern(a, action) case []interface{}: // Array of actions for _, item := range a { if str, ok := item.(string); ok { if matchesActionPattern(str, action) { return true } } } } return false } // matchesActionPattern checks if an action matches a pattern (supports wildcards) func matchesActionPattern(pattern, action string) bool { if pattern == "*" { return true } // Exact match if pattern == action { return true } // Wildcard match (e.g., "s3tables:*" matches "s3tables:GetTable") if strings.HasSuffix(pattern, "*") { prefix := strings.TrimSuffix(pattern, "*") return strings.HasPrefix(action, prefix) } return false } // Helper functions for specific permissions // CanCreateTableBucket checks if principal can create table buckets func CanCreateTableBucket(principal, owner, resourcePolicy string) bool { return CheckPermission("CreateTableBucket", principal, owner, resourcePolicy) } // CanGetTableBucket checks if principal can get table bucket details func CanGetTableBucket(principal, owner, resourcePolicy string) bool { return CheckPermission("GetTableBucket", principal, owner, resourcePolicy) } // CanListTableBuckets checks if principal can list table buckets func CanListTableBuckets(principal, owner, resourcePolicy string) bool { return CheckPermission("ListTableBuckets", principal, owner, resourcePolicy) } // CanDeleteTableBucket checks if principal can delete table buckets func CanDeleteTableBucket(principal, owner, resourcePolicy string) bool { return CheckPermission("DeleteTableBucket", principal, owner, resourcePolicy) } // CanPutTableBucketPolicy checks if principal can put table bucket policies func CanPutTableBucketPolicy(principal, owner, resourcePolicy string) bool { return CheckPermission("PutTableBucketPolicy", principal, owner, resourcePolicy) } // CanGetTableBucketPolicy checks if principal can get table bucket policies func CanGetTableBucketPolicy(principal, owner, resourcePolicy string) bool { return CheckPermission("GetTableBucketPolicy", principal, owner, resourcePolicy) } // CanDeleteTableBucketPolicy checks if principal can delete table bucket policies func CanDeleteTableBucketPolicy(principal, owner, resourcePolicy string) bool { return CheckPermission("DeleteTableBucketPolicy", principal, owner, resourcePolicy) } // CanCreateNamespace checks if principal can create namespaces func CanCreateNamespace(principal, owner, resourcePolicy string) bool { return CheckPermission("CreateNamespace", principal, owner, resourcePolicy) } // CanGetNamespace checks if principal can get namespace details func CanGetNamespace(principal, owner, resourcePolicy string) bool { return CheckPermission("GetNamespace", principal, owner, resourcePolicy) } // CanListNamespaces checks if principal can list namespaces func CanListNamespaces(principal, owner, resourcePolicy string) bool { return CheckPermission("ListNamespaces", principal, owner, resourcePolicy) } // CanDeleteNamespace checks if principal can delete namespaces func CanDeleteNamespace(principal, owner, resourcePolicy string) bool { return CheckPermission("DeleteNamespace", principal, owner, resourcePolicy) } // CanCreateTable checks if principal can create tables func CanCreateTable(principal, owner, resourcePolicy string) bool { return CheckPermission("CreateTable", principal, owner, resourcePolicy) } // CanGetTable checks if principal can get table details func CanGetTable(principal, owner, resourcePolicy string) bool { return CheckPermission("GetTable", principal, owner, resourcePolicy) } // CanListTables checks if principal can list tables func CanListTables(principal, owner, resourcePolicy string) bool { return CheckPermission("ListTables", principal, owner, resourcePolicy) } // CanDeleteTable checks if principal can delete tables func CanDeleteTable(principal, owner, resourcePolicy string) bool { return CheckPermission("DeleteTable", principal, owner, resourcePolicy) } // CanPutTablePolicy checks if principal can put table policies func CanPutTablePolicy(principal, owner, resourcePolicy string) bool { return CheckPermission("PutTablePolicy", principal, owner, resourcePolicy) } // CanGetTablePolicy checks if principal can get table policies func CanGetTablePolicy(principal, owner, resourcePolicy string) bool { return CheckPermission("GetTablePolicy", principal, owner, resourcePolicy) } // CanDeleteTablePolicy checks if principal can delete table policies func CanDeleteTablePolicy(principal, owner, resourcePolicy string) bool { return CheckPermission("DeleteTablePolicy", principal, owner, resourcePolicy) } // CanManageTags checks if principal can manage tags func CanManageTags(principal, owner, resourcePolicy string) bool { return CheckPermission("ManageTags", principal, owner, resourcePolicy) } // AuthError represents an authorization error type AuthError struct { Operation string Principal string Message string } func (e *AuthError) Error() string { return "unauthorized: " + e.Principal + " is not permitted to perform " + e.Operation + ": " + e.Message } // NewAuthError creates a new authorization error func NewAuthError(operation, principal, message string) *AuthError { return &AuthError{ Operation: operation, Principal: principal, Message: message, } }