You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
642 lines
20 KiB
642 lines
20 KiB
package policy_engine
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
|
)
|
|
|
|
// Action represents an S3 action - this should match the type in auth_credentials.go
|
|
type Action string
|
|
|
|
// Identity represents a user identity - this should match the type in auth_credentials.go
|
|
type Identity interface {
|
|
canDo(action Action, bucket string, objectKey string) bool
|
|
}
|
|
|
|
// PolicyBackedIAM provides policy-based access control with fallback to legacy IAM
|
|
type PolicyBackedIAM struct {
|
|
policyEngine *PolicyEngine
|
|
legacyIAM LegacyIAM // Interface to delegate to existing IAM system
|
|
}
|
|
|
|
// LegacyIAM interface for delegating to existing IAM implementation
|
|
type LegacyIAM interface {
|
|
authRequest(r *http.Request, action Action) (Identity, s3err.ErrorCode)
|
|
}
|
|
|
|
// NewPolicyBackedIAM creates a new policy-backed IAM system
|
|
func NewPolicyBackedIAM() *PolicyBackedIAM {
|
|
return &PolicyBackedIAM{
|
|
policyEngine: NewPolicyEngine(),
|
|
legacyIAM: nil, // Will be set when integrated with existing IAM
|
|
}
|
|
}
|
|
|
|
// NewPolicyBackedIAMWithLegacy creates a new policy-backed IAM system with legacy IAM set
|
|
func NewPolicyBackedIAMWithLegacy(legacyIAM LegacyIAM) *PolicyBackedIAM {
|
|
return &PolicyBackedIAM{
|
|
policyEngine: NewPolicyEngine(),
|
|
legacyIAM: legacyIAM,
|
|
}
|
|
}
|
|
|
|
// SetLegacyIAM sets the legacy IAM system for fallback
|
|
func (p *PolicyBackedIAM) SetLegacyIAM(legacyIAM LegacyIAM) {
|
|
p.legacyIAM = legacyIAM
|
|
}
|
|
|
|
// SetBucketPolicy sets the policy for a bucket
|
|
func (p *PolicyBackedIAM) SetBucketPolicy(bucketName string, policyJSON string) error {
|
|
return p.policyEngine.SetBucketPolicy(bucketName, policyJSON)
|
|
}
|
|
|
|
// GetBucketPolicy gets the policy for a bucket
|
|
func (p *PolicyBackedIAM) GetBucketPolicy(bucketName string) (*PolicyDocument, error) {
|
|
return p.policyEngine.GetBucketPolicy(bucketName)
|
|
}
|
|
|
|
// DeleteBucketPolicy deletes the policy for a bucket
|
|
func (p *PolicyBackedIAM) DeleteBucketPolicy(bucketName string) error {
|
|
return p.policyEngine.DeleteBucketPolicy(bucketName)
|
|
}
|
|
|
|
// CanDo checks if a principal can perform an action on a resource
|
|
func (p *PolicyBackedIAM) CanDo(action, bucketName, objectName, principal string, r *http.Request) bool {
|
|
// If there's a bucket policy, evaluate it
|
|
if p.policyEngine.HasPolicyForBucket(bucketName) {
|
|
result := p.policyEngine.EvaluatePolicyForRequest(bucketName, objectName, action, principal, r)
|
|
switch result {
|
|
case PolicyResultAllow:
|
|
return true
|
|
case PolicyResultDeny:
|
|
return false
|
|
case PolicyResultIndeterminate:
|
|
// Fall through to legacy system
|
|
}
|
|
}
|
|
|
|
// No bucket policy or indeterminate result, use legacy conversion
|
|
return p.evaluateLegacyAction(action, bucketName, objectName, principal)
|
|
}
|
|
|
|
// evaluateLegacyAction evaluates actions using legacy identity-based rules
|
|
func (p *PolicyBackedIAM) evaluateLegacyAction(action, bucketName, objectName, principal string) bool {
|
|
// If we have a legacy IAM system to delegate to, use it
|
|
if p.legacyIAM != nil {
|
|
// Create a dummy request for legacy evaluation
|
|
// In real implementation, this would use the actual request
|
|
r := &http.Request{
|
|
Header: make(http.Header),
|
|
}
|
|
|
|
// Convert the action string to Action type
|
|
legacyAction := Action(action)
|
|
|
|
// Use legacy IAM to check permission
|
|
identity, errCode := p.legacyIAM.authRequest(r, legacyAction)
|
|
if errCode != s3err.ErrNone {
|
|
return false
|
|
}
|
|
|
|
// If we have an identity, check if it can perform the action
|
|
if identity != nil {
|
|
return identity.canDo(legacyAction, bucketName, objectName)
|
|
}
|
|
}
|
|
|
|
// No legacy IAM available, convert to policy and evaluate
|
|
return p.evaluateUsingPolicyConversion(action, bucketName, objectName, principal)
|
|
}
|
|
|
|
// evaluateUsingPolicyConversion converts legacy action to policy and evaluates
|
|
func (p *PolicyBackedIAM) evaluateUsingPolicyConversion(action, bucketName, objectName, principal string) bool {
|
|
// For now, use a conservative approach for legacy actions
|
|
// In a real implementation, this would integrate with the existing identity system
|
|
glog.V(2).Infof("Legacy action evaluation for %s on %s/%s by %s", action, bucketName, objectName, principal)
|
|
|
|
// Return false to maintain security until proper legacy integration is implemented
|
|
// This ensures no unintended access is granted
|
|
return false
|
|
}
|
|
|
|
// extractBucketAndPrefix extracts bucket name and prefix from a resource pattern.
|
|
// Examples:
|
|
//
|
|
// "bucket" -> bucket="bucket", prefix=""
|
|
// "bucket/*" -> bucket="bucket", prefix=""
|
|
// "bucket/prefix/*" -> bucket="bucket", prefix="prefix"
|
|
// "bucket/a/b/c/*" -> bucket="bucket", prefix="a/b/c"
|
|
func extractBucketAndPrefix(pattern string) (string, string) {
|
|
// Validate input
|
|
pattern = strings.TrimSpace(pattern)
|
|
if pattern == "" || pattern == "/" {
|
|
return "", ""
|
|
}
|
|
|
|
// Remove trailing /* if present
|
|
pattern = strings.TrimSuffix(pattern, "/*")
|
|
|
|
// Remove a single trailing slash to avoid empty path segments
|
|
if strings.HasSuffix(pattern, "/") {
|
|
pattern = pattern[:len(pattern)-1]
|
|
}
|
|
if pattern == "" {
|
|
return "", ""
|
|
}
|
|
|
|
// Split on the first /
|
|
parts := strings.SplitN(pattern, "/", 2)
|
|
bucket := strings.TrimSpace(parts[0])
|
|
if bucket == "" {
|
|
return "", ""
|
|
}
|
|
|
|
if len(parts) == 1 {
|
|
// No slash, entire pattern is bucket
|
|
return bucket, ""
|
|
}
|
|
// Has slash, first part is bucket, rest is prefix
|
|
prefix := strings.Trim(parts[1], "/")
|
|
return bucket, prefix
|
|
}
|
|
|
|
// buildObjectResourceArn generates ARNs for object-level access.
|
|
// It properly handles both bucket-level (all objects) and prefix-level access.
|
|
// Returns empty slice if bucket is invalid to prevent generating malformed ARNs.
|
|
func buildObjectResourceArn(resourcePattern string) []string {
|
|
bucket, prefix := extractBucketAndPrefix(resourcePattern)
|
|
// If bucket is empty, the pattern is invalid; avoid generating malformed ARNs
|
|
if bucket == "" {
|
|
return []string{}
|
|
}
|
|
if prefix != "" {
|
|
// Prefix-based access: restrict to objects under this prefix
|
|
return []string{fmt.Sprintf("arn:aws:s3:::%s/%s/*", bucket, prefix)}
|
|
}
|
|
// Bucket-level access: all objects in bucket
|
|
return []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
|
|
}
|
|
|
|
// ConvertIdentityToPolicy converts a legacy identity action to an AWS policy
|
|
func ConvertIdentityToPolicy(identityActions []string) (*PolicyDocument, error) {
|
|
statements := make([]PolicyStatement, 0)
|
|
|
|
for _, action := range identityActions {
|
|
stmt, err := convertSingleAction(action)
|
|
if err != nil {
|
|
glog.Warningf("Failed to convert action %s: %v", action, err)
|
|
continue
|
|
}
|
|
if stmt != nil {
|
|
statements = append(statements, *stmt)
|
|
}
|
|
}
|
|
|
|
if len(statements) == 0 {
|
|
return nil, fmt.Errorf("no valid statements generated")
|
|
}
|
|
|
|
return &PolicyDocument{
|
|
Version: PolicyVersion2012_10_17,
|
|
Statement: statements,
|
|
}, nil
|
|
}
|
|
|
|
// convertSingleAction converts a single legacy action to a policy statement.
|
|
// action format: "ActionType:ResourcePattern" (e.g., "Write:bucket/prefix/*")
|
|
func convertSingleAction(action string) (*PolicyStatement, error) {
|
|
parts := strings.Split(action, ":")
|
|
if len(parts) != 2 {
|
|
return nil, fmt.Errorf("invalid action format: %s", action)
|
|
}
|
|
|
|
actionType := parts[0]
|
|
resourcePattern := parts[1]
|
|
|
|
var s3Actions []string
|
|
var resources []string
|
|
|
|
switch actionType {
|
|
case "Read":
|
|
// Read includes both object-level (GetObject, GetObjectAcl, GetObjectTagging, GetObjectVersions)
|
|
// and bucket-level operations (ListBucket, GetBucketLocation, GetBucketVersioning, GetBucketCors, etc.)
|
|
s3Actions = []string{
|
|
"s3:GetObject",
|
|
"s3:GetObjectVersion",
|
|
"s3:GetObjectAcl",
|
|
"s3:GetObjectVersionAcl",
|
|
"s3:GetObjectTagging",
|
|
"s3:GetObjectVersionTagging",
|
|
"s3:ListBucket",
|
|
"s3:ListBucketVersions",
|
|
"s3:GetBucketLocation",
|
|
"s3:GetBucketVersioning",
|
|
"s3:GetBucketAcl",
|
|
"s3:GetBucketCors",
|
|
"s3:GetBucketTagging",
|
|
"s3:GetBucketNotification",
|
|
}
|
|
bucket, _ := extractBucketAndPrefix(resourcePattern)
|
|
objectResources := buildObjectResourceArn(resourcePattern)
|
|
// Include both bucket ARN (for ListBucket* and Get*Bucket operations) and object ARNs (for GetObject* operations)
|
|
if bucket != "" {
|
|
resources = append([]string{fmt.Sprintf("arn:aws:s3:::%s", bucket)}, objectResources...)
|
|
} else {
|
|
resources = objectResources
|
|
}
|
|
|
|
case "Write":
|
|
// Write includes object-level writes (PutObject, DeleteObject, PutObjectAcl, DeleteObjectVersion, DeleteObjectTagging, PutObjectTagging)
|
|
// and bucket-level writes (PutBucketVersioning, PutBucketCors, DeleteBucketCors, PutBucketAcl, PutBucketTagging, DeleteBucketTagging, PutBucketNotification)
|
|
// and multipart upload operations (AbortMultipartUpload, ListMultipartUploads, ListParts).
|
|
// ListMultipartUploads and ListParts are included because they are part of the multipart upload workflow
|
|
// and require Write permissions to be meaningful (no point listing uploads if you can't abort/complete them).
|
|
s3Actions = []string{
|
|
"s3:PutObject",
|
|
"s3:PutObjectAcl",
|
|
"s3:PutObjectTagging",
|
|
"s3:DeleteObject",
|
|
"s3:DeleteObjectVersion",
|
|
"s3:DeleteObjectTagging",
|
|
"s3:AbortMultipartUpload",
|
|
"s3:ListMultipartUploads",
|
|
"s3:ListParts",
|
|
"s3:PutBucketAcl",
|
|
"s3:PutBucketCors",
|
|
"s3:PutBucketTagging",
|
|
"s3:PutBucketNotification",
|
|
"s3:PutBucketVersioning",
|
|
"s3:DeleteBucketTagging",
|
|
"s3:DeleteBucketCors",
|
|
}
|
|
bucket, _ := extractBucketAndPrefix(resourcePattern)
|
|
objectResources := buildObjectResourceArn(resourcePattern)
|
|
// Include bucket ARN so bucket-level write operations (e.g., PutBucketVersioning, PutBucketCors)
|
|
// have the correct resource, while still allowing object-level writes.
|
|
if bucket != "" {
|
|
resources = append([]string{fmt.Sprintf("arn:aws:s3:::%s", bucket)}, objectResources...)
|
|
} else {
|
|
resources = objectResources
|
|
}
|
|
|
|
case "Admin":
|
|
s3Actions = []string{"s3:*"}
|
|
bucket, prefix := extractBucketAndPrefix(resourcePattern)
|
|
if bucket == "" {
|
|
// Invalid pattern, return error
|
|
return nil, fmt.Errorf("Admin action requires a valid bucket name")
|
|
}
|
|
if prefix != "" {
|
|
// Subpath admin access: restrict to objects under this prefix
|
|
resources = []string{
|
|
fmt.Sprintf("arn:aws:s3:::%s", bucket),
|
|
fmt.Sprintf("arn:aws:s3:::%s/%s/*", bucket, prefix),
|
|
}
|
|
} else {
|
|
// Bucket-level admin access: full bucket permissions
|
|
resources = []string{
|
|
fmt.Sprintf("arn:aws:s3:::%s", bucket),
|
|
fmt.Sprintf("arn:aws:s3:::%s/*", bucket),
|
|
}
|
|
}
|
|
|
|
case "List":
|
|
// List includes bucket listing operations and also ListAllMyBuckets
|
|
s3Actions = []string{"s3:ListBucket", "s3:ListBucketVersions", "s3:ListAllMyBuckets"}
|
|
// ListBucket actions only require bucket ARN, not object-level ARNs
|
|
bucket, _ := extractBucketAndPrefix(resourcePattern)
|
|
if bucket != "" {
|
|
resources = []string{fmt.Sprintf("arn:aws:s3:::%s", bucket)}
|
|
} else {
|
|
// Invalid pattern, return empty resources to fail validation
|
|
resources = []string{}
|
|
}
|
|
|
|
case "Tagging":
|
|
// Tagging includes both object-level and bucket-level tagging operations
|
|
s3Actions = []string{
|
|
"s3:GetObjectTagging",
|
|
"s3:PutObjectTagging",
|
|
"s3:DeleteObjectTagging",
|
|
"s3:GetBucketTagging",
|
|
"s3:PutBucketTagging",
|
|
"s3:DeleteBucketTagging",
|
|
}
|
|
bucket, _ := extractBucketAndPrefix(resourcePattern)
|
|
objectResources := buildObjectResourceArn(resourcePattern)
|
|
// Include bucket ARN so bucket-level tagging operations have the correct resource
|
|
if bucket != "" {
|
|
resources = append([]string{fmt.Sprintf("arn:aws:s3:::%s", bucket)}, objectResources...)
|
|
} else {
|
|
resources = objectResources
|
|
}
|
|
|
|
case "BypassGovernanceRetention":
|
|
s3Actions = []string{"s3:BypassGovernanceRetention"}
|
|
resources = buildObjectResourceArn(resourcePattern)
|
|
|
|
case "GetObjectRetention":
|
|
s3Actions = []string{"s3:GetObjectRetention"}
|
|
resources = buildObjectResourceArn(resourcePattern)
|
|
|
|
case "PutObjectRetention":
|
|
s3Actions = []string{"s3:PutObjectRetention"}
|
|
resources = buildObjectResourceArn(resourcePattern)
|
|
|
|
case "GetObjectLegalHold":
|
|
s3Actions = []string{"s3:GetObjectLegalHold"}
|
|
resources = buildObjectResourceArn(resourcePattern)
|
|
|
|
case "PutObjectLegalHold":
|
|
s3Actions = []string{"s3:PutObjectLegalHold"}
|
|
resources = buildObjectResourceArn(resourcePattern)
|
|
|
|
case "GetBucketObjectLockConfiguration":
|
|
s3Actions = []string{"s3:GetBucketObjectLockConfiguration"}
|
|
bucket, _ := extractBucketAndPrefix(resourcePattern)
|
|
if bucket != "" {
|
|
resources = []string{fmt.Sprintf("arn:aws:s3:::%s", bucket)}
|
|
} else {
|
|
// Invalid pattern, return empty resources to fail validation
|
|
resources = []string{}
|
|
}
|
|
|
|
case "PutBucketObjectLockConfiguration":
|
|
s3Actions = []string{"s3:PutBucketObjectLockConfiguration"}
|
|
bucket, _ := extractBucketAndPrefix(resourcePattern)
|
|
if bucket != "" {
|
|
resources = []string{fmt.Sprintf("arn:aws:s3:::%s", bucket)}
|
|
} else {
|
|
// Invalid pattern, return empty resources to fail validation
|
|
resources = []string{}
|
|
}
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown action type: %s", actionType)
|
|
}
|
|
|
|
return &PolicyStatement{
|
|
Effect: PolicyEffectAllow,
|
|
Action: NewStringOrStringSlice(s3Actions...),
|
|
Resource: NewStringOrStringSlice(resources...),
|
|
}, nil
|
|
}
|
|
|
|
// GetActionMappings returns the mapping of legacy actions to S3 actions
|
|
func GetActionMappings() map[string][]string {
|
|
return map[string][]string{
|
|
"Read": {
|
|
"s3:GetObject",
|
|
"s3:GetObjectVersion",
|
|
"s3:GetObjectAcl",
|
|
"s3:GetObjectVersionAcl",
|
|
"s3:GetObjectTagging",
|
|
"s3:GetObjectVersionTagging",
|
|
"s3:ListBucket",
|
|
"s3:ListBucketVersions",
|
|
"s3:GetBucketLocation",
|
|
"s3:GetBucketVersioning",
|
|
"s3:GetBucketAcl",
|
|
"s3:GetBucketCors",
|
|
"s3:GetBucketTagging",
|
|
"s3:GetBucketNotification",
|
|
},
|
|
"Write": {
|
|
"s3:PutObject",
|
|
"s3:PutObjectAcl",
|
|
"s3:PutObjectTagging",
|
|
"s3:DeleteObject",
|
|
"s3:DeleteObjectVersion",
|
|
"s3:DeleteObjectTagging",
|
|
"s3:AbortMultipartUpload",
|
|
"s3:ListMultipartUploads",
|
|
"s3:ListParts",
|
|
"s3:PutBucketAcl",
|
|
"s3:PutBucketCors",
|
|
"s3:PutBucketTagging",
|
|
"s3:PutBucketNotification",
|
|
"s3:PutBucketVersioning",
|
|
"s3:DeleteBucketTagging",
|
|
"s3:DeleteBucketCors",
|
|
},
|
|
"Admin": {
|
|
"s3:*",
|
|
},
|
|
"List": {
|
|
"s3:ListBucket",
|
|
"s3:ListBucketVersions",
|
|
"s3:ListAllMyBuckets",
|
|
},
|
|
"Tagging": {
|
|
"s3:GetObjectTagging",
|
|
"s3:PutObjectTagging",
|
|
"s3:DeleteObjectTagging",
|
|
"s3:GetBucketTagging",
|
|
"s3:PutBucketTagging",
|
|
"s3:DeleteBucketTagging",
|
|
},
|
|
"BypassGovernanceRetention": {
|
|
"s3:BypassGovernanceRetention",
|
|
},
|
|
"GetObjectRetention": {
|
|
"s3:GetObjectRetention",
|
|
},
|
|
"PutObjectRetention": {
|
|
"s3:PutObjectRetention",
|
|
},
|
|
"GetObjectLegalHold": {
|
|
"s3:GetObjectLegalHold",
|
|
},
|
|
"PutObjectLegalHold": {
|
|
"s3:PutObjectLegalHold",
|
|
},
|
|
"GetBucketObjectLockConfiguration": {
|
|
"s3:GetBucketObjectLockConfiguration",
|
|
},
|
|
"PutBucketObjectLockConfiguration": {
|
|
"s3:PutBucketObjectLockConfiguration",
|
|
},
|
|
}
|
|
}
|
|
|
|
// ValidateActionMapping validates that a legacy action can be mapped to S3 actions
|
|
func ValidateActionMapping(action string) error {
|
|
mappings := GetActionMappings()
|
|
|
|
parts := strings.Split(action, ":")
|
|
if len(parts) != 2 {
|
|
return fmt.Errorf("invalid action format: %s, expected format: 'ActionType:Resource'", action)
|
|
}
|
|
|
|
actionType := parts[0]
|
|
resource := parts[1]
|
|
|
|
if _, exists := mappings[actionType]; !exists {
|
|
return fmt.Errorf("unknown action type: %s", actionType)
|
|
}
|
|
|
|
if resource == "" {
|
|
return fmt.Errorf("resource cannot be empty")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ConvertLegacyActions converts an array of legacy actions to S3 actions
|
|
func ConvertLegacyActions(legacyActions []string) ([]string, error) {
|
|
mappings := GetActionMappings()
|
|
s3Actions := make([]string, 0)
|
|
|
|
for _, legacyAction := range legacyActions {
|
|
if err := ValidateActionMapping(legacyAction); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
parts := strings.Split(legacyAction, ":")
|
|
actionType := parts[0]
|
|
|
|
if actionType == "Admin" {
|
|
// Admin gives all permissions, so we can just return s3:*
|
|
return []string{"s3:*"}, nil
|
|
}
|
|
|
|
if mapped, exists := mappings[actionType]; exists {
|
|
s3Actions = append(s3Actions, mapped...)
|
|
}
|
|
}
|
|
|
|
// Remove duplicates
|
|
uniqueActions := make([]string, 0)
|
|
seen := make(map[string]bool)
|
|
for _, action := range s3Actions {
|
|
if !seen[action] {
|
|
uniqueActions = append(uniqueActions, action)
|
|
seen[action] = true
|
|
}
|
|
}
|
|
|
|
return uniqueActions, nil
|
|
}
|
|
|
|
// GetResourcesFromLegacyAction extracts resources from a legacy action.
|
|
// It delegates to convertSingleAction to ensure consistent resource ARN generation
|
|
// across the codebase and avoid duplicating action-type-specific logic.
|
|
func GetResourcesFromLegacyAction(legacyAction string) ([]string, error) {
|
|
stmt, err := convertSingleAction(legacyAction)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return stmt.Resource.Strings(), nil
|
|
}
|
|
|
|
// CreatePolicyFromLegacyIdentity creates a policy document from legacy identity actions
|
|
func CreatePolicyFromLegacyIdentity(identityName string, actions []string) (*PolicyDocument, error) {
|
|
statements := make([]PolicyStatement, 0)
|
|
|
|
// Group actions by resource pattern
|
|
resourceActions := make(map[string][]string)
|
|
|
|
for _, action := range actions {
|
|
// Validate action format before processing
|
|
if err := ValidateActionMapping(action); err != nil {
|
|
glog.Warningf("Skipping invalid action %q for identity %q: %v", action, identityName, err)
|
|
continue
|
|
}
|
|
|
|
parts := strings.Split(action, ":")
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
|
|
resourcePattern := parts[1]
|
|
actionType := parts[0]
|
|
|
|
if _, exists := resourceActions[resourcePattern]; !exists {
|
|
resourceActions[resourcePattern] = make([]string, 0)
|
|
}
|
|
resourceActions[resourcePattern] = append(resourceActions[resourcePattern], actionType)
|
|
}
|
|
|
|
// Create statements for each resource pattern
|
|
for resourcePattern, actionTypes := range resourceActions {
|
|
s3Actions := make([]string, 0)
|
|
resourceSet := make(map[string]struct{})
|
|
|
|
// Collect S3 actions and aggregate resource ARNs from all action types.
|
|
// Different action types have different resource ARN requirements:
|
|
// - List: bucket-level ARNs only
|
|
// - Read/Write/Tagging: object-level ARNs
|
|
// - Admin: full bucket access
|
|
// We must merge all required ARNs for the combined policy statement.
|
|
for _, actionType := range actionTypes {
|
|
if actionType == "Admin" {
|
|
s3Actions = []string{"s3:*"}
|
|
// Admin action determines the resources, so we can break after processing it.
|
|
res, err := GetResourcesFromLegacyAction(fmt.Sprintf("Admin:%s", resourcePattern))
|
|
if err != nil {
|
|
glog.Warningf("Failed to get resources for Admin action on %s: %v", resourcePattern, err)
|
|
resourceSet = nil // Invalidate to skip this statement
|
|
break
|
|
}
|
|
for _, r := range res {
|
|
resourceSet[r] = struct{}{}
|
|
}
|
|
break
|
|
}
|
|
|
|
if mapped, exists := GetActionMappings()[actionType]; exists {
|
|
s3Actions = append(s3Actions, mapped...)
|
|
res, err := GetResourcesFromLegacyAction(fmt.Sprintf("%s:%s", actionType, resourcePattern))
|
|
if err != nil {
|
|
glog.Warningf("Failed to get resources for %s action on %s: %v", actionType, resourcePattern, err)
|
|
resourceSet = nil // Invalidate to skip this statement
|
|
break
|
|
}
|
|
for _, r := range res {
|
|
resourceSet[r] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
if resourceSet == nil || len(s3Actions) == 0 {
|
|
continue
|
|
}
|
|
|
|
resources := make([]string, 0, len(resourceSet))
|
|
for r := range resourceSet {
|
|
resources = append(resources, r)
|
|
}
|
|
|
|
statement := PolicyStatement{
|
|
Sid: fmt.Sprintf("%s-%s", identityName, strings.ReplaceAll(resourcePattern, "/", "-")),
|
|
Effect: PolicyEffectAllow,
|
|
Action: NewStringOrStringSlice(s3Actions...),
|
|
Resource: NewStringOrStringSlice(resources...),
|
|
}
|
|
|
|
statements = append(statements, statement)
|
|
}
|
|
|
|
if len(statements) == 0 {
|
|
return nil, fmt.Errorf("no valid statements generated for identity %s", identityName)
|
|
}
|
|
|
|
return &PolicyDocument{
|
|
Version: PolicyVersion2012_10_17,
|
|
Statement: statements,
|
|
}, nil
|
|
}
|
|
|
|
// HasPolicyForBucket checks if a bucket has a policy
|
|
func (p *PolicyBackedIAM) HasPolicyForBucket(bucketName string) bool {
|
|
return p.policyEngine.HasPolicyForBucket(bucketName)
|
|
}
|
|
|
|
// GetPolicyEngine returns the underlying policy engine
|
|
func (p *PolicyBackedIAM) GetPolicyEngine() *PolicyEngine {
|
|
return p.policyEngine
|
|
}
|