Skip to content

Commit

Permalink
Gov authorization propagation for sub-messages (#1482)
Browse files Browse the repository at this point in the history
* Add gov authorization propagation for sub-messages

* Minor update
  • Loading branch information
alpe authored Jul 6, 2023
1 parent 1763477 commit 63f73d3
Show file tree
Hide file tree
Showing 14 changed files with 620 additions and 109 deletions.
101 changes: 81 additions & 20 deletions x/wasm/keeper/authz_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,11 @@ import (
"github.com/CosmWasm/wasmd/x/wasm/types"
)

// ChainAccessConfigs chain settings
type ChainAccessConfigs struct {
Upload types.AccessConfig
Instantiate types.AccessConfig
}

// NewChainAccessConfigs constructor
func NewChainAccessConfigs(upload types.AccessConfig, instantiate types.AccessConfig) ChainAccessConfigs {
return ChainAccessConfigs{Upload: upload, Instantiate: instantiate}
}

type AuthorizationPolicy interface {
CanCreateCode(chainConfigs ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool
CanInstantiateContract(c types.AccessConfig, actor sdk.AccAddress) bool
CanModifyContract(admin, actor sdk.AccAddress) bool
CanModifyCodeAccessConfig(creator, actor sdk.AccAddress, isSubset bool) bool
}
var _ types.AuthorizationPolicy = DefaultAuthorizationPolicy{}

type DefaultAuthorizationPolicy struct{}

func (p DefaultAuthorizationPolicy) CanCreateCode(chainConfigs ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool {
func (p DefaultAuthorizationPolicy) CanCreateCode(chainConfigs types.ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool {
return chainConfigs.Upload.Allowed(actor) &&
contractConfig.IsSubset(chainConfigs.Instantiate)
}
Expand All @@ -43,10 +27,35 @@ func (p DefaultAuthorizationPolicy) CanModifyCodeAccessConfig(creator, actor sdk
return creator != nil && creator.Equals(actor) && isSubset
}

type GovAuthorizationPolicy struct{}
// SubMessageAuthorizationPolicy always returns the default policy
func (p DefaultAuthorizationPolicy) SubMessageAuthorizationPolicy(_ types.AuthorizationPolicyAction) types.AuthorizationPolicy {
return p
}

var _ types.AuthorizationPolicy = GovAuthorizationPolicy{}

type GovAuthorizationPolicy struct {
propagate map[types.AuthorizationPolicyAction]struct{}
}

// NewGovAuthorizationPolicy public constructor
func NewGovAuthorizationPolicy(actions ...types.AuthorizationPolicyAction) types.AuthorizationPolicy {
propagate := make(map[types.AuthorizationPolicyAction]struct{}, len(actions))
for _, a := range actions {
propagate[a] = struct{}{}
}
return newGovAuthorizationPolicy(propagate)
}

// newGovAuthorizationPolicy internal constructor
func newGovAuthorizationPolicy(propagate map[types.AuthorizationPolicyAction]struct{}) types.AuthorizationPolicy {
return GovAuthorizationPolicy{
propagate: propagate,
}
}

// CanCreateCode implements AuthorizationPolicy.CanCreateCode to allow gov actions. Always returns true.
func (p GovAuthorizationPolicy) CanCreateCode(ChainAccessConfigs, sdk.AccAddress, types.AccessConfig) bool {
func (p GovAuthorizationPolicy) CanCreateCode(types.ChainAccessConfigs, sdk.AccAddress, types.AccessConfig) bool {
return true
}

Expand All @@ -61,3 +70,55 @@ func (p GovAuthorizationPolicy) CanModifyContract(sdk.AccAddress, sdk.AccAddress
func (p GovAuthorizationPolicy) CanModifyCodeAccessConfig(sdk.AccAddress, sdk.AccAddress, bool) bool {
return true
}

// SubMessageAuthorizationPolicy returns new policy with fine-grained gov permission for given action only
func (p GovAuthorizationPolicy) SubMessageAuthorizationPolicy(action types.AuthorizationPolicyAction) types.AuthorizationPolicy {
defaultPolicy := DefaultAuthorizationPolicy{}
if p.propagate != nil && len(p.propagate) != 0 {
if _, ok := p.propagate[action]; ok {
return NewPartialGovAuthorizationPolicy(defaultPolicy, action)
}
}
return defaultPolicy
}

var _ types.AuthorizationPolicy = PartialGovAuthorizationPolicy{}

// PartialGovAuthorizationPolicy decorates the given default policy to add fine-grained gov permissions
// to the defined action
type PartialGovAuthorizationPolicy struct {
action types.AuthorizationPolicyAction
defaultPolicy types.AuthorizationPolicy
}

// NewPartialGovAuthorizationPolicy constructor
func NewPartialGovAuthorizationPolicy(defaultPolicy types.AuthorizationPolicy, entrypoint types.AuthorizationPolicyAction) PartialGovAuthorizationPolicy {
return PartialGovAuthorizationPolicy{action: entrypoint, defaultPolicy: defaultPolicy}
}

func (p PartialGovAuthorizationPolicy) CanCreateCode(chainConfigs types.ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool {
return p.defaultPolicy.CanCreateCode(chainConfigs, actor, contractConfig)
}

func (p PartialGovAuthorizationPolicy) CanInstantiateContract(c types.AccessConfig, actor sdk.AccAddress) bool {
if p.action == types.AuthZActionInstantiate {
return true
}
return p.defaultPolicy.CanInstantiateContract(c, actor)
}

func (p PartialGovAuthorizationPolicy) CanModifyContract(admin, actor sdk.AccAddress) bool {
if p.action == types.AuthZActionMigrateContract {
return true
}
return p.defaultPolicy.CanModifyContract(admin, actor)
}

func (p PartialGovAuthorizationPolicy) CanModifyCodeAccessConfig(creator, actor sdk.AccAddress, isSubset bool) bool {
return p.defaultPolicy.CanModifyCodeAccessConfig(creator, actor, isSubset)
}

// SubMessageAuthorizationPolicy always returns self
func (p PartialGovAuthorizationPolicy) SubMessageAuthorizationPolicy(_ types.AuthorizationPolicyAction) types.AuthorizationPolicy {
return p
}
172 changes: 155 additions & 17 deletions x/wasm/keeper/authz_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,44 @@ func TestDefaultAuthzPolicyCanCreateCode(t *testing.T) {
myActorAddress := RandomAccountAddress(t)
otherAddress := RandomAccountAddress(t)
specs := map[string]struct {
chainConfigs ChainAccessConfigs
chainConfigs types.ChainAccessConfigs
contractInstConf types.AccessConfig
actor sdk.AccAddress
exp bool
panics bool
}{
"upload nobody": {
chainConfigs: NewChainAccessConfigs(types.AllowNobody, types.AllowEverybody),
chainConfigs: types.NewChainAccessConfigs(types.AllowNobody, types.AllowEverybody),
contractInstConf: types.AllowEverybody,
exp: false,
},
"upload everybody": {
chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody),
chainConfigs: types.NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody),
contractInstConf: types.AllowEverybody,
exp: true,
},
"upload any address - included": {
chainConfigs: NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress), types.AllowEverybody),
chainConfigs: types.NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress), types.AllowEverybody),
contractInstConf: types.AllowEverybody,
exp: true,
},
"upload any address - not included": {
chainConfigs: NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress), types.AllowEverybody),
chainConfigs: types.NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress), types.AllowEverybody),
contractInstConf: types.AllowEverybody,
exp: false,
},
"contract config - subtype": {
chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody),
chainConfigs: types.NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody),
contractInstConf: types.AccessTypeAnyOfAddresses.With(myActorAddress),
exp: true,
},
"contract config - not subtype": {
chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowNobody),
chainConfigs: types.NewChainAccessConfigs(types.AllowEverybody, types.AllowNobody),
contractInstConf: types.AllowEverybody,
exp: false,
},
"upload undefined config - panics": {
chainConfigs: NewChainAccessConfigs(types.AccessConfig{}, types.AllowEverybody),
chainConfigs: types.NewChainAccessConfigs(types.AccessConfig{}, types.AllowEverybody),
contractInstConf: types.AllowEverybody,
panics: true,
},
Expand Down Expand Up @@ -180,40 +180,48 @@ func TestDefaultAuthzPolicyCanModifyCodeAccessConfig(t *testing.T) {
}
}

