Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gov authorization propagation for sub-messages #1482

Merged
merged 2 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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