4 changed files with 246 additions and 12 deletions
-
8weed/s3api/s3api_tables.go
-
7weed/s3api/s3tables/handler.go
-
34weed/s3api/s3tables/handler_bucket_create.go
-
209weed/s3api/s3tables/iam.go
@ -0,0 +1,209 @@ |
|||
package s3tables |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/http" |
|||
"reflect" |
|||
"strings" |
|||
|
|||
"github.com/seaweedfs/seaweedfs/weed/glog" |
|||
"github.com/seaweedfs/seaweedfs/weed/iam/integration" |
|||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" |
|||
) |
|||
|
|||
// IAMAuthorizer allows s3tables handlers to evaluate IAM policies without importing s3api.
|
|||
type IAMAuthorizer interface { |
|||
IsActionAllowed(ctx context.Context, request *integration.ActionRequest) (bool, error) |
|||
} |
|||
|
|||
// SetIAMAuthorizer injects the IAM authorizer for policy-based access checks.
|
|||
func (h *S3TablesHandler) SetIAMAuthorizer(authorizer IAMAuthorizer) { |
|||
h.iamAuthorizer = authorizer |
|||
} |
|||
|
|||
func (h *S3TablesHandler) shouldUseIAM(r *http.Request, identityActions []string) bool { |
|||
if h.iamAuthorizer == nil || r == nil { |
|||
return false |
|||
} |
|||
if hasSessionToken(r) { |
|||
return true |
|||
} |
|||
return len(identityActions) == 0 |
|||
} |
|||
|
|||
func hasSessionToken(r *http.Request) bool { |
|||
if r.Header.Get("X-SeaweedFS-Session-Token") != "" { |
|||
return true |
|||
} |
|||
if r.Header.Get("X-Amz-Security-Token") != "" { |
|||
return true |
|||
} |
|||
return r.URL.Query().Get("X-Amz-Security-Token") != "" |
|||
} |
|||
|
|||
func (h *S3TablesHandler) authorizeIAMAction(r *http.Request, action string, resources ...string) (bool, error) { |
|||
if h.iamAuthorizer == nil { |
|||
return false, nil |
|||
} |
|||
principal := r.Header.Get("X-SeaweedFS-Principal") |
|||
if principal == "" { |
|||
principal = getIdentityPrincipalArn(r) |
|||
} |
|||
if principal == "" { |
|||
return false, fmt.Errorf("missing principal for IAM authorization") |
|||
} |
|||
|
|||
if !strings.Contains(action, ":") { |
|||
action = "s3tables:" + action |
|||
} |
|||
|
|||
sessionToken := r.Header.Get("X-SeaweedFS-Session-Token") |
|||
if sessionToken == "" { |
|||
sessionToken = r.Header.Get("X-Amz-Security-Token") |
|||
if sessionToken == "" { |
|||
sessionToken = r.URL.Query().Get("X-Amz-Security-Token") |
|||
} |
|||
} |
|||
|
|||
requestContext := buildIAMRequestContext(r, getIdentityClaims(r)) |
|||
policyNames := getIdentityPolicyNames(r) |
|||
|
|||
var lastErr error |
|||
for _, resource := range resources { |
|||
if resource == "" { |
|||
continue |
|||
} |
|||
allowed, err := h.iamAuthorizer.IsActionAllowed(r.Context(), &integration.ActionRequest{ |
|||
Principal: principal, |
|||
Action: action, |
|||
Resource: resource, |
|||
SessionToken: sessionToken, |
|||
RequestContext: requestContext, |
|||
PolicyNames: policyNames, |
|||
}) |
|||
if err != nil { |
|||
lastErr = err |
|||
glog.V(2).Infof("S3Tables: IAM authorization error action=%s resource=%s principal=%s: %v", action, resource, principal, err) |
|||
continue |
|||
} |
|||
if allowed { |
|||
return true, nil |
|||
} |
|||
} |
|||
return false, lastErr |
|||
} |
|||
|
|||
func getIdentityPrincipalArn(r *http.Request) string { |
|||
identityRaw := s3_constants.GetIdentityFromContext(r) |
|||
if identityRaw == nil { |
|||
return "" |
|||
} |
|||
val := reflect.ValueOf(identityRaw) |
|||
if val.Kind() == reflect.Ptr { |
|||
val = val.Elem() |
|||
} |
|||
if val.Kind() != reflect.Struct { |
|||
return "" |
|||
} |
|||
field := val.FieldByName("PrincipalArn") |
|||
if field.IsValid() && field.Kind() == reflect.String { |
|||
return field.String() |
|||
} |
|||
return "" |
|||
} |
|||
|
|||
func getIdentityPolicyNames(r *http.Request) []string { |
|||
identityRaw := s3_constants.GetIdentityFromContext(r) |
|||
if identityRaw == nil { |
|||
return nil |
|||
} |
|||
val := reflect.ValueOf(identityRaw) |
|||
if val.Kind() == reflect.Ptr { |
|||
val = val.Elem() |
|||
} |
|||
if val.Kind() != reflect.Struct { |
|||
return nil |
|||
} |
|||
field := val.FieldByName("PolicyNames") |
|||
if !field.IsValid() || field.Kind() != reflect.Slice { |
|||
return nil |
|||
} |
|||
policies := make([]string, 0, field.Len()) |
|||
for i := 0; i < field.Len(); i++ { |
|||
item := field.Index(i) |
|||
if item.Kind() == reflect.String { |
|||
policies = append(policies, item.String()) |
|||
} else if item.CanInterface() { |
|||
policies = append(policies, fmt.Sprint(item.Interface())) |
|||
} |
|||
} |
|||
if len(policies) == 0 { |
|||
return nil |
|||
} |
|||
return policies |
|||
} |
|||
|
|||
func getIdentityClaims(r *http.Request) map[string]interface{} { |
|||
identityRaw := s3_constants.GetIdentityFromContext(r) |
|||
if identityRaw == nil { |
|||
return nil |
|||
} |
|||
val := reflect.ValueOf(identityRaw) |
|||
if val.Kind() == reflect.Ptr { |
|||
val = val.Elem() |
|||
} |
|||
if val.Kind() != reflect.Struct { |
|||
return nil |
|||
} |
|||
field := val.FieldByName("Claims") |
|||
if !field.IsValid() || field.Kind() != reflect.Map || field.IsNil() { |
|||
return nil |
|||
} |
|||
if field.Type().Key().Kind() != reflect.String { |
|||
return nil |
|||
} |
|||
claims := make(map[string]interface{}, field.Len()) |
|||
for _, key := range field.MapKeys() { |
|||
if key.Kind() != reflect.String { |
|||
continue |
|||
} |
|||
val := field.MapIndex(key) |
|||
if !val.IsValid() { |
|||
continue |
|||
} |
|||
claims[key.String()] = val.Interface() |
|||
} |
|||
if len(claims) == 0 { |
|||
return nil |
|||
} |
|||
return claims |
|||
} |
|||
|
|||
func buildIAMRequestContext(r *http.Request, claims map[string]interface{}) map[string]interface{} { |
|||
ctx := make(map[string]interface{}) |
|||
if ua := r.Header.Get("User-Agent"); ua != "" { |
|||
ctx["userAgent"] = ua |
|||
} |
|||
if referer := r.Header.Get("Referer"); referer != "" { |
|||
ctx["referer"] = referer |
|||
} |
|||
if requestTime := r.Context().Value("requestTime"); requestTime != nil { |
|||
ctx["requestTime"] = requestTime |
|||
} |
|||
for k, v := range claims { |
|||
if _, exists := ctx[k]; !exists { |
|||
ctx[k] = v |
|||
} |
|||
if !strings.Contains(k, ":") { |
|||
jwtKey := "jwt:" + k |
|||
if _, exists := ctx[jwtKey]; !exists { |
|||
ctx[jwtKey] = v |
|||
} |
|||
} |
|||
} |
|||
if len(ctx) == 0 { |
|||
return nil |
|||
} |
|||
return ctx |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue