package s3api import ( "time" "github.com/seaweedfs/seaweedfs/weed/iam/policy" ) // S3PolicyTemplates provides pre-built IAM policy templates for common S3 use cases type S3PolicyTemplates struct{} // NewS3PolicyTemplates creates a new policy templates provider func NewS3PolicyTemplates() *S3PolicyTemplates { return &S3PolicyTemplates{} } // GetS3ReadOnlyPolicy returns a policy that allows read-only access to all S3 resources func (t *S3PolicyTemplates) GetS3ReadOnlyPolicy() *policy.PolicyDocument { return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "S3ReadOnlyAccess", Effect: "Allow", Action: []string{ "s3:GetObject", "s3:GetObjectVersion", "s3:ListBucket", "s3:ListBucketVersions", "s3:GetBucketLocation", "s3:GetBucketVersioning", "s3:ListAllMyBuckets", }, Resource: []string{ "arn:seaweed:s3:::*", "arn:seaweed:s3:::*/*", }, }, }, } } // GetS3WriteOnlyPolicy returns a policy that allows write-only access to all S3 resources func (t *S3PolicyTemplates) GetS3WriteOnlyPolicy() *policy.PolicyDocument { return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "S3WriteOnlyAccess", Effect: "Allow", Action: []string{ "s3:PutObject", "s3:PutObjectAcl", "s3:CreateMultipartUpload", "s3:UploadPart", "s3:CompleteMultipartUpload", "s3:AbortMultipartUpload", "s3:ListMultipartUploads", "s3:ListParts", }, Resource: []string{ "arn:seaweed:s3:::*", "arn:seaweed:s3:::*/*", }, }, }, } } // GetS3AdminPolicy returns a policy that allows full admin access to all S3 resources func (t *S3PolicyTemplates) GetS3AdminPolicy() *policy.PolicyDocument { return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "S3FullAccess", Effect: "Allow", Action: []string{ "s3:*", }, Resource: []string{ "arn:seaweed:s3:::*", "arn:seaweed:s3:::*/*", }, }, }, } } // GetBucketSpecificReadPolicy returns a policy for read-only access to a specific bucket func (t *S3PolicyTemplates) GetBucketSpecificReadPolicy(bucketName string) *policy.PolicyDocument { return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "BucketSpecificReadAccess", Effect: "Allow", Action: []string{ "s3:GetObject", "s3:GetObjectVersion", "s3:ListBucket", "s3:ListBucketVersions", "s3:GetBucketLocation", }, Resource: []string{ "arn:seaweed:s3:::" + bucketName, "arn:seaweed:s3:::" + bucketName + "/*", }, }, }, } } // GetBucketSpecificWritePolicy returns a policy for write-only access to a specific bucket func (t *S3PolicyTemplates) GetBucketSpecificWritePolicy(bucketName string) *policy.PolicyDocument { return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "BucketSpecificWriteAccess", Effect: "Allow", Action: []string{ "s3:PutObject", "s3:PutObjectAcl", "s3:CreateMultipartUpload", "s3:UploadPart", "s3:CompleteMultipartUpload", "s3:AbortMultipartUpload", "s3:ListMultipartUploads", "s3:ListParts", }, Resource: []string{ "arn:seaweed:s3:::" + bucketName, "arn:seaweed:s3:::" + bucketName + "/*", }, }, }, } } // GetPathBasedAccessPolicy returns a policy that restricts access to a specific path within a bucket func (t *S3PolicyTemplates) GetPathBasedAccessPolicy(bucketName, pathPrefix string) *policy.PolicyDocument { return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "ListBucketPermission", Effect: "Allow", Action: []string{ "s3:ListBucket", }, Resource: []string{ "arn:seaweed:s3:::" + bucketName, }, Condition: map[string]map[string]interface{}{ "StringLike": map[string]interface{}{ "s3:prefix": []string{pathPrefix + "/*"}, }, }, }, { Sid: "PathBasedObjectAccess", Effect: "Allow", Action: []string{ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:CreateMultipartUpload", "s3:UploadPart", "s3:CompleteMultipartUpload", "s3:AbortMultipartUpload", }, Resource: []string{ "arn:seaweed:s3:::" + bucketName + "/" + pathPrefix + "/*", }, }, }, } } // GetIPRestrictedPolicy returns a policy that restricts access based on source IP func (t *S3PolicyTemplates) GetIPRestrictedPolicy(allowedCIDRs []string) *policy.PolicyDocument { return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "IPRestrictedS3Access", Effect: "Allow", Action: []string{ "s3:*", }, Resource: []string{ "arn:seaweed:s3:::*", "arn:seaweed:s3:::*/*", }, Condition: map[string]map[string]interface{}{ "IpAddress": map[string]interface{}{ "aws:SourceIp": allowedCIDRs, }, }, }, }, } } // GetTimeBasedAccessPolicy returns a policy that allows access only during specific hours func (t *S3PolicyTemplates) GetTimeBasedAccessPolicy(startHour, endHour int) *policy.PolicyDocument { return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "TimeBasedS3Access", Effect: "Allow", Action: []string{ "s3:GetObject", "s3:PutObject", "s3:ListBucket", }, Resource: []string{ "arn:seaweed:s3:::*", "arn:seaweed:s3:::*/*", }, Condition: map[string]map[string]interface{}{ "DateGreaterThan": map[string]interface{}{ "aws:CurrentTime": time.Now().Format("2006-01-02") + "T" + formatHour(startHour) + ":00:00Z", }, "DateLessThan": map[string]interface{}{ "aws:CurrentTime": time.Now().Format("2006-01-02") + "T" + formatHour(endHour) + ":00:00Z", }, }, }, }, } } // GetMultipartUploadPolicy returns a policy specifically for multipart upload operations func (t *S3PolicyTemplates) GetMultipartUploadPolicy(bucketName string) *policy.PolicyDocument { return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "MultipartUploadOperations", Effect: "Allow", Action: []string{ "s3:CreateMultipartUpload", "s3:UploadPart", "s3:CompleteMultipartUpload", "s3:AbortMultipartUpload", "s3:ListMultipartUploads", "s3:ListParts", }, Resource: []string{ "arn:seaweed:s3:::" + bucketName + "/*", }, }, { Sid: "ListBucketForMultipart", Effect: "Allow", Action: []string{ "s3:ListBucket", }, Resource: []string{ "arn:seaweed:s3:::" + bucketName, }, }, }, } } // GetPresignedURLPolicy returns a policy for generating and using presigned URLs func (t *S3PolicyTemplates) GetPresignedURLPolicy(bucketName string) *policy.PolicyDocument { return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "PresignedURLAccess", Effect: "Allow", Action: []string{ "s3:GetObject", "s3:PutObject", }, Resource: []string{ "arn:seaweed:s3:::" + bucketName + "/*", }, Condition: map[string]map[string]interface{}{ "StringEquals": map[string]interface{}{ "s3:x-amz-signature-version": "AWS4-HMAC-SHA256", }, }, }, }, } } // GetTemporaryAccessPolicy returns a policy for temporary access with expiration func (t *S3PolicyTemplates) GetTemporaryAccessPolicy(bucketName string, expirationHours int) *policy.PolicyDocument { expirationTime := time.Now().Add(time.Duration(expirationHours) * time.Hour) return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "TemporaryS3Access", Effect: "Allow", Action: []string{ "s3:GetObject", "s3:PutObject", "s3:ListBucket", }, Resource: []string{ "arn:seaweed:s3:::" + bucketName, "arn:seaweed:s3:::" + bucketName + "/*", }, Condition: map[string]map[string]interface{}{ "DateLessThan": map[string]interface{}{ "aws:CurrentTime": expirationTime.UTC().Format("2006-01-02T15:04:05Z"), }, }, }, }, } } // GetContentTypeRestrictedPolicy returns a policy that restricts uploads to specific content types func (t *S3PolicyTemplates) GetContentTypeRestrictedPolicy(bucketName string, allowedContentTypes []string) *policy.PolicyDocument { return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "ContentTypeRestrictedUpload", Effect: "Allow", Action: []string{ "s3:PutObject", "s3:CreateMultipartUpload", "s3:UploadPart", "s3:CompleteMultipartUpload", }, Resource: []string{ "arn:seaweed:s3:::" + bucketName + "/*", }, Condition: map[string]map[string]interface{}{ "StringEquals": map[string]interface{}{ "s3:content-type": allowedContentTypes, }, }, }, { Sid: "ReadAccess", Effect: "Allow", Action: []string{ "s3:GetObject", "s3:ListBucket", }, Resource: []string{ "arn:seaweed:s3:::" + bucketName, "arn:seaweed:s3:::" + bucketName + "/*", }, }, }, } } // GetDenyDeletePolicy returns a policy that allows all operations except delete func (t *S3PolicyTemplates) GetDenyDeletePolicy() *policy.PolicyDocument { return &policy.PolicyDocument{ Version: "2012-10-17", Statement: []policy.Statement{ { Sid: "AllowAllExceptDelete", Effect: "Allow", Action: []string{ "s3:GetObject", "s3:GetObjectVersion", "s3:PutObject", "s3:PutObjectAcl", "s3:ListBucket", "s3:ListBucketVersions", "s3:CreateMultipartUpload", "s3:UploadPart", "s3:CompleteMultipartUpload", "s3:AbortMultipartUpload", "s3:ListMultipartUploads", "s3:ListParts", }, Resource: []string{ "arn:seaweed:s3:::*", "arn:seaweed:s3:::*/*", }, }, { Sid: "DenyDeleteOperations", Effect: "Deny", Action: []string{ "s3:DeleteObject", "s3:DeleteObjectVersion", "s3:DeleteBucket", }, Resource: []string{ "arn:seaweed:s3:::*", "arn:seaweed:s3:::*/*", }, }, }, } } // Helper function to format hour with leading zero func formatHour(hour int) string { if hour < 10 { return "0" + string(rune('0'+hour)) } return string(rune('0'+hour/10)) + string(rune('0'+hour%10)) } // PolicyTemplateDefinition represents metadata about a policy template type PolicyTemplateDefinition struct { Name string `json:"name"` Description string `json:"description"` Category string `json:"category"` UseCase string `json:"use_case"` Parameters []PolicyTemplateParam `json:"parameters,omitempty"` Policy *policy.PolicyDocument `json:"policy"` } // PolicyTemplateParam represents a parameter for customizing policy templates type PolicyTemplateParam struct { Name string `json:"name"` Type string `json:"type"` Description string `json:"description"` Required bool `json:"required"` DefaultValue string `json:"default_value,omitempty"` Example string `json:"example,omitempty"` } // GetAllPolicyTemplates returns all available policy templates with metadata func (t *S3PolicyTemplates) GetAllPolicyTemplates() []PolicyTemplateDefinition { return []PolicyTemplateDefinition{ { Name: "S3ReadOnlyAccess", Description: "Provides read-only access to all S3 buckets and objects", Category: "Basic Access", UseCase: "Data consumers, backup services, monitoring applications", Policy: t.GetS3ReadOnlyPolicy(), }, { Name: "S3WriteOnlyAccess", Description: "Provides write-only access to all S3 buckets and objects", Category: "Basic Access", UseCase: "Data ingestion services, backup applications", Policy: t.GetS3WriteOnlyPolicy(), }, { Name: "S3AdminAccess", Description: "Provides full administrative access to all S3 resources", Category: "Administrative", UseCase: "S3 administrators, service accounts with full control", Policy: t.GetS3AdminPolicy(), }, { Name: "BucketSpecificRead", Description: "Provides read-only access to a specific bucket", Category: "Bucket-Specific", UseCase: "Applications that need access to specific data sets", Parameters: []PolicyTemplateParam{ { Name: "bucketName", Type: "string", Description: "Name of the S3 bucket to grant access to", Required: true, Example: "my-data-bucket", }, }, Policy: t.GetBucketSpecificReadPolicy("${bucketName}"), }, { Name: "BucketSpecificWrite", Description: "Provides write-only access to a specific bucket", Category: "Bucket-Specific", UseCase: "Upload services, data ingestion for specific datasets", Parameters: []PolicyTemplateParam{ { Name: "bucketName", Type: "string", Description: "Name of the S3 bucket to grant access to", Required: true, Example: "my-upload-bucket", }, }, Policy: t.GetBucketSpecificWritePolicy("${bucketName}"), }, { Name: "PathBasedAccess", Description: "Restricts access to a specific path/prefix within a bucket", Category: "Path-Restricted", UseCase: "Multi-tenant applications, user-specific directories", Parameters: []PolicyTemplateParam{ { Name: "bucketName", Type: "string", Description: "Name of the S3 bucket", Required: true, Example: "shared-bucket", }, { Name: "pathPrefix", Type: "string", Description: "Path prefix to restrict access to", Required: true, Example: "user123/documents", }, }, Policy: t.GetPathBasedAccessPolicy("${bucketName}", "${pathPrefix}"), }, { Name: "IPRestrictedAccess", Description: "Allows access only from specific IP addresses or ranges", Category: "Security", UseCase: "Corporate networks, office-based access, VPN restrictions", Parameters: []PolicyTemplateParam{ { Name: "allowedCIDRs", Type: "array", Description: "List of allowed IP addresses or CIDR ranges", Required: true, Example: "[\"192.168.1.0/24\", \"10.0.0.0/8\"]", }, }, Policy: t.GetIPRestrictedPolicy([]string{"${allowedCIDRs}"}), }, { Name: "MultipartUploadOnly", Description: "Allows only multipart upload operations on a specific bucket", Category: "Upload-Specific", UseCase: "Large file upload services, streaming applications", Parameters: []PolicyTemplateParam{ { Name: "bucketName", Type: "string", Description: "Name of the S3 bucket for multipart uploads", Required: true, Example: "large-files-bucket", }, }, Policy: t.GetMultipartUploadPolicy("${bucketName}"), }, { Name: "PresignedURLAccess", Description: "Policy for generating and using presigned URLs", Category: "Presigned URLs", UseCase: "Frontend applications, temporary file sharing", Parameters: []PolicyTemplateParam{ { Name: "bucketName", Type: "string", Description: "Name of the S3 bucket for presigned URL access", Required: true, Example: "shared-files-bucket", }, }, Policy: t.GetPresignedURLPolicy("${bucketName}"), }, { Name: "ContentTypeRestricted", Description: "Restricts uploads to specific content types", Category: "Content Control", UseCase: "Image galleries, document repositories, media libraries", Parameters: []PolicyTemplateParam{ { Name: "bucketName", Type: "string", Description: "Name of the S3 bucket", Required: true, Example: "media-bucket", }, { Name: "allowedContentTypes", Type: "array", Description: "List of allowed MIME content types", Required: true, Example: "[\"image/jpeg\", \"image/png\", \"video/mp4\"]", }, }, Policy: t.GetContentTypeRestrictedPolicy("${bucketName}", []string{"${allowedContentTypes}"}), }, { Name: "DenyDeleteAccess", Description: "Allows all operations except delete (immutable storage)", Category: "Data Protection", UseCase: "Compliance storage, audit logs, backup retention", Policy: t.GetDenyDeletePolicy(), }, } } // GetPolicyTemplateByName returns a specific policy template by name func (t *S3PolicyTemplates) GetPolicyTemplateByName(name string) *PolicyTemplateDefinition { templates := t.GetAllPolicyTemplates() for _, template := range templates { if template.Name == name { return &template } } return nil } // GetPolicyTemplatesByCategory returns all policy templates in a specific category func (t *S3PolicyTemplates) GetPolicyTemplatesByCategory(category string) []PolicyTemplateDefinition { var result []PolicyTemplateDefinition templates := t.GetAllPolicyTemplates() for _, template := range templates { if template.Category == category { result = append(result, template) } } return result }