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.
189 lines
7.0 KiB
189 lines
7.0 KiB
package iam
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha1"
|
|
"fmt"
|
|
"math/big"
|
|
"sort"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
)
|
|
|
|
// Hash computes a SHA1 hash of the input string.
|
|
func Hash(s *string) string {
|
|
h := sha1.New()
|
|
h.Write([]byte(*s))
|
|
return fmt.Sprintf("%x", h.Sum(nil))
|
|
}
|
|
|
|
// GenerateRandomString generates a cryptographically secure random string.
|
|
// Uses crypto/rand for security-sensitive credential generation.
|
|
func GenerateRandomString(length int, charset string) (string, error) {
|
|
if length <= 0 {
|
|
return "", fmt.Errorf("length must be positive, got %d", length)
|
|
}
|
|
if charset == "" {
|
|
return "", fmt.Errorf("charset must not be empty")
|
|
}
|
|
b := make([]byte, length)
|
|
for i := range b {
|
|
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to generate random index: %w", err)
|
|
}
|
|
b[i] = charset[n.Int64()]
|
|
}
|
|
return string(b), nil
|
|
}
|
|
|
|
// GenerateAccessKeyId generates a new access key ID.
|
|
func GenerateAccessKeyId() (string, error) {
|
|
return GenerateRandomString(AccessKeyIdLength, CharsetUpper)
|
|
}
|
|
|
|
// GenerateSecretAccessKey generates a new secret access key.
|
|
func GenerateSecretAccessKey() (string, error) {
|
|
return GenerateRandomString(SecretAccessKeyLength, Charset)
|
|
}
|
|
|
|
// StringSlicesEqual compares two string slices for equality, ignoring order.
|
|
// This is used instead of reflect.DeepEqual to avoid order-dependent comparisons.
|
|
func StringSlicesEqual(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
// Make copies to avoid modifying the originals
|
|
aCopy := make([]string, len(a))
|
|
bCopy := make([]string, len(b))
|
|
copy(aCopy, a)
|
|
copy(bCopy, b)
|
|
sort.Strings(aCopy)
|
|
sort.Strings(bCopy)
|
|
for i := range aCopy {
|
|
if aCopy[i] != bCopy[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// fineGrainedActionMap maps S3 IAM action names to internal S3 action constants.
|
|
// Supports both prefixed (e.g., "s3:DeleteObject") and unprefixed (e.g., "DeleteObject") formats.
|
|
// Populated in init() to avoid duplication of prefixed/unprefixed variants.
|
|
var fineGrainedActionMap = map[string]string{
|
|
// Coarse-grained actions (populated statically)
|
|
StatementActionAdmin: s3_constants.ACTION_ADMIN,
|
|
StatementActionWrite: s3_constants.ACTION_WRITE,
|
|
StatementActionWriteAcp: s3_constants.ACTION_WRITE_ACP,
|
|
StatementActionRead: s3_constants.ACTION_READ,
|
|
StatementActionReadAcp: s3_constants.ACTION_READ_ACP,
|
|
StatementActionList: s3_constants.ACTION_LIST,
|
|
StatementActionTagging: s3_constants.ACTION_TAGGING,
|
|
StatementActionDelete: s3_constants.ACTION_DELETE_BUCKET,
|
|
}
|
|
|
|
// baseS3ActionMap defines the base S3 actions that will be populated with both
|
|
// prefixed (s3:Action) and unprefixed (Action) variants in init().
|
|
var baseS3ActionMap = map[string]string{
|
|
// Object operations
|
|
"DeleteObject": s3_constants.ACTION_WRITE,
|
|
"PutObject": s3_constants.ACTION_WRITE,
|
|
"GetObject": s3_constants.ACTION_READ,
|
|
"DeleteObjectVersion": s3_constants.ACTION_WRITE,
|
|
"GetObjectVersion": s3_constants.ACTION_READ,
|
|
// Tagging operations
|
|
"GetObjectTagging": s3_constants.ACTION_TAGGING,
|
|
"GetObjectVersionTagging": s3_constants.ACTION_TAGGING,
|
|
"PutObjectTagging": s3_constants.ACTION_TAGGING,
|
|
"DeleteObjectTagging": s3_constants.ACTION_TAGGING,
|
|
"GetBucketTagging": s3_constants.ACTION_TAGGING,
|
|
"PutBucketTagging": s3_constants.ACTION_TAGGING,
|
|
"DeleteBucketTagging": s3_constants.ACTION_TAGGING,
|
|
// ACL operations
|
|
"PutObjectAcl": s3_constants.ACTION_WRITE_ACP,
|
|
"GetObjectAcl": s3_constants.ACTION_READ_ACP,
|
|
"GetObjectVersionAcl": s3_constants.ACTION_READ_ACP,
|
|
"PutBucketAcl": s3_constants.ACTION_WRITE_ACP,
|
|
"GetBucketAcl": s3_constants.ACTION_READ_ACP,
|
|
// Bucket operations
|
|
"DeleteBucket": s3_constants.ACTION_DELETE_BUCKET,
|
|
"DeleteBucketPolicy": s3_constants.ACTION_ADMIN,
|
|
"ListBucket": s3_constants.ACTION_LIST,
|
|
"ListBucketVersions": s3_constants.ACTION_LIST,
|
|
"ListAllMyBuckets": s3_constants.ACTION_LIST,
|
|
"GetBucketLocation": s3_constants.ACTION_READ,
|
|
"GetBucketVersioning": s3_constants.ACTION_READ,
|
|
"PutBucketVersioning": s3_constants.ACTION_WRITE,
|
|
"GetBucketCors": s3_constants.ACTION_READ,
|
|
"PutBucketCors": s3_constants.ACTION_WRITE,
|
|
"DeleteBucketCors": s3_constants.ACTION_WRITE,
|
|
"GetBucketNotification": s3_constants.ACTION_READ,
|
|
"PutBucketNotification": s3_constants.ACTION_WRITE,
|
|
"GetBucketObjectLockConfiguration": s3_constants.ACTION_READ,
|
|
"PutBucketObjectLockConfiguration": s3_constants.ACTION_WRITE,
|
|
// Multipart upload operations
|
|
"CreateMultipartUpload": s3_constants.ACTION_WRITE,
|
|
"UploadPart": s3_constants.ACTION_WRITE,
|
|
"CompleteMultipartUpload": s3_constants.ACTION_WRITE,
|
|
"AbortMultipartUpload": s3_constants.ACTION_WRITE,
|
|
"ListMultipartUploads": s3_constants.ACTION_WRITE,
|
|
"ListParts": s3_constants.ACTION_WRITE,
|
|
// Retention and legal hold operations
|
|
"GetObjectRetention": s3_constants.ACTION_READ,
|
|
"PutObjectRetention": s3_constants.ACTION_WRITE,
|
|
"GetObjectLegalHold": s3_constants.ACTION_READ,
|
|
"PutObjectLegalHold": s3_constants.ACTION_WRITE,
|
|
"BypassGovernanceRetention": s3_constants.ACTION_WRITE,
|
|
}
|
|
|
|
func init() {
|
|
// Populate both prefixed and unprefixed variants for all base S3 actions.
|
|
// This avoids duplication and makes it easy to add new actions in one place.
|
|
for action, constant := range baseS3ActionMap {
|
|
fineGrainedActionMap[action] = constant // unprefixed: "DeleteObject"
|
|
fineGrainedActionMap["s3:"+action] = constant // prefixed: "s3:DeleteObject"
|
|
}
|
|
}
|
|
|
|
// MapToStatementAction converts a policy statement action to an S3 action constant.
|
|
// It handles both coarse-grained statement actions (e.g., "Put*", "Get*") and
|
|
// fine-grained S3 actions (e.g., "s3:DeleteObject", "s3:PutObject") via exact lookup.
|
|
func MapToStatementAction(action string) string {
|
|
if val, ok := fineGrainedActionMap[action]; ok {
|
|
return val
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// MapToIdentitiesAction converts an S3 action constant to a policy statement action.
|
|
func MapToIdentitiesAction(action string) string {
|
|
switch action {
|
|
case s3_constants.ACTION_ADMIN:
|
|
return StatementActionAdmin
|
|
case s3_constants.ACTION_WRITE:
|
|
return StatementActionWrite
|
|
case s3_constants.ACTION_WRITE_ACP:
|
|
return StatementActionWriteAcp
|
|
case s3_constants.ACTION_READ:
|
|
return StatementActionRead
|
|
case s3_constants.ACTION_READ_ACP:
|
|
return StatementActionReadAcp
|
|
case s3_constants.ACTION_LIST:
|
|
return StatementActionList
|
|
case s3_constants.ACTION_TAGGING:
|
|
return StatementActionTagging
|
|
case s3_constants.ACTION_DELETE_BUCKET:
|
|
return StatementActionDelete
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// MaskAccessKey masks an access key for logging, showing only the first 4 characters.
|
|
func MaskAccessKey(accessKeyId string) string {
|
|
if len(accessKeyId) > 4 {
|
|
return accessKeyId[:4] + "***"
|
|
}
|
|
return accessKeyId
|
|
}
|