func TestDefaultAuthzPolicySubMessageAuthorizationPolicy(t *testing.T) {
policy := DefaultAuthorizationPolicy{}
for _, v := range []types.AuthorizationPolicyAction{types.AuthZActionInstantiate, types.AuthZActionMigrateContract} {
got := policy.SubMessageAuthorizationPolicy(v)
assert.Equal(t, policy, got)
}
}

func TestGovAuthzPolicyCanCreateCode(t *testing.T) {
myActorAddress := RandomAccountAddress(t)
otherAddress := RandomAccountAddress(t)
specs := map[string]struct {
chainConfigs ChainAccessConfigs
chainConfigs types.ChainAccessConfigs
contractInstConf types.AccessConfig
actor sdk.AccAddress
}{
"upload nobody": {
chainConfigs: NewChainAccessConfigs(types.AllowNobody, types.AllowEverybody),
chainConfigs: types.NewChainAccessConfigs(types.AllowNobody, types.AllowEverybody),
contractInstConf: types.AllowEverybody,
},
"upload everybody": {
chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody),
chainConfigs: types.NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody),
contractInstConf: types.AllowEverybody,
},
"upload any address - included": {
chainConfigs: NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress), types.AllowEverybody),
chainConfigs: types.NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress), types.AllowEverybody),
contractInstConf: types.AllowEverybody,
},
"upload any address - not included": {
chainConfigs: NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress), types.AllowEverybody),
chainConfigs: types.NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress), types.AllowEverybody),
contractInstConf: types.AllowEverybody,
},
"contract config - subtype": {
chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody),
chainConfigs: types.NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody),
contractInstConf: types.AccessTypeAnyOfAddresses.With(myActorAddress),
},
"contract config - not subtype": {
chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowNobody),
chainConfigs: types.NewChainAccessConfigs(types.AllowEverybody, types.AllowNobody),
contractInstConf: types.AllowEverybody,
},
"upload undefined config - not panics": {
chainConfigs: NewChainAccessConfigs(types.AccessConfig{}, types.AllowEverybody),
chainConfigs: types.NewChainAccessConfigs(types.AccessConfig{}, types.AllowEverybody),
contractInstConf: types.AllowEverybody,
},
}
Expand Down Expand Up @@ -305,9 +313,139 @@ func TestGovAuthzPolicyCanModifyCodeAccessConfig(t *testing.T) {
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
policy := GovAuthorizationPolicy{}
policy := newGovAuthorizationPolicy(nil)
got := policy.CanModifyCodeAccessConfig(spec.admin, myActorAddress, spec.subset)
assert.True(t, got)
})
}
}

