mirror of https://github.com/minio/minio.git
policy: Add Merge API (#11793)
This commit adds a new API in pkg/bucket/policy package called Merge to merge multiple policies of a user or a group into one policy document.
This commit is contained in:
parent
6160188bf3
commit
fa94682e83
|
@ -93,10 +93,14 @@ func (actionSet ActionSet) ToSlice() []Action {
|
||||||
for action := range actionSet {
|
for action := range actionSet {
|
||||||
actions = append(actions, action)
|
actions = append(actions, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone clones ActionSet structure
|
||||||
|
func (actionSet ActionSet) Clone() ActionSet {
|
||||||
|
return NewActionSet(actionSet.ToSlice()...)
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalJSON - decodes JSON data to ActionSet.
|
// UnmarshalJSON - decodes JSON data to ActionSet.
|
||||||
func (actionSet *ActionSet) UnmarshalJSON(data []byte) error {
|
func (actionSet *ActionSet) UnmarshalJSON(data []byte) error {
|
||||||
var sset set.StringSet
|
var sset set.StringSet
|
||||||
|
|
|
@ -66,6 +66,42 @@ func (functions Functions) Keys() KeySet {
|
||||||
return keySet
|
return keySet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone clones Functions structure
|
||||||
|
func (functions Functions) Clone() Functions {
|
||||||
|
funcs := []Function{}
|
||||||
|
|
||||||
|
for _, f := range functions {
|
||||||
|
vfn := conditionFuncMap[f.name()]
|
||||||
|
for key, values := range f.toMap() {
|
||||||
|
function, _ := vfn(key, values.Clone())
|
||||||
|
funcs = append(funcs, function)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return funcs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns true if two Functions structures are equal
|
||||||
|
func (functions Functions) Equals(funcs Functions) bool {
|
||||||
|
if len(functions) != len(funcs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, fi := range functions {
|
||||||
|
fistr := fi.String()
|
||||||
|
found := false
|
||||||
|
for _, fj := range funcs {
|
||||||
|
if fistr == fj.String() {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalJSON - encodes Functions to JSON data.
|
// MarshalJSON - encodes Functions to JSON data.
|
||||||
func (functions Functions) MarshalJSON() ([]byte, error) {
|
func (functions Functions) MarshalJSON() ([]byte, error) {
|
||||||
nm := make(map[name]map[Key]ValueSet)
|
nm := make(map[name]map[Key]ValueSet)
|
||||||
|
|
|
@ -29,6 +29,15 @@ func (set ValueSet) Add(value Value) {
|
||||||
set[value] = struct{}{}
|
set[value] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToSlice converts ValueSet to a slice of Value
|
||||||
|
func (set ValueSet) ToSlice() []Value {
|
||||||
|
var values []Value
|
||||||
|
for k := range set {
|
||||||
|
values = append(values, k)
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalJSON - encodes ValueSet to JSON data.
|
// MarshalJSON - encodes ValueSet to JSON data.
|
||||||
func (set ValueSet) MarshalJSON() ([]byte, error) {
|
func (set ValueSet) MarshalJSON() ([]byte, error) {
|
||||||
var values []Value
|
var values []Value
|
||||||
|
@ -73,6 +82,11 @@ func (set *ValueSet) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone clones ValueSet structure
|
||||||
|
func (set ValueSet) Clone() ValueSet {
|
||||||
|
return NewValueSet(set.ToSlice()...)
|
||||||
|
}
|
||||||
|
|
||||||
// NewValueSet - returns new value set containing given values.
|
// NewValueSet - returns new value set containing given values.
|
||||||
func NewValueSet(values ...Value) ValueSet {
|
func NewValueSet(values ...Value) ValueSet {
|
||||||
set := make(ValueSet)
|
set := make(ValueSet)
|
||||||
|
|
|
@ -100,27 +100,30 @@ func (policy Policy) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(subPolicy(policy))
|
return json.Marshal(subPolicy(policy))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge merges two policies documents and drop
|
||||||
|
// duplicate statements if any.
|
||||||
|
func (policy Policy) Merge(input Policy) Policy {
|
||||||
|
var mergedPolicy Policy
|
||||||
|
if policy.Version != "" {
|
||||||
|
mergedPolicy.Version = policy.Version
|
||||||
|
} else {
|
||||||
|
mergedPolicy.Version = input.Version
|
||||||
|
}
|
||||||
|
for _, st := range policy.Statements {
|
||||||
|
mergedPolicy.Statements = append(mergedPolicy.Statements, st.Clone())
|
||||||
|
}
|
||||||
|
for _, st := range input.Statements {
|
||||||
|
mergedPolicy.Statements = append(mergedPolicy.Statements, st.Clone())
|
||||||
|
}
|
||||||
|
mergedPolicy.dropDuplicateStatements()
|
||||||
|
return mergedPolicy
|
||||||
|
}
|
||||||
|
|
||||||
func (policy *Policy) dropDuplicateStatements() {
|
func (policy *Policy) dropDuplicateStatements() {
|
||||||
redo:
|
redo:
|
||||||
for i := range policy.Statements {
|
for i := range policy.Statements {
|
||||||
for j, statement := range policy.Statements[i+1:] {
|
for j, statement := range policy.Statements[i+1:] {
|
||||||
if policy.Statements[i].Effect != statement.Effect {
|
if !policy.Statements[i].Equals(statement) {
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !policy.Statements[i].Principal.Equals(statement.Principal) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !policy.Statements[i].Actions.Equals(statement.Actions) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !policy.Statements[i].Resources.Equals(statement.Resources) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if policy.Statements[i].Conditions.String() != statement.Conditions.String() {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
policy.Statements = append(policy.Statements[:j], policy.Statements[j+1:]...)
|
policy.Statements = append(policy.Statements[:j], policy.Statements[j+1:]...)
|
||||||
|
|
|
@ -1144,3 +1144,97 @@ func TestPolicyValidate(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPolicyMerge(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
policy string
|
||||||
|
}{
|
||||||
|
{`{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Id": "S3PolicyId1",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Sid": "statement1",
|
||||||
|
"Effect": "Deny",
|
||||||
|
"Principal": "*",
|
||||||
|
"Action":["s3:GetObject", "s3:PutObject"],
|
||||||
|
"Resource": "arn:aws:s3:::awsexamplebucket1/*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`},
|
||||||
|
{`{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Id": "S3PolicyId1",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Sid": "statement1",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": "*",
|
||||||
|
"Action":"s3:GetObject",
|
||||||
|
"Resource": "arn:aws:s3:::awsexamplebucket1/*",
|
||||||
|
"Condition" : {
|
||||||
|
"IpAddress" : {
|
||||||
|
"aws:SourceIp": "192.0.2.0/24"
|
||||||
|
},
|
||||||
|
"NotIpAddress" : {
|
||||||
|
"aws:SourceIp": "192.0.2.188/32"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`},
|
||||||
|
{`{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Sid": "cross-account permission to user in your own account",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": "arn:aws:iam::123456789012:user/Dave"
|
||||||
|
},
|
||||||
|
"Action": "s3:PutObject",
|
||||||
|
"Resource": "arn:aws:s3:::awsexamplebucket1/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sid": "Deny your user permission to upload object if copy source is not /bucket/folder",
|
||||||
|
"Effect": "Deny",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": "arn:aws:iam::123456789012:user/Dave"
|
||||||
|
},
|
||||||
|
"Action": "s3:PutObject",
|
||||||
|
"Resource": "arn:aws:s3:::awsexamplebucket1/*",
|
||||||
|
"Condition": {
|
||||||
|
"StringNotLike": {
|
||||||
|
"s3:x-amz-copy-source": "awsexamplebucket1/public/*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, testCase := range testCases {
|
||||||
|
var p Policy
|
||||||
|
err := json.Unmarshal([]byte(testCase.policy), &p)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %v: unexpected error: %v", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var clonedPolicy Policy
|
||||||
|
clonedPolicy = clonedPolicy.Merge(p)
|
||||||
|
|
||||||
|
j, err := json.Marshal(clonedPolicy)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %v: unexpected error: %v", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(j, &clonedPolicy)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %v: unexpected error: %v", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !clonedPolicy.Statements[0].Equals(p.Statements[0]) {
|
||||||
|
t.Fatalf("case %v: different policy outcome", i+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -90,6 +90,12 @@ func (p *Principal) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone clones Principal structure
|
||||||
|
func (p Principal) Clone() Principal {
|
||||||
|
return NewPrincipal(p.AWS.ToSlice()...)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// NewPrincipal - creates new Principal.
|
// NewPrincipal - creates new Principal.
|
||||||
func NewPrincipal(principals ...string) Principal {
|
func NewPrincipal(principals ...string) Principal {
|
||||||
return Principal{AWS: set.CreateStringSet(principals...)}
|
return Principal{AWS: set.CreateStringSet(principals...)}
|
||||||
|
|
|
@ -154,6 +154,21 @@ func (resourceSet ResourceSet) Validate(bucketName string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToSlice - returns slice of resources from the resource set.
|
||||||
|
func (resourceSet ResourceSet) ToSlice() []Resource {
|
||||||
|
resources := []Resource{}
|
||||||
|
for resource := range resourceSet {
|
||||||
|
resources = append(resources, resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone clones ResourceSet structure
|
||||||
|
func (resourceSet ResourceSet) Clone() ResourceSet {
|
||||||
|
return NewResourceSet(resourceSet.ToSlice()...)
|
||||||
|
}
|
||||||
|
|
||||||
// NewResourceSet - creates new resource set.
|
// NewResourceSet - creates new resource set.
|
||||||
func NewResourceSet(resources ...Resource) ResourceSet {
|
func NewResourceSet(resources ...Resource) ResourceSet {
|
||||||
resourceSet := make(ResourceSet)
|
resourceSet := make(ResourceSet)
|
||||||
|
|
|
@ -33,6 +33,26 @@ type Statement struct {
|
||||||
Conditions condition.Functions `json:"Condition,omitempty"`
|
Conditions condition.Functions `json:"Condition,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equals checks if two statements are equal
|
||||||
|
func (statement Statement) Equals(st Statement) bool {
|
||||||
|
if statement.Effect != st.Effect {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !statement.Principal.Equals(st.Principal) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !statement.Actions.Equals(st.Actions) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !statement.Resources.Equals(st.Resources) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !statement.Conditions.Equals(st.Conditions) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
||||||
func (statement Statement) IsAllowed(args Args) bool {
|
func (statement Statement) IsAllowed(args Args) bool {
|
||||||
check := func() bool {
|
check := func() bool {
|
||||||
|
@ -143,6 +163,12 @@ func (statement Statement) Validate(bucketName string) error {
|
||||||
return statement.Resources.Validate(bucketName)
|
return statement.Resources.Validate(bucketName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone clones Statement structure
|
||||||
|
func (statement Statement) Clone() Statement {
|
||||||
|
return NewStatement(statement.Effect, statement.Principal.Clone(),
|
||||||
|
statement.Actions.Clone(), statement.Resources.Clone(), statement.Conditions.Clone())
|
||||||
|
}
|
||||||
|
|
||||||
// NewStatement - creates new statement.
|
// NewStatement - creates new statement.
|
||||||
func NewStatement(effect Effect, principal Principal, actionSet ActionSet, resourceSet ResourceSet, conditions condition.Functions) Statement {
|
func NewStatement(effect Effect, principal Principal, actionSet ActionSet, resourceSet ResourceSet, conditions condition.Functions) Statement {
|
||||||
return Statement{
|
return Statement{
|
||||||
|
|
|
@ -27,6 +27,11 @@ import (
|
||||||
// ActionSet - set of actions.
|
// ActionSet - set of actions.
|
||||||
type ActionSet map[Action]struct{}
|
type ActionSet map[Action]struct{}
|
||||||
|
|
||||||
|
// Clone clones ActionSet structure
|
||||||
|
func (actionSet ActionSet) Clone() ActionSet {
|
||||||
|
return NewActionSet(actionSet.ToSlice()...)
|
||||||
|
}
|
||||||
|
|
||||||
// Add - add action to the set.
|
// Add - add action to the set.
|
||||||
func (actionSet ActionSet) Add(action Action) {
|
func (actionSet ActionSet) Add(action Action) {
|
||||||
actionSet[action] = struct{}{}
|
actionSet[action] = struct{}{}
|
||||||
|
|
|
@ -151,23 +151,30 @@ func (iamp Policy) isValid() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge merges two policies documents and drop
|
||||||
|
// duplicate statements if any.
|
||||||
|
func (iamp Policy) Merge(input Policy) Policy {
|
||||||
|
var mergedPolicy Policy
|
||||||
|
if iamp.Version != "" {
|
||||||
|
mergedPolicy.Version = iamp.Version
|
||||||
|
} else {
|
||||||
|
mergedPolicy.Version = input.Version
|
||||||
|
}
|
||||||
|
for _, st := range iamp.Statements {
|
||||||
|
mergedPolicy.Statements = append(mergedPolicy.Statements, st.Clone())
|
||||||
|
}
|
||||||
|
for _, st := range input.Statements {
|
||||||
|
mergedPolicy.Statements = append(mergedPolicy.Statements, st.Clone())
|
||||||
|
}
|
||||||
|
mergedPolicy.dropDuplicateStatements()
|
||||||
|
return mergedPolicy
|
||||||
|
}
|
||||||
|
|
||||||
func (iamp *Policy) dropDuplicateStatements() {
|
func (iamp *Policy) dropDuplicateStatements() {
|
||||||
redo:
|
redo:
|
||||||
for i := range iamp.Statements {
|
for i := range iamp.Statements {
|
||||||
for j, statement := range iamp.Statements[i+1:] {
|
for j, statement := range iamp.Statements[i+1:] {
|
||||||
if iamp.Statements[i].Effect != statement.Effect {
|
if !iamp.Statements[i].Equals(statement) {
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !iamp.Statements[i].Actions.Equals(statement.Actions) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !iamp.Statements[i].Resources.Equals(statement.Resources) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if iamp.Statements[i].Conditions.String() != statement.Conditions.String() {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
iamp.Statements = append(iamp.Statements[:j], iamp.Statements[j+1:]...)
|
iamp.Statements = append(iamp.Statements[:j], iamp.Statements[j+1:]...)
|
||||||
|
|
|
@ -154,6 +154,21 @@ func (resourceSet ResourceSet) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToSlice - returns slice of resources from the resource set.
|
||||||
|
func (resourceSet ResourceSet) ToSlice() []Resource {
|
||||||
|
resources := []Resource{}
|
||||||
|
for resource := range resourceSet {
|
||||||
|
resources = append(resources, resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone clones ResourceSet structure
|
||||||
|
func (resourceSet ResourceSet) Clone() ResourceSet {
|
||||||
|
return NewResourceSet(resourceSet.ToSlice()...)
|
||||||
|
}
|
||||||
|
|
||||||
// NewResourceSet - creates new resource set.
|
// NewResourceSet - creates new resource set.
|
||||||
func NewResourceSet(resources ...Resource) ResourceSet {
|
func NewResourceSet(resources ...Resource) ResourceSet {
|
||||||
resourceSet := make(ResourceSet)
|
resourceSet := make(ResourceSet)
|
||||||
|
|
|
@ -129,6 +129,29 @@ func (statement Statement) Validate() error {
|
||||||
return statement.isValid()
|
return statement.isValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equals checks if two statements are equal
|
||||||
|
func (statement Statement) Equals(st Statement) bool {
|
||||||
|
if statement.Effect != st.Effect {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !statement.Actions.Equals(st.Actions) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !statement.Resources.Equals(st.Resources) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !statement.Conditions.Equals(st.Conditions) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone clones Statement structure
|
||||||
|
func (statement Statement) Clone() Statement {
|
||||||
|
return NewStatement(statement.Effect, statement.Actions.Clone(),
|
||||||
|
statement.Resources.Clone(), statement.Conditions.Clone())
|
||||||
|
}
|
||||||
|
|
||||||
// NewStatement - creates new statement.
|
// NewStatement - creates new statement.
|
||||||
func NewStatement(effect policy.Effect, actionSet ActionSet, resourceSet ResourceSet, conditions condition.Functions) Statement {
|
func NewStatement(effect policy.Effect, actionSet ActionSet, resourceSet ResourceSet, conditions condition.Functions) Statement {
|
||||||
return Statement{
|
return Statement{
|
||||||
|
|
Loading…
Reference in New Issue