Move the ACL logic into the ConfigEntry interface
This commit is contained in:
parent
81254deb59
commit
2cffe4894f
|
@ -36,8 +36,8 @@ func (c *ConfigEntry) Apply(args *structs.ConfigEntryRequest, reply *struct{}) e
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := verifyConfigWriteACL(rule, args.Entry.GetKind(), args.Entry.GetName()); err != nil {
|
if rule != nil && !args.Entry.VerifyWriteACL(rule) {
|
||||||
return err
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Op = structs.ConfigEntryUpsert
|
args.Op = structs.ConfigEntryUpsert
|
||||||
|
@ -63,9 +63,15 @@ func (c *ConfigEntry) Get(args *structs.ConfigEntryQuery, reply *structs.Indexed
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := verifyConfigReadACL(rule, args.Kind, args.Name); err != nil {
|
|
||||||
|
// Create a dummy config entry to check the ACL permissions.
|
||||||
|
lookupEntry, err := structs.MakeConfigEntry(args.Kind, args.Name)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if rule != nil && !lookupEntry.VerifyReadACL(rule) {
|
||||||
|
return acl.ErrPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
return c.srv.blockingQuery(
|
return c.srv.blockingQuery(
|
||||||
&args.QueryOptions,
|
&args.QueryOptions,
|
||||||
|
@ -77,6 +83,11 @@ func (c *ConfigEntry) Get(args *structs.ConfigEntryQuery, reply *structs.Indexed
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Index = index
|
reply.Index = index
|
||||||
|
if entry == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.Kind = args.Kind
|
||||||
reply.Entries = []structs.ConfigEntry{entry}
|
reply.Entries = []structs.ConfigEntry{entry}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
@ -106,20 +117,15 @@ func (c *ConfigEntry) List(args *structs.ConfigEntryQuery, reply *structs.Indexe
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter the entries returned by ACL permissions.
|
// Filter the entries returned by ACL permissions.
|
||||||
// TODO(kyhavlov): should we handle the proxy config differently here since
|
|
||||||
// it's a singleton?
|
|
||||||
filteredEntries := make([]structs.ConfigEntry, 0, len(entries))
|
filteredEntries := make([]structs.ConfigEntry, 0, len(entries))
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if err := verifyConfigReadACL(rule, entry.GetKind(), entry.GetName()); err != nil {
|
if rule != nil && !entry.VerifyReadACL(rule) {
|
||||||
if acl.IsErrPermissionDenied(err) {
|
continue
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
filteredEntries = append(filteredEntries, entry)
|
filteredEntries = append(filteredEntries, entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reply.Kind = args.Kind
|
||||||
reply.Index = index
|
reply.Index = index
|
||||||
reply.Entries = filteredEntries
|
reply.Entries = filteredEntries
|
||||||
return nil
|
return nil
|
||||||
|
@ -143,8 +149,8 @@ func (c *ConfigEntry) Delete(args *structs.ConfigEntryRequest, reply *struct{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := verifyConfigWriteACL(rule, args.Entry.GetKind(), args.Entry.GetName()); err != nil {
|
if rule != nil && !args.Entry.VerifyWriteACL(rule) {
|
||||||
return err
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Op = structs.ConfigEntryDelete
|
args.Op = structs.ConfigEntryDelete
|
||||||
|
@ -218,49 +224,3 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyConfigReadACL checks whether the given ACL authorizer has permission
|
|
||||||
// to read the config entry of the given kind/name.
|
|
||||||
func verifyConfigReadACL(rule acl.Authorizer, kind, name string) error {
|
|
||||||
if rule == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case structs.ServiceDefaults:
|
|
||||||
if !rule.ServiceRead(name) {
|
|
||||||
return acl.ErrPermissionDenied
|
|
||||||
}
|
|
||||||
case structs.ProxyDefaults:
|
|
||||||
if !rule.OperatorRead() {
|
|
||||||
return acl.ErrPermissionDenied
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown config entry type %q", kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyConfigWriteACL checks whether the given ACL authorizer has permission
|
|
||||||
// to update the config entry of the given kind/name.
|
|
||||||
func verifyConfigWriteACL(rule acl.Authorizer, kind, name string) error {
|
|
||||||
if rule == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case structs.ServiceDefaults:
|
|
||||||
if !rule.ServiceWrite(name, nil) {
|
|
||||||
return acl.ErrPermissionDenied
|
|
||||||
}
|
|
||||||
case structs.ProxyDefaults:
|
|
||||||
if !rule.OperatorWrite() {
|
|
||||||
return acl.ErrPermissionDenied
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown config entry type %q", kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -200,7 +200,6 @@ operator = "read"
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a dummy service in the state store to look up.
|
|
||||||
// Create some dummy service/proxy configs to be looked up.
|
// Create some dummy service/proxy configs to be looked up.
|
||||||
state := s1.fsm.State()
|
state := s1.fsm.State()
|
||||||
require.NoError(state.EnsureConfigEntry(1, &structs.ProxyConfigEntry{
|
require.NoError(state.EnsureConfigEntry(1, &structs.ProxyConfigEntry{
|
||||||
|
@ -233,18 +232,6 @@ operator = "read"
|
||||||
require.True(ok)
|
require.True(ok)
|
||||||
require.Equal("foo", serviceConf.Name)
|
require.Equal("foo", serviceConf.Name)
|
||||||
require.Equal(structs.ServiceDefaults, serviceConf.Kind)
|
require.Equal(structs.ServiceDefaults, serviceConf.Kind)
|
||||||
|
|
||||||
// Try to look up the proxy config with no token.
|
|
||||||
args.Kind = structs.ProxyDefaults
|
|
||||||
args.Name = structs.ProxyConfigGlobal
|
|
||||||
args.QueryOptions.Token = ""
|
|
||||||
err = msgpackrpc.CallWithCodec(codec, "ConfigEntry.Get", &args, &out)
|
|
||||||
if !acl.IsErrPermissionDenied(err) {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
args.QueryOptions.Token = id
|
|
||||||
require.NoError(msgpackrpc.CallWithCodec(codec, "ConfigEntry.Get", &args, &out))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigEntry_List(t *testing.T) {
|
func TestConfigEntry_List(t *testing.T) {
|
||||||
|
@ -282,6 +269,7 @@ func TestConfigEntry_List(t *testing.T) {
|
||||||
var out structs.IndexedConfigEntries
|
var out structs.IndexedConfigEntries
|
||||||
require.NoError(msgpackrpc.CallWithCodec(codec, "ConfigEntry.List", &args, &out))
|
require.NoError(msgpackrpc.CallWithCodec(codec, "ConfigEntry.List", &args, &out))
|
||||||
|
|
||||||
|
expected.Kind = structs.ServiceDefaults
|
||||||
expected.QueryMeta = out.QueryMeta
|
expected.QueryMeta = out.QueryMeta
|
||||||
require.Equal(expected, out)
|
require.Equal(expected, out)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/go-msgpack/codec"
|
"github.com/hashicorp/go-msgpack/codec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,6 +26,11 @@ type ConfigEntry interface {
|
||||||
Normalize() error
|
Normalize() error
|
||||||
Validate() error
|
Validate() error
|
||||||
|
|
||||||
|
// VerifyReadACL and VerifyWriteACL return whether or not the given Authorizer
|
||||||
|
// has permission to read or write to the config entry, respectively.
|
||||||
|
VerifyReadACL(acl.Authorizer) bool
|
||||||
|
VerifyWriteACL(acl.Authorizer) bool
|
||||||
|
|
||||||
GetRaftIndex() *RaftIndex
|
GetRaftIndex() *RaftIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +76,14 @@ func (e *ServiceConfigEntry) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *ServiceConfigEntry) VerifyReadACL(rule acl.Authorizer) bool {
|
||||||
|
return rule.ServiceRead(e.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ServiceConfigEntry) VerifyWriteACL(rule acl.Authorizer) bool {
|
||||||
|
return rule.ServiceWrite(e.Name, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (e *ServiceConfigEntry) GetRaftIndex() *RaftIndex {
|
func (e *ServiceConfigEntry) GetRaftIndex() *RaftIndex {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return &RaftIndex{}
|
return &RaftIndex{}
|
||||||
|
@ -126,6 +140,14 @@ func (e *ProxyConfigEntry) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *ProxyConfigEntry) VerifyReadACL(rule acl.Authorizer) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ProxyConfigEntry) VerifyWriteACL(rule acl.Authorizer) bool {
|
||||||
|
return rule.OperatorWrite()
|
||||||
|
}
|
||||||
|
|
||||||
func (e *ProxyConfigEntry) GetRaftIndex() *RaftIndex {
|
func (e *ProxyConfigEntry) GetRaftIndex() *RaftIndex {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return &RaftIndex{}
|
return &RaftIndex{}
|
||||||
|
@ -186,7 +208,7 @@ func (c *ConfigEntryRequest) UnmarshalBinary(data []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then decode the real thing with appropriate kind of ConfigEntry
|
// Then decode the real thing with appropriate kind of ConfigEntry
|
||||||
entry, err := makeConfigEntry(kind)
|
entry, err := MakeConfigEntry(kind, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -206,12 +228,12 @@ func (c *ConfigEntryRequest) UnmarshalBinary(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeConfigEntry(kind string) (ConfigEntry, error) {
|
func MakeConfigEntry(kind, name string) (ConfigEntry, error) {
|
||||||
switch kind {
|
switch kind {
|
||||||
case ServiceDefaults:
|
case ServiceDefaults:
|
||||||
return &ServiceConfigEntry{}, nil
|
return &ServiceConfigEntry{Name: name}, nil
|
||||||
case ProxyDefaults:
|
case ProxyDefaults:
|
||||||
return &ProxyConfigEntry{}, nil
|
return &ProxyConfigEntry{Name: name}, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
|
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1143,6 +1143,7 @@ type IndexedNodeDump struct {
|
||||||
// IndexedConfigEntries has its own encoding logic which differs from
|
// IndexedConfigEntries has its own encoding logic which differs from
|
||||||
// ConfigEntryRequest as it has to send a slice of ConfigEntry.
|
// ConfigEntryRequest as it has to send a slice of ConfigEntry.
|
||||||
type IndexedConfigEntries struct {
|
type IndexedConfigEntries struct {
|
||||||
|
Kind string
|
||||||
Entries []ConfigEntry
|
Entries []ConfigEntry
|
||||||
QueryMeta
|
QueryMeta
|
||||||
}
|
}
|
||||||
|
@ -1153,16 +1154,16 @@ func (c *IndexedConfigEntries) MarshalBinary() (data []byte, err error) {
|
||||||
bs := make([]byte, 128)
|
bs := make([]byte, 128)
|
||||||
enc := codec.NewEncoderBytes(&bs, msgpackHandle)
|
enc := codec.NewEncoderBytes(&bs, msgpackHandle)
|
||||||
|
|
||||||
// Encode kinds of entries first
|
// Encode length.
|
||||||
err = enc.Encode(len(c.Entries))
|
err = enc.Encode(len(c.Entries))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, entry := range c.Entries {
|
|
||||||
err = enc.Encode(entry.GetKind())
|
// Encode kind.
|
||||||
if err != nil {
|
err = enc.Encode(c.Kind)
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then actual value using alias trick to avoid infinite recursion
|
// Then actual value using alias trick to avoid infinite recursion
|
||||||
|
@ -1179,23 +1180,23 @@ func (c *IndexedConfigEntries) MarshalBinary() (data []byte, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *IndexedConfigEntries) UnmarshalBinary(data []byte) error {
|
func (c *IndexedConfigEntries) UnmarshalBinary(data []byte) error {
|
||||||
// First decode the number of entries
|
// First decode the number of entries.
|
||||||
var numEntries int
|
var numEntries int
|
||||||
dec := codec.NewDecoderBytes(data, msgpackHandle)
|
dec := codec.NewDecoderBytes(data, msgpackHandle)
|
||||||
if err := dec.Decode(&numEntries); err != nil {
|
if err := dec.Decode(&numEntries); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Next decode the kind.
|
||||||
|
var kind string
|
||||||
|
if err := dec.Decode(&kind); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then decode the slice of ConfigEntries
|
||||||
c.Entries = make([]ConfigEntry, numEntries)
|
c.Entries = make([]ConfigEntry, numEntries)
|
||||||
for i := 0; i < numEntries; i++ {
|
for i := 0; i < numEntries; i++ {
|
||||||
// First decode the kind prefix
|
entry, err := MakeConfigEntry(kind, "")
|
||||||
var kind string
|
|
||||||
if err := dec.Decode(&kind); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then decode the real thing with appropriate kind of ConfigEntry
|
|
||||||
entry, err := makeConfigEntry(kind)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue