Skip to content

Commit

Permalink
Allow configuration of per-provider publishers policy (#2526)
Browse files Browse the repository at this point in the history
* Allow configuration of per-provider publishers policy

This allows each provider to have its own policy for which publishers are allowed to publish advertisements on behalf of that provider. The previous publisher policy serves as the default policy when there is no policy configured for a specific provider.

* Allow any publisher for new provider by default
  • Loading branch information
gammazero authored Feb 20, 2024
1 parent 1a69247 commit e2e3e62
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 14 deletions.
25 changes: 18 additions & 7 deletions config/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,27 @@ type Policy struct {
// in Except. in other words, Allow=true means that Except is a deny-list
// and Allow=false means that Except is an allow-list.
Except []string

// Publish determines whether or not peers are allowed to publish
// Publish is the default Allow policy when a provider has no policy in
// PublisherForProviders. It determines if any peers are allowed to publish
// advertisements for a provider with a differen peer ID.
Publish bool
// PublisherExcept is a list of peer IDs that are exceptions to the Publish
// policy. If Publish is false, then all allowed peers cannot publish
// advertisements for providers with a different peer ID, unless listed in
// PublishExcept. If Publish is true, then all allowed peers can publish
// advertisements for any provider, unless listed in PublishExcept.
// PublisherExcept is a list of peer IDs that are exceptions to the default
// Publish policy.
PublishExcept []string
// PublishersForProvider is a list of policies specifying which publishers
// are allowed to publish advertisements on behalf of a specified provider.
PublishersForProvider []PublishersPolicy
}

type PublishersPolicy struct {
// Provider is the provider peer ID this policy pertains to.
Provider string
// Allow determines whether a peer is allowed or blocked from publishing
// advertisements on behalf of a provider with a differen peer ID.
Allow bool
// Except is a list of peer IDs that are exceptions to the Allow
// policy.
Except []string
}

// NewPolicy returns Policy with values set to their defaults.
Expand Down
43 changes: 37 additions & 6 deletions internal/registry/policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
type Policy struct {
allow peerutil.Policy
publish peerutil.Policy
rwmutex sync.RWMutex
// publishForProvider contains a publish policy for a specific provider.
publishForProvider map[peer.ID]peerutil.Policy
rwmutex sync.RWMutex
}

func New(cfg config.Policy) (*Policy, error) {
Expand All @@ -26,9 +28,26 @@ func New(cfg config.Policy) (*Policy, error) {
return nil, fmt.Errorf("bad publish policy: %s", err)
}

var pubForProvider map[peer.ID]peerutil.Policy
if len(cfg.PublishersForProvider) != 0 {
pubForProvider = make(map[peer.ID]peerutil.Policy, len(cfg.PublishersForProvider))
for i, pubPolicyCfg := range cfg.PublishersForProvider {
providerID, err := peer.Decode(pubPolicyCfg.Provider)
if err != nil {
return nil, fmt.Errorf("error decoding provider id in publisher policy %d: %s", i+1, err)
}
pubPolicy, err := peerutil.NewPolicyStrings(pubPolicyCfg.Allow, pubPolicyCfg.Except)
if err != nil {
return nil, fmt.Errorf("bad publisher policy for provider %s: %s", pubPolicyCfg.Provider, err)
}
pubForProvider[providerID] = pubPolicy
}
}

return &Policy{
allow: allow,
publish: publish,
allow: allow,
publish: publish,
publishForProvider: pubForProvider,
}, nil
}

Expand All @@ -54,7 +73,13 @@ func (p *Policy) PublishAllowed(publisherID, providerID peer.ID) bool {
if !p.allow.Eval(providerID) {
return false
}
return p.publish.Eval(publisherID)

pubPolicy, found := p.publishForProvider[providerID]
if !found {
// Use default publish policy.
pubPolicy = p.publish
}
return pubPolicy.Eval(publisherID)
}

// Allow alters the policy to allow the specified peer. Returns true if the
Expand Down Expand Up @@ -93,8 +118,14 @@ func (p *Policy) Copy(other *Policy) {
defer p.rwmutex.Unlock()

other.rwmutex.RLock()
p.allow = other.allow
p.publish = other.publish
p.allow = peerutil.NewPolicy(other.allow.Default(), other.allow.Except()...)
p.publish = peerutil.NewPolicy(other.publish.Default(), other.publish.Except()...)
if len(other.publishForProvider) != 0 {
p.publishForProvider = make(map[peer.ID]peerutil.Policy, len(other.publishForProvider))
for k, v := range other.publishForProvider {
p.publishForProvider[k] = peerutil.NewPolicy(v.Default(), v.Except()...)
}
}
other.rwmutex.RUnlock()
}

Expand Down
56 changes: 56 additions & 0 deletions internal/registry/policy/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,59 @@ func TestPolicyAccess(t *testing.T) {
require.NoError(t, err)
require.True(t, p.NoneAllowed(), "expected inaccessible policy")
}

func TestPublishersForProvider(t *testing.T) {
prov1Str := "12D3KooWPMGfQs5CaJKG4yCxVWizWBRtB85gEUwiX2ekStvYvqgp"
prov1, err := peer.Decode(prov1Str)
if err != nil {
panic(err)
}
prov2Str := "12D3KooWNH73Z4oxej8u6NzYswQxH7Cd92U2aUYUsxX9mKr5SMuj"
prov2, err := peer.Decode(prov2Str)
if err != nil {
panic(err)
}

policyCfg := config.Policy{
Allow: true,
Publish: false,
PublishersForProvider: []config.PublishersPolicy{
{
Provider: prov1Str,
Allow: false,
Except: []string{exceptIDStr},
},
{
Provider: prov2Str,
Allow: true,
Except: []string{exceptIDStr},
},
},
}

p, err := New(policyCfg)
require.NoError(t, err)

require.True(t, p.PublishAllowed(prov1, prov1), "should be allowed to publish to self")
require.False(t, p.PublishAllowed(exceptID, otherID))

// Test publishing for prov1.
require.False(t, p.PublishAllowed(otherID, prov1))
require.True(t, p.PublishAllowed(exceptID, prov1))
require.False(t, p.PublishAllowed(prov2, prov1))

// Test publishing for prov1.
require.True(t, p.PublishAllowed(otherID, prov2))
require.False(t, p.PublishAllowed(exceptID, prov2))
require.True(t, p.PublishAllowed(prov1, prov2))

// Test with default policy set to default allow true.
policyCfg.Publish = true
p, err = New(policyCfg)
require.NoError(t, err)

require.True(t, p.PublishAllowed(exceptID, otherID))
require.False(t, p.PublishAllowed(exceptID, prov2))
require.True(t, p.PublishAllowed(otherID, prov2))
require.False(t, p.PublishAllowed(otherID, prov1))
}
5 changes: 4 additions & 1 deletion internal/registry/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,9 @@ func TestPollProviderOverrides(t *testing.T) {

func TestRegistry_RegisterOrUpdateToleratesEmptyPublisherAddrs(t *testing.T) {
ctx := context.Background()
subject, err := New(ctx, config.NewDiscovery(), datastore.NewMapDatastore())
cfg := config.NewDiscovery()
cfg.Policy.Publish = true
subject, err := New(ctx, cfg, datastore.NewMapDatastore())
require.NoError(t, err)
t.Cleanup(func() { subject.Close() })

Expand Down Expand Up @@ -607,6 +609,7 @@ func TestRegistry_RegisterOrUpdateToleratesEmptyPublisherAddrs(t *testing.T) {

func TestFilterIPs(t *testing.T) {
cfg := config.NewDiscovery()
cfg.Policy.Publish = true

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
Expand Down

0 comments on commit e2e3e62

Please sign in to comment.