func TestGovAuthorizationPolicySubMessageAuthorizationPolicy(t *testing.T) {
specs := map[string]struct {
propagate map[types.AuthorizationPolicyAction]struct{}
entrypoint types.AuthorizationPolicyAction
exp types.AuthorizationPolicy
}{
"non propagating": {
exp: DefaultAuthorizationPolicy{},
},
"propagating with matching action": {
propagate: map[types.AuthorizationPolicyAction]struct{}{
types.AuthZActionMigrateContract: {},
},
entrypoint: types.AuthZActionMigrateContract,
exp: NewPartialGovAuthorizationPolicy(DefaultAuthorizationPolicy{}, types.AuthZActionMigrateContract),
},
"propagating for non matching action": {
propagate: map[types.AuthorizationPolicyAction]struct{}{
types.AuthZActionMigrateContract: {},
},
entrypoint: types.AuthZActionInstantiate,
exp: DefaultAuthorizationPolicy{},
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got := newGovAuthorizationPolicy(spec.propagate).SubMessageAuthorizationPolicy(spec.entrypoint)
assert.Equal(t, spec.exp, got)
})
}
}

func TestPartialGovAuthorizationPolicyCanInstantiateContract(t *testing.T) {
specs := map[string]struct {
allowedAction types.AuthorizationPolicyAction
exp bool
}{
"instantiation granted": {
allowedAction: types.AuthZActionInstantiate,
exp: true,
},
"decorated policy when instantiation not granted ": {
allowedAction: types.AuthZActionMigrateContract,
exp: false,
},
"decorated policy when nothing set": {
exp: false,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
policy := NewPartialGovAuthorizationPolicy(AlwaysRejectTestAuthZPolicy{}, spec.allowedAction)
got := policy.CanInstantiateContract(types.AccessConfig{}, nil)
assert.Equal(t, spec.exp, got)
})
}
}

