Support policy variable replacement (#7085)

This PR supports iam and bucket policies to have
policy variable replacements in resource and
condition key values.

For example
- ${aws:username}
- ${aws:userid}
This commit is contained in:
Harshavardhana
2019-01-21 10:27:14 +05:30
committed by GitHub
parent 3265112d04
commit 5353edcc38
19 changed files with 125 additions and 56 deletions

View File

@@ -21,6 +21,7 @@ import (
"fmt"
"strings"
"github.com/minio/minio/pkg/policy/condition"
"github.com/minio/minio/pkg/wildcard"
)
@@ -47,11 +48,18 @@ func (r Resource) IsValid() bool {
}
// Match - matches object name with resource pattern.
func (r Resource) Match(resource string) bool {
if strings.HasPrefix(resource, r.Pattern) {
func (r Resource) Match(resource string, conditionValues map[string][]string) bool {
pattern := r.Pattern
for _, key := range condition.CommonKeys {
// Empty values are not supported for policy variables.
if rvalues, ok := conditionValues[key.Name()]; ok && rvalues[0] != "" {
pattern = strings.Replace(pattern, key.VarName(), rvalues[0], -1)
}
}
if strings.HasPrefix(resource, pattern) {
return true
}
return wildcard.Match(r.Pattern, resource)
return wildcard.Match(pattern, resource)
}
// MarshalJSON - encodes Resource to JSON data.

View File

@@ -124,7 +124,7 @@ func TestResourceMatch(t *testing.T) {
}
for i, testCase := range testCases {
result := testCase.resource.Match(testCase.objectName)
result := testCase.resource.Match(testCase.objectName, nil)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)

View File

@@ -81,9 +81,9 @@ func (resourceSet ResourceSet) MarshalJSON() ([]byte, error) {
}
// Match - matches object name with anyone of resource pattern in resource set.
func (resourceSet ResourceSet) Match(resource string) bool {
func (resourceSet ResourceSet) Match(resource string, conditionValues map[string][]string) bool {
for r := range resourceSet {
if r.Match(resource) {
if r.Match(resource, conditionValues) {
return true
}
}

View File

@@ -179,7 +179,7 @@ func TestResourceSetMatch(t *testing.T) {
}
for i, testCase := range testCases {
result := testCase.resourceSet.Match(testCase.resource)
result := testCase.resourceSet.Match(testCase.resource, nil)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)

View File

@@ -52,7 +52,7 @@ func (statement Statement) IsAllowed(args Args) bool {
resource += "/"
}
if !statement.Resources.Match(resource) {
if !statement.Resources.Match(resource, args.ConditionValues) {
return false
}

View File

@@ -51,7 +51,8 @@ func (f binaryEqualsFunc) evaluate(values map[string][]string) bool {
requestValue = values[f.k.Name()]
}
return !f.values.Intersection(set.CreateStringSet(requestValue...)).IsEmpty()
fvalues := f.values.ApplyFunc(substFuncFromValues(values))
return !fvalues.Intersection(set.CreateStringSet(requestValue...)).IsEmpty()
}
// key() - returns condition key which is used by this condition function.

View File

@@ -33,49 +33,58 @@ const (
// S3XAmzServerSideEncryption - key representing x-amz-server-side-encryption HTTP header applicable
// to PutObject API only.
S3XAmzServerSideEncryption = "s3:x-amz-server-side-encryption"
S3XAmzServerSideEncryption Key = "s3:x-amz-server-side-encryption"
// S3XAmzServerSideEncryptionCustomerAlgorithm - key representing
// x-amz-server-side-encryption-customer-algorithm HTTP header applicable to PutObject API only.
S3XAmzServerSideEncryptionCustomerAlgorithm = "s3:x-amz-server-side-encryption-customer-algorithm"
S3XAmzServerSideEncryptionCustomerAlgorithm Key = "s3:x-amz-server-side-encryption-customer-algorithm"
// S3XAmzMetadataDirective - key representing x-amz-metadata-directive HTTP header applicable to
// PutObject API only.
S3XAmzMetadataDirective = "s3:x-amz-metadata-directive"
S3XAmzMetadataDirective Key = "s3:x-amz-metadata-directive"
// S3XAmzStorageClass - key representing x-amz-storage-class HTTP header applicable to PutObject API
// only.
S3XAmzStorageClass = "s3:x-amz-storage-class"
S3XAmzStorageClass Key = "s3:x-amz-storage-class"
// S3LocationConstraint - key representing LocationConstraint XML tag of CreateBucket API only.
S3LocationConstraint = "s3:LocationConstraint"
S3LocationConstraint Key = "s3:LocationConstraint"
// S3Prefix - key representing prefix query parameter of ListBucket API only.
S3Prefix = "s3:prefix"
S3Prefix Key = "s3:prefix"
// S3Delimiter - key representing delimiter query parameter of ListBucket API only.
S3Delimiter = "s3:delimiter"
S3Delimiter Key = "s3:delimiter"
// S3MaxKeys - key representing max-keys query parameter of ListBucket API only.
S3MaxKeys = "s3:max-keys"
S3MaxKeys Key = "s3:max-keys"
// AWSReferer - key representing Referer header of any API.
AWSReferer = "aws:Referer"
AWSReferer Key = "aws:Referer"
// AWSSourceIP - key representing client's IP address (not intermittent proxies) of any API.
AWSSourceIP = "aws:SourceIp"
AWSSourceIP Key = "aws:SourceIp"
// AWSUserAgent - key representing UserAgent header for any API.
AWSUserAgent = "aws:UserAgent"
AWSUserAgent Key = "aws:UserAgent"
// AWSSecureTransport - key representing if the clients request is authenticated or not.
AWSSecureTransport = "aws:SecureTransport"
AWSSecureTransport Key = "aws:SecureTransport"
// AWSCurrentTime - key representing the current time.
AWSCurrentTime = "aws:CurrentTime"
AWSCurrentTime Key = "aws:CurrentTime"
// AWSEpochTime - key representing the current epoch time.
AWSEpochTime = "aws:EpochTime"
AWSEpochTime Key = "aws:EpochTime"
// AWSPrincipalType - user principal type currently supported values are "User" and "Anonymous".
AWSPrincipalType Key = "aws:principaltype"
// AWSUserID - user unique ID, in Minio this value is same as your user Access Key.
AWSUserID Key = "aws:userid"
// AWSUsername - user friendly name, in Minio this value is same as your user Access Key.
AWSUsername Key = "aws:username"
)
// AllSupportedKeys - is list of all all supported keys.
@@ -95,6 +104,9 @@ var AllSupportedKeys = []Key{
AWSSecureTransport,
AWSCurrentTime,
AWSEpochTime,
AWSPrincipalType,
AWSUserID,
AWSUsername,
// Add new supported condition keys.
}
@@ -106,6 +118,21 @@ var CommonKeys = []Key{
AWSSecureTransport,
AWSCurrentTime,
AWSEpochTime,
AWSPrincipalType,
AWSUserID,
AWSUsername,
}
func substFuncFromValues(values map[string][]string) func(string) string {
return func(v string) string {
for _, key := range CommonKeys {
// Empty values are not supported for policy variables.
if rvalues, ok := values[key.Name()]; ok && rvalues[0] != "" {
v = strings.Replace(v, key.VarName(), rvalues[0], -1)
}
}
return v
}
}
// IsValid - checks if key is valid or not.
@@ -128,6 +155,11 @@ func (key Key) MarshalJSON() ([]byte, error) {
return json.Marshal(string(key))
}
// VarName - returns variable key name, such as "${aws:username}"
func (key Key) VarName() string {
return fmt.Sprintf("${%s}", key)
}
// Name - returns key name which is stripped value of prefixes "aws:" and "s3:"
func (key Key) Name() string {
keyString := string(key)

View File

@@ -50,7 +50,8 @@ func (f stringEqualsFunc) evaluate(values map[string][]string) bool {
requestValue = values[f.k.Name()]
}
return !f.values.Intersection(set.CreateStringSet(requestValue...)).IsEmpty()
fvalues := f.values.ApplyFunc(substFuncFromValues(values))
return !fvalues.Intersection(set.CreateStringSet(requestValue...)).IsEmpty()
}
// key() - returns condition key which is used by this condition function.

View File

@@ -50,11 +50,14 @@ func (f stringEqualsIgnoreCaseFunc) evaluate(values map[string][]string) bool {
requestValue = values[f.k.Name()]
}
fvalues := f.values.ApplyFunc(substFuncFromValues(values))
for _, v := range requestValue {
if !f.values.FuncMatch(strings.EqualFold, v).IsEmpty() {
if !fvalues.FuncMatch(strings.EqualFold, v).IsEmpty() {
return true
}
}
return false
}

View File

@@ -51,8 +51,10 @@ func (f stringLikeFunc) evaluate(values map[string][]string) bool {
requestValue = values[f.k.Name()]
}
fvalues := f.values.ApplyFunc(substFuncFromValues(values))
for _, v := range requestValue {
if !f.values.FuncMatch(wildcard.Match, v).IsEmpty() {
if !fvalues.FuncMatch(wildcard.Match, v).IsEmpty() {
return true
}
}

View File

@@ -21,6 +21,7 @@ import (
"fmt"
"strings"
"github.com/minio/minio/pkg/policy/condition"
"github.com/minio/minio/pkg/wildcard"
)
@@ -47,8 +48,16 @@ func (r Resource) IsValid() bool {
}
// Match - matches object name with resource pattern.
func (r Resource) Match(resource string) bool {
return wildcard.Match(r.Pattern, resource)
func (r Resource) Match(resource string, conditionValues map[string][]string) bool {
pattern := r.Pattern
for _, key := range condition.CommonKeys {
// Empty values are not supported for policy variables.
if rvalues, ok := conditionValues[key.Name()]; ok && rvalues[0] != "" {
pattern = strings.Replace(pattern, key.VarName(), rvalues[0], -1)
}
}
return wildcard.Match(pattern, resource)
}
// MarshalJSON - encodes Resource to JSON data.

View File

@@ -124,7 +124,7 @@ func TestResourceMatch(t *testing.T) {
}
for i, testCase := range testCases {
result := testCase.resource.Match(testCase.objectName)
result := testCase.resource.Match(testCase.objectName, nil)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)

View File

@@ -81,9 +81,9 @@ func (resourceSet ResourceSet) MarshalJSON() ([]byte, error) {
}
// Match - matches object name with anyone of resource pattern in resource set.
func (resourceSet ResourceSet) Match(resource string) bool {
func (resourceSet ResourceSet) Match(resource string, conditionValues map[string][]string) bool {
for r := range resourceSet {
if r.Match(resource) {
if r.Match(resource, conditionValues) {
return true
}
}

View File

@@ -179,7 +179,7 @@ func TestResourceSetMatch(t *testing.T) {
}
for i, testCase := range testCases {
result := testCase.resourceSet.Match(testCase.resource)
result := testCase.resourceSet.Match(testCase.resource, nil)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)

View File

@@ -54,7 +54,7 @@ func (statement Statement) IsAllowed(args Args) bool {
resource += args.ObjectName
}
if !statement.Resources.Match(resource) {
if !statement.Resources.Match(resource, args.ConditionValues) {
return false
}