storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/iam/policy/policy.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2018 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package iampolicy
    18  
    19  import (
    20  	"encoding/json"
    21  	"io"
    22  	"strings"
    23  
    24  	"github.com/minio/minio-go/v7/pkg/set"
    25  
    26  	"storj.io/minio/pkg/bucket/policy"
    27  )
    28  
    29  // DefaultVersion - default policy version as per AWS S3 specification.
    30  const DefaultVersion = "2012-10-17"
    31  
    32  // Args - arguments to policy to check whether it is allowed
    33  type Args struct {
    34  	AccountName     string                 `json:"account"`
    35  	Groups          []string               `json:"groups"`
    36  	Action          Action                 `json:"action"`
    37  	BucketName      string                 `json:"bucket"`
    38  	ConditionValues map[string][]string    `json:"conditions"`
    39  	IsOwner         bool                   `json:"owner"`
    40  	ObjectName      string                 `json:"object"`
    41  	Claims          map[string]interface{} `json:"claims"`
    42  	DenyOnly        bool                   `json:"denyOnly"` // only applies deny
    43  }
    44  
    45  // GetPoliciesFromClaims returns the list of policies to be applied for this
    46  // incoming request, extracting the information from input JWT claims.
    47  func GetPoliciesFromClaims(claims map[string]interface{}, policyClaimName string) (set.StringSet, bool) {
    48  	s := set.NewStringSet()
    49  	pname, ok := claims[policyClaimName]
    50  	if !ok {
    51  		return s, false
    52  	}
    53  	pnames, ok := pname.([]interface{})
    54  	if !ok {
    55  		pnameStr, ok := pname.(string)
    56  		if ok {
    57  			for _, pname := range strings.Split(pnameStr, ",") {
    58  				pname = strings.TrimSpace(pname)
    59  				if pname == "" {
    60  					// ignore any empty strings, considerate
    61  					// towards some user errors.
    62  					continue
    63  				}
    64  				s.Add(pname)
    65  			}
    66  			return s, true
    67  		}
    68  		return s, false
    69  	}
    70  	for _, pname := range pnames {
    71  		pnameStr, ok := pname.(string)
    72  		if ok {
    73  			for _, pnameStr := range strings.Split(pnameStr, ",") {
    74  				pnameStr = strings.TrimSpace(pnameStr)
    75  				if pnameStr == "" {
    76  					// ignore any empty strings, considerate
    77  					// towards some user errors.
    78  					continue
    79  				}
    80  				s.Add(pnameStr)
    81  			}
    82  		}
    83  	}
    84  	return s, true
    85  }
    86  
    87  // GetPolicies returns the list of policies to be applied for this
    88  // incoming request, extracting the information from JWT claims.
    89  func (a Args) GetPolicies(policyClaimName string) (set.StringSet, bool) {
    90  	return GetPoliciesFromClaims(a.Claims, policyClaimName)
    91  }
    92  
    93  // Policy - iam bucket iamp.
    94  type Policy struct {
    95  	ID         policy.ID `json:"ID,omitempty"`
    96  	Version    string
    97  	Statements []Statement `json:"Statement"`
    98  }
    99  
   100  // IsAllowed - checks given policy args is allowed to continue the Rest API.
   101  func (iamp Policy) IsAllowed(args Args) bool {
   102  	// Check all deny statements. If any one statement denies, return false.
   103  	for _, statement := range iamp.Statements {
   104  		if statement.Effect == policy.Deny {
   105  			if !statement.IsAllowed(args) {
   106  				return false
   107  			}
   108  		}
   109  	}
   110  
   111  	// Applied any 'Deny' only policies, if we have
   112  	// reached here it means that there were no 'Deny'
   113  	// policies - this function mainly used for
   114  	// specific scenarios where we only want to validate
   115  	// 'Deny' only policies.
   116  	if args.DenyOnly {
   117  		return true
   118  	}
   119  
   120  	// For owner, its allowed by default.
   121  	if args.IsOwner {
   122  		return true
   123  	}
   124  
   125  	// Check all allow statements. If any one statement allows, return true.
   126  	for _, statement := range iamp.Statements {
   127  		if statement.Effect == policy.Allow {
   128  			if statement.IsAllowed(args) {
   129  				return true
   130  			}
   131  		}
   132  	}
   133  
   134  	return false
   135  }
   136  
   137  // IsEmpty - returns whether policy is empty or not.
   138  func (iamp Policy) IsEmpty() bool {
   139  	return len(iamp.Statements) == 0
   140  }
   141  
   142  // isValid - checks if Policy is valid or not.
   143  func (iamp Policy) isValid() error {
   144  	if iamp.Version != DefaultVersion && iamp.Version != "" {
   145  		return Errorf("invalid version '%v'", iamp.Version)
   146  	}
   147  
   148  	for _, statement := range iamp.Statements {
   149  		if err := statement.isValid(); err != nil {
   150  			return err
   151  		}
   152  	}
   153  	return nil
   154  }
   155  
   156  // Merge merges two policies documents and drop
   157  // duplicate statements if any.
   158  func (iamp Policy) Merge(input Policy) Policy {
   159  	var mergedPolicy Policy
   160  	if iamp.Version != "" {
   161  		mergedPolicy.Version = iamp.Version
   162  	} else {
   163  		mergedPolicy.Version = input.Version
   164  	}
   165  	for _, st := range iamp.Statements {
   166  		mergedPolicy.Statements = append(mergedPolicy.Statements, st.Clone())
   167  	}
   168  	for _, st := range input.Statements {
   169  		mergedPolicy.Statements = append(mergedPolicy.Statements, st.Clone())
   170  	}
   171  	mergedPolicy.dropDuplicateStatements()
   172  	return mergedPolicy
   173  }
   174  
   175  func (iamp *Policy) dropDuplicateStatements() {
   176  redo:
   177  	for i := range iamp.Statements {
   178  		for j, statement := range iamp.Statements[i+1:] {
   179  			if !iamp.Statements[i].Equals(statement) {
   180  				continue
   181  			}
   182  			iamp.Statements = append(iamp.Statements[:j], iamp.Statements[j+1:]...)
   183  			goto redo
   184  		}
   185  	}
   186  }
   187  
   188  // UnmarshalJSON - decodes JSON data to Iamp.
   189  func (iamp *Policy) UnmarshalJSON(data []byte) error {
   190  	// subtype to avoid recursive call to UnmarshalJSON()
   191  	type subPolicy Policy
   192  	var sp subPolicy
   193  	if err := json.Unmarshal(data, &sp); err != nil {
   194  		return err
   195  	}
   196  
   197  	p := Policy(sp)
   198  	p.dropDuplicateStatements()
   199  	*iamp = p
   200  	return nil
   201  }
   202  
   203  // Validate - validates all statements are for given bucket or not.
   204  func (iamp Policy) Validate() error {
   205  	return iamp.isValid()
   206  }
   207  
   208  // ParseConfig - parses data in given reader to Iamp.
   209  func ParseConfig(reader io.Reader) (*Policy, error) {
   210  	var iamp Policy
   211  
   212  	decoder := json.NewDecoder(reader)
   213  	decoder.DisallowUnknownFields()
   214  	if err := decoder.Decode(&iamp); err != nil {
   215  		return nil, Errorf("%w", err)
   216  	}
   217  
   218  	return &iamp, iamp.Validate()
   219  }