func TestPartialGovAuthorizationPolicyCanModifyContract(t *testing.T) {
specs := map[string]struct {
allowedAction types.AuthorizationPolicyAction
exp bool
}{
"migration granted": {
allowedAction: types.AuthZActionMigrateContract,
exp: true,
},
"decorated policy when migration not granted ": {
allowedAction: types.AuthZActionInstantiate,
exp: false,
},
"decorated policy when nothing set": {
exp: false,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
policy := NewPartialGovAuthorizationPolicy(AlwaysRejectTestAuthZPolicy{}, spec.allowedAction)
got := policy.CanModifyContract(nil, nil)
assert.Equal(t, spec.exp, got)
})
}
}

func TestPartialGovAuthorizationPolicyDelegatedOnly(t *testing.T) {
for _, v := range []types.AuthorizationPolicy{AlwaysRejectTestAuthZPolicy{}, NewGovAuthorizationPolicy()} {
policy := NewPartialGovAuthorizationPolicy(v, types.AuthZActionInstantiate)

got := policy.CanCreateCode(types.ChainAccessConfigs{}, nil, types.AccessConfig{})
exp := v.CanCreateCode(types.ChainAccessConfigs{}, nil, types.AccessConfig{})
assert.Equal(t, exp, got)

got = policy.CanModifyCodeAccessConfig(nil, nil, false)
exp = v.CanModifyCodeAccessConfig(nil, nil, false)
assert.Equal(t, exp, got)
}
}

func TestPartialGovAuthorizationPolicySubMessageAuthorizationPolicy(t *testing.T) {
policy := NewPartialGovAuthorizationPolicy(DefaultAuthorizationPolicy{}, types.AuthZActionInstantiate)
for _, v := range []types.AuthorizationPolicyAction{types.AuthZActionInstantiate, types.AuthZActionMigrateContract} {
got := policy.SubMessageAuthorizationPolicy(v)
assert.Equal(t, policy, got)
}
}

var _ types.AuthorizationPolicy = AlwaysRejectTestAuthZPolicy{}

type AlwaysRejectTestAuthZPolicy struct{}

func (a AlwaysRejectTestAuthZPolicy) CanCreateCode(chainConfigs types.ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool {
return false
}

func (a AlwaysRejectTestAuthZPolicy) CanInstantiateContract(c types.AccessConfig, actor sdk.AccAddress) bool {
return false
}

func (a AlwaysRejectTestAuthZPolicy) CanModifyContract(admin, actor sdk.AccAddress) bool {
return false
}

func (a AlwaysRejectTestAuthZPolicy) CanModifyCodeAccessConfig(creator, actor sdk.AccAddress, isSubset bool) bool {
return false
}

func (a AlwaysRejectTestAuthZPolicy) SubMessageAuthorizationPolicy(entrypoint types.AuthorizationPolicyAction) types.AuthorizationPolicy {
return a
}
Loading

0 comments on commit 63f73d3

Please sign in to comment.