mirror of
https://github.com/cubefs/cubefs.git
synced 2026-08-02 10:06:14 +00:00
313 lines
7.3 KiB
Go
313 lines
7.3 KiB
Go
// Copyright 2019 The CubeFS Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
// implied. See the License for the specific language governing
|
|
// permissions and limitations under the License.
|
|
|
|
package objectnode
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/cubefs/cubefs/util/log"
|
|
)
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html
|
|
// https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/example-bucket-policies.html
|
|
|
|
type (
|
|
Principal map[string]StringSet
|
|
Resource string
|
|
)
|
|
|
|
const (
|
|
Allow = "Allow"
|
|
Deny = "Deny"
|
|
)
|
|
|
|
type Statement struct {
|
|
Sid string `json:"Sid"`
|
|
Effect string `json:"Effect"`
|
|
Principal interface{} `json:"Principal"` // map or string
|
|
Action interface{} `json:"Action"` // []string or string
|
|
Resource interface{} `json:"Resource"` // []string or string
|
|
Condition Condition `json:"Condition,omitempty"`
|
|
}
|
|
|
|
func (s Statement) IsAllowed(p *RequestParam) bool {
|
|
return s.Effect == Allow
|
|
}
|
|
|
|
func (s *Statement) Validate(bucket string) (bool, error) {
|
|
return s.isValid(bucket)
|
|
}
|
|
|
|
func (s *Statement) isValid(bucket string) (bool, error) {
|
|
log.LogDebug("start to validate statement")
|
|
// step 1: check required field
|
|
if err := s.checkRequiredField(); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// step2: check each field
|
|
if !s.isEffectValid() {
|
|
return false, ErrInvalidEffectValue
|
|
}
|
|
|
|
if !s.isPrincipalValid() {
|
|
return false, ErrInvalidPricipalInPolicy
|
|
}
|
|
|
|
if !s.isActionValid() {
|
|
return false, ErrInvalidActionInPolicy
|
|
}
|
|
|
|
if !s.isResourceValid(bucket) {
|
|
return false, ErrInvalidResourceInPolicy
|
|
}
|
|
|
|
// step3: check action & resource valid combination
|
|
if !s.isValidCombination() {
|
|
return false, ErrInvalidActionResourceCombination
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (s *Statement) checkRequiredField() error {
|
|
switch {
|
|
case s.Effect == "":
|
|
return ErrMissingEffectInPolicy
|
|
case s.Principal == nil:
|
|
return ErrMissingPrincipalInPolicy
|
|
case s.Action == nil:
|
|
return ErrMissingActionInPolicy
|
|
case s.Resource == nil:
|
|
return ErrMissingResourceInPolicy
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (s *Statement) isEffectValid() bool {
|
|
e := strings.ToLower(s.Effect)
|
|
if e == "allow" || e == "deny" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// "Principal": "*" or "Principal" : {"AWS":"111122223333"} or "Principal" : {"AWS":["111122223333","444455556666"]}
|
|
func (s *Statement) isPrincipalValid() bool {
|
|
// principal: uid must be "*" or uint32, and can't be 0
|
|
switch s.Principal.(type) {
|
|
case string: // "*" or "123"
|
|
p := s.Principal.(string)
|
|
if !PrincipalElementType(p).valid() {
|
|
return false
|
|
}
|
|
case map[string]interface{}:
|
|
p := s.Principal.(map[string]interface{})
|
|
if !PrincipalType(p).valid() {
|
|
return false
|
|
}
|
|
default:
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (p PrincipalType) valid() bool {
|
|
p1, ok := p[S3_PRINCIPAL_PREFIX]
|
|
if !ok {
|
|
return false
|
|
}
|
|
switch pval := p1.(type) {
|
|
case []interface{}:
|
|
p2 := pval
|
|
if len(p2) == 0 {
|
|
return false
|
|
}
|
|
for _, p3 := range p2 {
|
|
p4, ok := p3.(string)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if !PrincipalElementType(p4).valid() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case string:
|
|
if !PrincipalElementType(pval).valid() {
|
|
return false
|
|
}
|
|
default:
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (p PrincipalElementType) valid() bool {
|
|
return true
|
|
}
|
|
|
|
func (s *Statement) isActionValid() bool {
|
|
switch s.Action.(type) {
|
|
case []interface{}: // ["s3:PutObject", "s3:GetObject","s3:DeleteObject"]
|
|
actions := s.Action.([]interface{})
|
|
return ActionType(actions).valid()
|
|
case string: // "s3:ListBucket"
|
|
action := s.Action.(string)
|
|
return ActionElementType(action).valid()
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (a ActionElementType) valid() bool {
|
|
a1 := strings.TrimPrefix(strings.ToLower(string(a)), S3_ACTION_PREFIX)
|
|
if a1 == ACTION_ANY {
|
|
return true
|
|
}
|
|
_, ok := S3ActionToApis[a1]
|
|
return ok
|
|
}
|
|
|
|
func (actions ActionType) valid() bool {
|
|
if len(actions) == 0 {
|
|
return false
|
|
}
|
|
for _, a := range actions {
|
|
a1, ok := a.(string)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if !ActionElementType(a1).valid() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s *Statement) isResourceValid(bucketId string) bool {
|
|
switch s.Resource.(type) {
|
|
case string: // "Resource":"arn:aws:s3:::bucket/*"
|
|
r := s.Resource.(string)
|
|
return ResourceElement(r).valid(bucketId)
|
|
case []interface{}: // "Resource":["arn:aws:s3:::bucket/abc/*"]
|
|
r := s.Resource.([]interface{})
|
|
if len(r) == 0 {
|
|
return false
|
|
}
|
|
for _, r1 := range r {
|
|
r2, ok := r1.(string)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if !ResourceElement(r2).valid(bucketId) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (r ResourceElementType) valid(bucketId string) bool {
|
|
bucket_key := strings.SplitN(string(r), "/", 2)
|
|
if len(bucket_key) < 2 {
|
|
return r == ResourceElementType(bucketId)
|
|
}
|
|
if bucket_key[0] != bucketId { // bucketId in resource list must be same with current bucketId
|
|
return false
|
|
}
|
|
if bucket_key[1] == "" { // key can't be empty when bucket is followed by a slash
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s *Statement) isValidCombination() bool {
|
|
// check action & resource valid combination
|
|
hasBucketFormat, hasObjectFormat := s.getResourceFormat()
|
|
switch s.Action.(type) {
|
|
case string:
|
|
action := s.Action.(string)
|
|
return ActionElementType(action).matchResource(hasBucketFormat, hasObjectFormat)
|
|
case []interface{}:
|
|
actions := s.Action.([]interface{})
|
|
return ActionType(actions).matchResource(hasBucketFormat, hasObjectFormat)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (a ActionElementType) matchResource(hasBucketFormat, hasObjectFormat bool) bool {
|
|
a1 := strings.TrimPrefix(strings.ToLower(string(a)), S3_ACTION_PREFIX)
|
|
if a1 == ACTION_ANY { // when action = "*", resource can contain "bucket" or "bucket/key"
|
|
return hasBucketFormat || hasObjectFormat
|
|
}
|
|
if validBucketActions.Contain(string(a1)) {
|
|
return hasBucketFormat
|
|
}
|
|
return hasObjectFormat
|
|
}
|
|
|
|
func (actions ActionType) matchResource(hasBucketFormat, hasObjectFormat bool) bool {
|
|
if len(actions) == 0 {
|
|
return false
|
|
}
|
|
for _, a := range actions { // every action should match
|
|
if a1, ok := a.(string); ok {
|
|
if !ActionElementType(a1).matchResource(hasBucketFormat, hasObjectFormat) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s *Statement) getResourceFormat() (hasBucketFormat, hasObjectFormat bool) {
|
|
switch s.Resource.(type) {
|
|
case string:
|
|
r := s.Resource.(string)
|
|
return ResourceElement(r).format()
|
|
case []interface{}:
|
|
r := s.Resource.([]interface{})
|
|
if len(r) == 0 {
|
|
return
|
|
}
|
|
for _, r1 := range r {
|
|
if r2, ok := r1.(string); ok {
|
|
isBucketFormat, isKeyFormat := ResourceElement(r2).format()
|
|
if isBucketFormat {
|
|
hasBucketFormat = true
|
|
}
|
|
if isKeyFormat {
|
|
hasObjectFormat = true
|
|
}
|
|
}
|
|
}
|
|
return
|
|
default:
|
|
return false, false
|
|
}
|
|
}
|
|
|
|
func (r ResourceElementType) format() (isBucketFormat, isKeyFormat bool) {
|
|
isBucketFormat = r.isBucketFormat()
|
|
isKeyFormat = r.isKeyFormat()
|
|
return
|
|
}
|