mirror of https://github.com/hashicorp/consul
commit
a6317f2fb2
44
acl/acl.go
44
acl/acl.go
|
@ -58,6 +58,13 @@ type ACL interface {
|
||||||
// EventWrite determines if a specific event may be fired.
|
// EventWrite determines if a specific event may be fired.
|
||||||
EventWrite(string) bool
|
EventWrite(string) bool
|
||||||
|
|
||||||
|
// KeyringRead determines if the encryption keyring used in
|
||||||
|
// the gossip layer can be read.
|
||||||
|
KeyringRead() bool
|
||||||
|
|
||||||
|
// KeyringWrite determines if the keyring can be manipulated
|
||||||
|
KeyringWrite() bool
|
||||||
|
|
||||||
// ACLList checks for permission to list all the ACLs
|
// ACLList checks for permission to list all the ACLs
|
||||||
ACLList() bool
|
ACLList() bool
|
||||||
|
|
||||||
|
@ -101,6 +108,14 @@ func (s *StaticACL) EventWrite(string) bool {
|
||||||
return s.defaultAllow
|
return s.defaultAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StaticACL) KeyringRead() bool {
|
||||||
|
return s.defaultAllow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StaticACL) KeyringWrite() bool {
|
||||||
|
return s.defaultAllow
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StaticACL) ACLList() bool {
|
func (s *StaticACL) ACLList() bool {
|
||||||
return s.allowManage
|
return s.allowManage
|
||||||
}
|
}
|
||||||
|
@ -153,6 +168,11 @@ type PolicyACL struct {
|
||||||
|
|
||||||
// eventRules contains the user event policies
|
// eventRules contains the user event policies
|
||||||
eventRules *radix.Tree
|
eventRules *radix.Tree
|
||||||
|
|
||||||
|
// keyringRules contains the keyring policies. The keyring has
|
||||||
|
// a very simple yes/no without prefix mathing, so here we
|
||||||
|
// don't need to use a radix tree.
|
||||||
|
keyringRule string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New is used to construct a policy based ACL from a set of policies
|
// New is used to construct a policy based ACL from a set of policies
|
||||||
|
@ -180,6 +200,9 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
|
||||||
p.eventRules.Insert(ep.Event, ep.Policy)
|
p.eventRules.Insert(ep.Event, ep.Policy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the keyring policy
|
||||||
|
p.keyringRule = policy.Keyring
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,6 +344,27 @@ func (p *PolicyACL) EventWrite(name string) bool {
|
||||||
return p.parent.EventWrite(name)
|
return p.parent.EventWrite(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeyringRead is used to determine if the keyring can be
|
||||||
|
// read by the current ACL token.
|
||||||
|
func (p *PolicyACL) KeyringRead() bool {
|
||||||
|
switch p.keyringRule {
|
||||||
|
case KeyringPolicyRead, KeyringPolicyWrite:
|
||||||
|
return true
|
||||||
|
case KeyringPolicyDeny:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return p.parent.KeyringRead()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyringWrite determines if the keyring can be manipulated.
|
||||||
|
func (p *PolicyACL) KeyringWrite() bool {
|
||||||
|
if p.keyringRule == KeyringPolicyWrite {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return p.parent.KeyringWrite()
|
||||||
|
}
|
||||||
|
|
||||||
// ACLList checks if listing of ACLs is allowed
|
// ACLList checks if listing of ACLs is allowed
|
||||||
func (p *PolicyACL) ACLList() bool {
|
func (p *PolicyACL) ACLList() bool {
|
||||||
return p.parent.ACLList()
|
return p.parent.ACLList()
|
||||||
|
|
|
@ -47,6 +47,18 @@ func TestStaticACL(t *testing.T) {
|
||||||
if !all.ServiceWrite("foobar") {
|
if !all.ServiceWrite("foobar") {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
|
if !all.EventRead("foobar") {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
|
if !all.EventWrite("foobar") {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
|
if !all.KeyringRead() {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
|
if !all.KeyringWrite() {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
if all.ACLList() {
|
if all.ACLList() {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
|
@ -78,6 +90,12 @@ func TestStaticACL(t *testing.T) {
|
||||||
if none.EventWrite("") {
|
if none.EventWrite("") {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
|
if none.KeyringRead() {
|
||||||
|
t.Fatalf("should now allow")
|
||||||
|
}
|
||||||
|
if none.KeyringWrite() {
|
||||||
|
t.Fatalf("should not allow")
|
||||||
|
}
|
||||||
if none.ACLList() {
|
if none.ACLList() {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
|
@ -97,6 +115,18 @@ func TestStaticACL(t *testing.T) {
|
||||||
if !manage.ServiceWrite("foobar") {
|
if !manage.ServiceWrite("foobar") {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
|
if !manage.EventRead("foobar") {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
|
if !manage.EventWrite("foobar") {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
|
if !manage.KeyringRead() {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
|
if !manage.KeyringWrite() {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
if !manage.ACLList() {
|
if !manage.ACLList() {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
|
@ -215,6 +245,7 @@ func TestPolicyACL(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test the events
|
||||||
type eventcase struct {
|
type eventcase struct {
|
||||||
inp string
|
inp string
|
||||||
read bool
|
read bool
|
||||||
|
@ -339,3 +370,30 @@ func TestPolicyACL_Parent(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPolicyACL_Keyring(t *testing.T) {
|
||||||
|
// Test keyring ACLs
|
||||||
|
type keyringcase struct {
|
||||||
|
inp string
|
||||||
|
read bool
|
||||||
|
write bool
|
||||||
|
}
|
||||||
|
keyringcases := []keyringcase{
|
||||||
|
{"", false, false},
|
||||||
|
{KeyringPolicyRead, true, false},
|
||||||
|
{KeyringPolicyWrite, true, true},
|
||||||
|
{KeyringPolicyDeny, false, false},
|
||||||
|
}
|
||||||
|
for _, c := range keyringcases {
|
||||||
|
acl, err := New(DenyAll(), &Policy{Keyring: c.inp})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
if acl.KeyringRead() != c.read {
|
||||||
|
t.Fatalf("bad: %#v", c)
|
||||||
|
}
|
||||||
|
if acl.KeyringWrite() != c.write {
|
||||||
|
t.Fatalf("bad: %#v", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,9 @@ const (
|
||||||
EventPolicyRead = "read"
|
EventPolicyRead = "read"
|
||||||
EventPolicyWrite = "write"
|
EventPolicyWrite = "write"
|
||||||
EventPolicyDeny = "deny"
|
EventPolicyDeny = "deny"
|
||||||
|
KeyringPolicyWrite = "write"
|
||||||
|
KeyringPolicyRead = "read"
|
||||||
|
KeyringPolicyDeny = "deny"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Policy is used to represent the policy specified by
|
// Policy is used to represent the policy specified by
|
||||||
|
@ -25,6 +28,7 @@ type Policy struct {
|
||||||
Keys []*KeyPolicy `hcl:"key,expand"`
|
Keys []*KeyPolicy `hcl:"key,expand"`
|
||||||
Services []*ServicePolicy `hcl:"service,expand"`
|
Services []*ServicePolicy `hcl:"service,expand"`
|
||||||
Events []*EventPolicy `hcl:"event,expand"`
|
Events []*EventPolicy `hcl:"event,expand"`
|
||||||
|
Keyring string `hcl:"keyring"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyPolicy represents a policy for a key
|
// KeyPolicy represents a policy for a key
|
||||||
|
@ -105,5 +109,15 @@ func Parse(rules string) (*Policy, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the keyring policy
|
||||||
|
switch p.Keyring {
|
||||||
|
case KeyringPolicyRead:
|
||||||
|
case KeyringPolicyWrite:
|
||||||
|
case KeyringPolicyDeny:
|
||||||
|
case "": // Special case to allow omitting the keyring policy
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid keyring policy: %#v", p.Keyring)
|
||||||
|
}
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package acl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ event "foo" {
|
||||||
event "bar" {
|
event "bar" {
|
||||||
policy = "deny"
|
policy = "deny"
|
||||||
}
|
}
|
||||||
|
keyring = "deny"
|
||||||
`
|
`
|
||||||
exp := &Policy{
|
exp := &Policy{
|
||||||
Keys: []*KeyPolicy{
|
Keys: []*KeyPolicy{
|
||||||
|
@ -78,6 +80,7 @@ event "bar" {
|
||||||
Policy: EventPolicyDeny,
|
Policy: EventPolicyDeny,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Keyring: KeyringPolicyDeny,
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := Parse(inp)
|
out, err := Parse(inp)
|
||||||
|
@ -124,7 +127,8 @@ func TestParse_JSON(t *testing.T) {
|
||||||
"bar": {
|
"bar": {
|
||||||
"policy": "deny"
|
"policy": "deny"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"keyring": "deny"
|
||||||
}`
|
}`
|
||||||
exp := &Policy{
|
exp := &Policy{
|
||||||
Keys: []*KeyPolicy{
|
Keys: []*KeyPolicy{
|
||||||
|
@ -169,6 +173,7 @@ func TestParse_JSON(t *testing.T) {
|
||||||
Policy: EventPolicyDeny,
|
Policy: EventPolicyDeny,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Keyring: KeyringPolicyDeny,
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := Parse(inp)
|
out, err := Parse(inp)
|
||||||
|
@ -180,3 +185,18 @@ func TestParse_JSON(t *testing.T) {
|
||||||
t.Fatalf("bad: %#v %#v", out, exp)
|
t.Fatalf("bad: %#v %#v", out, exp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestACLPolicy_badPolicy(t *testing.T) {
|
||||||
|
cases := []string{
|
||||||
|
`key "" { policy = "nope" }`,
|
||||||
|
`service "" { policy = "nope" }`,
|
||||||
|
`event "" { policy = "nope" }`,
|
||||||
|
`keyring = "nope"`,
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
_, err := Parse(c)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "Invalid") {
|
||||||
|
t.Fatalf("expected policy error, got: %#v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -121,25 +121,29 @@ func (a *Agent) keyringProcess(args *structs.KeyringRequest) (*structs.KeyringRe
|
||||||
|
|
||||||
// ListKeys lists out all keys installed on the collective Consul cluster. This
|
// ListKeys lists out all keys installed on the collective Consul cluster. This
|
||||||
// includes both servers and clients in all DC's.
|
// includes both servers and clients in all DC's.
|
||||||
func (a *Agent) ListKeys() (*structs.KeyringResponses, error) {
|
func (a *Agent) ListKeys(token string) (*structs.KeyringResponses, error) {
|
||||||
args := structs.KeyringRequest{Operation: structs.KeyringList}
|
args := structs.KeyringRequest{Operation: structs.KeyringList}
|
||||||
|
args.Token = token
|
||||||
return a.keyringProcess(&args)
|
return a.keyringProcess(&args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstallKey installs a new gossip encryption key
|
// InstallKey installs a new gossip encryption key
|
||||||
func (a *Agent) InstallKey(key string) (*structs.KeyringResponses, error) {
|
func (a *Agent) InstallKey(key, token string) (*structs.KeyringResponses, error) {
|
||||||
args := structs.KeyringRequest{Key: key, Operation: structs.KeyringInstall}
|
args := structs.KeyringRequest{Key: key, Operation: structs.KeyringInstall}
|
||||||
|
args.Token = token
|
||||||
return a.keyringProcess(&args)
|
return a.keyringProcess(&args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseKey changes the primary encryption key used to encrypt messages
|
// UseKey changes the primary encryption key used to encrypt messages
|
||||||
func (a *Agent) UseKey(key string) (*structs.KeyringResponses, error) {
|
func (a *Agent) UseKey(key, token string) (*structs.KeyringResponses, error) {
|
||||||
args := structs.KeyringRequest{Key: key, Operation: structs.KeyringUse}
|
args := structs.KeyringRequest{Key: key, Operation: structs.KeyringUse}
|
||||||
|
args.Token = token
|
||||||
return a.keyringProcess(&args)
|
return a.keyringProcess(&args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveKey will remove a gossip encryption key from the keyring
|
// RemoveKey will remove a gossip encryption key from the keyring
|
||||||
func (a *Agent) RemoveKey(key string) (*structs.KeyringResponses, error) {
|
func (a *Agent) RemoveKey(key, token string) (*structs.KeyringResponses, error) {
|
||||||
args := structs.KeyringRequest{Key: key, Operation: structs.KeyringRemove}
|
args := structs.KeyringRequest{Key: key, Operation: structs.KeyringRemove}
|
||||||
|
args.Token = token
|
||||||
return a.keyringProcess(&args)
|
return a.keyringProcess(&args)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,10 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAgent_LoadKeyrings(t *testing.T) {
|
func TestAgent_LoadKeyrings(t *testing.T) {
|
||||||
|
@ -113,3 +116,66 @@ func TestAgent_InitKeyring(t *testing.T) {
|
||||||
t.Fatalf("bad: %s", content)
|
t.Fatalf("bad: %s", content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAgentKeyring_ACL(t *testing.T) {
|
||||||
|
key1 := "tbLJg26ZJyJ9pK3qhc9jig=="
|
||||||
|
key2 := "4leC33rgtXKIVUr9Nr0snQ=="
|
||||||
|
|
||||||
|
conf := nextConfig()
|
||||||
|
conf.ACLDatacenter = "dc1"
|
||||||
|
conf.ACLMasterToken = "root"
|
||||||
|
conf.ACLDefaultPolicy = "deny"
|
||||||
|
dir, agent := makeAgentKeyring(t, conf, key1)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
defer agent.Shutdown()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, agent.RPC, "dc1")
|
||||||
|
|
||||||
|
// List keys without access fails
|
||||||
|
_, err := agent.ListKeys("")
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "denied") {
|
||||||
|
t.Fatalf("expected denied error, got: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List keys with access works
|
||||||
|
_, err = agent.ListKeys("root")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install without access fails
|
||||||
|
_, err = agent.InstallKey(key2, "")
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "denied") {
|
||||||
|
t.Fatalf("expected denied error, got: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install with access works
|
||||||
|
_, err = agent.InstallKey(key2, "root")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use without access fails
|
||||||
|
_, err = agent.UseKey(key2, "")
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "denied") {
|
||||||
|
t.Fatalf("expected denied error, got: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use with access works
|
||||||
|
_, err = agent.UseKey(key2, "root")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove without access fails
|
||||||
|
_, err = agent.RemoveKey(key1, "")
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "denied") {
|
||||||
|
t.Fatalf("expected denied error, got: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove with access works
|
||||||
|
_, err = agent.RemoveKey(key1, "root")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ var msgpackHandle = &codec.MsgpackHandle{
|
||||||
type requestHeader struct {
|
type requestHeader struct {
|
||||||
Command string
|
Command string
|
||||||
Seq uint64
|
Seq uint64
|
||||||
|
Token string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response header is sent before each response
|
// Response header is sent before each response
|
||||||
|
@ -365,6 +366,7 @@ func (i *AgentRPC) handleRequest(client *rpcClient, reqHeader *requestHeader) er
|
||||||
// Look for a command field
|
// Look for a command field
|
||||||
command := reqHeader.Command
|
command := reqHeader.Command
|
||||||
seq := reqHeader.Seq
|
seq := reqHeader.Seq
|
||||||
|
token := reqHeader.Token
|
||||||
|
|
||||||
// Ensure the handshake is performed before other commands
|
// Ensure the handshake is performed before other commands
|
||||||
if command != handshakeCommand && client.version == 0 {
|
if command != handshakeCommand && client.version == 0 {
|
||||||
|
@ -406,7 +408,7 @@ func (i *AgentRPC) handleRequest(client *rpcClient, reqHeader *requestHeader) er
|
||||||
return i.handleReload(client, seq)
|
return i.handleReload(client, seq)
|
||||||
|
|
||||||
case installKeyCommand, useKeyCommand, removeKeyCommand, listKeysCommand:
|
case installKeyCommand, useKeyCommand, removeKeyCommand, listKeysCommand:
|
||||||
return i.handleKeyring(client, seq, command)
|
return i.handleKeyring(client, seq, command, token)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
respHeader := responseHeader{Seq: seq, Error: unsupportedCommand}
|
respHeader := responseHeader{Seq: seq, Error: unsupportedCommand}
|
||||||
|
@ -618,7 +620,7 @@ func (i *AgentRPC) handleReload(client *rpcClient, seq uint64) error {
|
||||||
return client.Send(&resp, nil)
|
return client.Send(&resp, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd string) error {
|
func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd, token string) error {
|
||||||
var req keyringRequest
|
var req keyringRequest
|
||||||
var queryResp *structs.KeyringResponses
|
var queryResp *structs.KeyringResponses
|
||||||
var r keyringResponse
|
var r keyringResponse
|
||||||
|
@ -632,13 +634,13 @@ func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd string) erro
|
||||||
|
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case listKeysCommand:
|
case listKeysCommand:
|
||||||
queryResp, err = i.agent.ListKeys()
|
queryResp, err = i.agent.ListKeys(token)
|
||||||
case installKeyCommand:
|
case installKeyCommand:
|
||||||
queryResp, err = i.agent.InstallKey(req.Key)
|
queryResp, err = i.agent.InstallKey(req.Key, token)
|
||||||
case useKeyCommand:
|
case useKeyCommand:
|
||||||
queryResp, err = i.agent.UseKey(req.Key)
|
queryResp, err = i.agent.UseKey(req.Key, token)
|
||||||
case removeKeyCommand:
|
case removeKeyCommand:
|
||||||
queryResp, err = i.agent.RemoveKey(req.Key)
|
queryResp, err = i.agent.RemoveKey(req.Key, token)
|
||||||
default:
|
default:
|
||||||
respHeader := responseHeader{Seq: seq, Error: unsupportedCommand}
|
respHeader := responseHeader{Seq: seq, Error: unsupportedCommand}
|
||||||
client.Send(&respHeader, nil)
|
client.Send(&respHeader, nil)
|
||||||
|
|
|
@ -188,20 +188,22 @@ func (c *RPCClient) WANMembers() ([]Member, error) {
|
||||||
return resp.Members, err
|
return resp.Members, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RPCClient) ListKeys() (keyringResponse, error) {
|
func (c *RPCClient) ListKeys(token string) (keyringResponse, error) {
|
||||||
header := requestHeader{
|
header := requestHeader{
|
||||||
Command: listKeysCommand,
|
Command: listKeysCommand,
|
||||||
Seq: c.getSeq(),
|
Seq: c.getSeq(),
|
||||||
|
Token: token,
|
||||||
}
|
}
|
||||||
var resp keyringResponse
|
var resp keyringResponse
|
||||||
err := c.genericRPC(&header, nil, &resp)
|
err := c.genericRPC(&header, nil, &resp)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RPCClient) InstallKey(key string) (keyringResponse, error) {
|
func (c *RPCClient) InstallKey(key, token string) (keyringResponse, error) {
|
||||||
header := requestHeader{
|
header := requestHeader{
|
||||||
Command: installKeyCommand,
|
Command: installKeyCommand,
|
||||||
Seq: c.getSeq(),
|
Seq: c.getSeq(),
|
||||||
|
Token: token,
|
||||||
}
|
}
|
||||||
req := keyringRequest{key}
|
req := keyringRequest{key}
|
||||||
var resp keyringResponse
|
var resp keyringResponse
|
||||||
|
@ -209,10 +211,11 @@ func (c *RPCClient) InstallKey(key string) (keyringResponse, error) {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RPCClient) UseKey(key string) (keyringResponse, error) {
|
func (c *RPCClient) UseKey(key, token string) (keyringResponse, error) {
|
||||||
header := requestHeader{
|
header := requestHeader{
|
||||||
Command: useKeyCommand,
|
Command: useKeyCommand,
|
||||||
Seq: c.getSeq(),
|
Seq: c.getSeq(),
|
||||||
|
Token: token,
|
||||||
}
|
}
|
||||||
req := keyringRequest{key}
|
req := keyringRequest{key}
|
||||||
var resp keyringResponse
|
var resp keyringResponse
|
||||||
|
@ -220,10 +223,11 @@ func (c *RPCClient) UseKey(key string) (keyringResponse, error) {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RPCClient) RemoveKey(key string) (keyringResponse, error) {
|
func (c *RPCClient) RemoveKey(key, token string) (keyringResponse, error) {
|
||||||
header := requestHeader{
|
header := requestHeader{
|
||||||
Command: removeKeyCommand,
|
Command: removeKeyCommand,
|
||||||
Seq: c.getSeq(),
|
Seq: c.getSeq(),
|
||||||
|
Token: token,
|
||||||
}
|
}
|
||||||
req := keyringRequest{key}
|
req := keyringRequest{key}
|
||||||
var resp keyringResponse
|
var resp keyringResponse
|
||||||
|
|
|
@ -325,6 +325,7 @@ func TestRPCClientListKeys(t *testing.T) {
|
||||||
p1 := testRPCClientWithConfig(t, func(c *Config) {
|
p1 := testRPCClientWithConfig(t, func(c *Config) {
|
||||||
c.EncryptKey = key1
|
c.EncryptKey = key1
|
||||||
c.Datacenter = "dc1"
|
c.Datacenter = "dc1"
|
||||||
|
c.ACLDatacenter = ""
|
||||||
})
|
})
|
||||||
defer p1.Close()
|
defer p1.Close()
|
||||||
|
|
||||||
|
@ -343,6 +344,7 @@ func TestRPCClientInstallKey(t *testing.T) {
|
||||||
key2 := "xAEZ3uVHRMZD9GcYMZaRQw=="
|
key2 := "xAEZ3uVHRMZD9GcYMZaRQw=="
|
||||||
p1 := testRPCClientWithConfig(t, func(c *Config) {
|
p1 := testRPCClientWithConfig(t, func(c *Config) {
|
||||||
c.EncryptKey = key1
|
c.EncryptKey = key1
|
||||||
|
c.ACLDatacenter = ""
|
||||||
})
|
})
|
||||||
defer p1.Close()
|
defer p1.Close()
|
||||||
|
|
||||||
|
@ -361,7 +363,7 @@ func TestRPCClientInstallKey(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// install key2
|
// install key2
|
||||||
r, err := p1.client.InstallKey(key2)
|
r, err := p1.client.InstallKey(key2, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -387,11 +389,12 @@ func TestRPCClientUseKey(t *testing.T) {
|
||||||
key2 := "xAEZ3uVHRMZD9GcYMZaRQw=="
|
key2 := "xAEZ3uVHRMZD9GcYMZaRQw=="
|
||||||
p1 := testRPCClientWithConfig(t, func(c *Config) {
|
p1 := testRPCClientWithConfig(t, func(c *Config) {
|
||||||
c.EncryptKey = key1
|
c.EncryptKey = key1
|
||||||
|
c.ACLDatacenter = ""
|
||||||
})
|
})
|
||||||
defer p1.Close()
|
defer p1.Close()
|
||||||
|
|
||||||
// add a second key to the ring
|
// add a second key to the ring
|
||||||
r, err := p1.client.InstallKey(key2)
|
r, err := p1.client.InstallKey(key2, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -412,21 +415,21 @@ func TestRPCClientUseKey(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// can't remove key1 yet
|
// can't remove key1 yet
|
||||||
r, err = p1.client.RemoveKey(key1)
|
r, err = p1.client.RemoveKey(key1, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
keyringError(t, r)
|
keyringError(t, r)
|
||||||
|
|
||||||
// change primary key
|
// change primary key
|
||||||
r, err = p1.client.UseKey(key2)
|
r, err = p1.client.UseKey(key2, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
keyringSuccess(t, r)
|
keyringSuccess(t, r)
|
||||||
|
|
||||||
// can remove key1 now
|
// can remove key1 now
|
||||||
r, err = p1.client.RemoveKey(key1)
|
r, err = p1.client.RemoveKey(key1, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -434,10 +437,12 @@ func TestRPCClientUseKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRPCClientKeyOperation_encryptionDisabled(t *testing.T) {
|
func TestRPCClientKeyOperation_encryptionDisabled(t *testing.T) {
|
||||||
p1 := testRPCClient(t)
|
p1 := testRPCClientWithConfig(t, func(c *Config) {
|
||||||
|
c.ACLDatacenter = ""
|
||||||
|
})
|
||||||
defer p1.Close()
|
defer p1.Close()
|
||||||
|
|
||||||
r, err := p1.client.ListKeys()
|
r, err := p1.client.ListKeys("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -445,7 +450,7 @@ func TestRPCClientKeyOperation_encryptionDisabled(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func listKeys(t *testing.T, c *RPCClient) map[string]map[string]int {
|
func listKeys(t *testing.T, c *RPCClient) map[string]map[string]int {
|
||||||
resp, err := c.ListKeys()
|
resp, err := c.ListKeys("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ type KeyringCommand struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *KeyringCommand) Run(args []string) int {
|
func (c *KeyringCommand) Run(args []string) int {
|
||||||
var installKey, useKey, removeKey string
|
var installKey, useKey, removeKey, token string
|
||||||
var listKeys bool
|
var listKeys bool
|
||||||
|
|
||||||
cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError)
|
cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError)
|
||||||
|
@ -26,6 +26,7 @@ func (c *KeyringCommand) Run(args []string) int {
|
||||||
cmdFlags.StringVar(&useKey, "use", "", "use key")
|
cmdFlags.StringVar(&useKey, "use", "", "use key")
|
||||||
cmdFlags.StringVar(&removeKey, "remove", "", "remove key")
|
cmdFlags.StringVar(&removeKey, "remove", "", "remove key")
|
||||||
cmdFlags.BoolVar(&listKeys, "list", false, "list keys")
|
cmdFlags.BoolVar(&listKeys, "list", false, "list keys")
|
||||||
|
cmdFlags.StringVar(&token, "token", "", "acl token")
|
||||||
|
|
||||||
rpcAddr := RPCAddrFlag(cmdFlags)
|
rpcAddr := RPCAddrFlag(cmdFlags)
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
|
@ -65,7 +66,7 @@ func (c *KeyringCommand) Run(args []string) int {
|
||||||
|
|
||||||
if listKeys {
|
if listKeys {
|
||||||
c.Ui.Info("Gathering installed encryption keys...")
|
c.Ui.Info("Gathering installed encryption keys...")
|
||||||
r, err := client.ListKeys()
|
r, err := client.ListKeys(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("error: %s", err))
|
c.Ui.Error(fmt.Sprintf("error: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -79,7 +80,7 @@ func (c *KeyringCommand) Run(args []string) int {
|
||||||
|
|
||||||
if installKey != "" {
|
if installKey != "" {
|
||||||
c.Ui.Info("Installing new gossip encryption key...")
|
c.Ui.Info("Installing new gossip encryption key...")
|
||||||
r, err := client.InstallKey(installKey)
|
r, err := client.InstallKey(installKey, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("error: %s", err))
|
c.Ui.Error(fmt.Sprintf("error: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -89,7 +90,7 @@ func (c *KeyringCommand) Run(args []string) int {
|
||||||
|
|
||||||
if useKey != "" {
|
if useKey != "" {
|
||||||
c.Ui.Info("Changing primary gossip encryption key...")
|
c.Ui.Info("Changing primary gossip encryption key...")
|
||||||
r, err := client.UseKey(useKey)
|
r, err := client.UseKey(useKey, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("error: %s", err))
|
c.Ui.Error(fmt.Sprintf("error: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -99,7 +100,7 @@ func (c *KeyringCommand) Run(args []string) int {
|
||||||
|
|
||||||
if removeKey != "" {
|
if removeKey != "" {
|
||||||
c.Ui.Info("Removing gossip encryption key...")
|
c.Ui.Info("Removing gossip encryption key...")
|
||||||
r, err := client.RemoveKey(removeKey)
|
r, err := client.RemoveKey(removeKey, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("error: %s", err))
|
c.Ui.Error(fmt.Sprintf("error: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -199,13 +200,15 @@ Options:
|
||||||
|
|
||||||
-install=<key> Install a new encryption key. This will broadcast
|
-install=<key> Install a new encryption key. This will broadcast
|
||||||
the new key to all members in the cluster.
|
the new key to all members in the cluster.
|
||||||
-use=<key> Change the primary encryption key, which is used to
|
-list List all keys currently in use within the cluster.
|
||||||
encrypt messages. The key must already be installed
|
|
||||||
before this operation can succeed.
|
|
||||||
-remove=<key> Remove the given key from the cluster. This
|
-remove=<key> Remove the given key from the cluster. This
|
||||||
operation may only be performed on keys which are
|
operation may only be performed on keys which are
|
||||||
not currently the primary key.
|
not currently the primary key.
|
||||||
-list List all keys currently in use within the cluster.
|
-token="" ACL token to use during requests. Defaults to that
|
||||||
|
of the agent.
|
||||||
|
-use=<key> Change the primary encryption key, which is used to
|
||||||
|
encrypt messages. The key must already be installed
|
||||||
|
before this operation can succeed.
|
||||||
-rpc-addr=127.0.0.1:8400 RPC address of the Consul agent.
|
-rpc-addr=127.0.0.1:8400 RPC address of the Consul agent.
|
||||||
`
|
`
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package consul
|
package consul
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/consul/structs"
|
"github.com/hashicorp/consul/consul/structs"
|
||||||
"github.com/hashicorp/serf/serf"
|
"github.com/hashicorp/serf/serf"
|
||||||
)
|
)
|
||||||
|
@ -83,6 +85,30 @@ func (m *Internal) KeyringOperation(
|
||||||
args *structs.KeyringRequest,
|
args *structs.KeyringRequest,
|
||||||
reply *structs.KeyringResponses) error {
|
reply *structs.KeyringResponses) error {
|
||||||
|
|
||||||
|
// Check ACLs
|
||||||
|
acl, err := m.srv.resolveToken(args.Token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if acl != nil {
|
||||||
|
switch args.Operation {
|
||||||
|
case structs.KeyringList:
|
||||||
|
if !acl.KeyringRead() {
|
||||||
|
return fmt.Errorf("Reading keyring denied by ACLs")
|
||||||
|
}
|
||||||
|
case structs.KeyringInstall:
|
||||||
|
fallthrough
|
||||||
|
case structs.KeyringUse:
|
||||||
|
fallthrough
|
||||||
|
case structs.KeyringRemove:
|
||||||
|
if !acl.KeyringWrite() {
|
||||||
|
return fmt.Errorf("Modifying keyring denied due to ACLs")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("Invalid keyring operation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Only perform WAN keyring querying and RPC forwarding once
|
// Only perform WAN keyring querying and RPC forwarding once
|
||||||
if !args.Forwarded {
|
if !args.Forwarded {
|
||||||
args.Forwarded = true
|
args.Forwarded = true
|
||||||
|
|
|
@ -18,8 +18,8 @@ on tokens to which fine grained rules can be applied. It is very similar to
|
||||||
|
|
||||||
When the ACL system was launched in Consul 0.4, it was only possible to specify
|
When the ACL system was launched in Consul 0.4, it was only possible to specify
|
||||||
policies for the KV store. In Consul 0.5, ACL policies were extended to service
|
policies for the KV store. In Consul 0.5, ACL policies were extended to service
|
||||||
registrations. In Consul 0.6, ACL's were further extended to restrict the
|
registrations. In Consul 0.6, ACL's were further extended to restrict service
|
||||||
service discovery mechanisms and user events..
|
discovery mechanisms, user events, and encryption keyring operations.
|
||||||
|
|
||||||
## ACL Design
|
## ACL Design
|
||||||
|
|
||||||
|
@ -147,6 +147,27 @@ event "" {
|
||||||
As always, the more secure way to handle user events is to explicitly grant
|
As always, the more secure way to handle user events is to explicitly grant
|
||||||
access to each API token based on the events they should be able to fire.
|
access to each API token based on the events they should be able to fire.
|
||||||
|
|
||||||
|
### Blacklist mode and Keyring Operations
|
||||||
|
|
||||||
|
Consul 0.6 and later supports securing the encryption keyring operations using
|
||||||
|
ACL's. Encryption is an optional component of the gossip layer. More information
|
||||||
|
about Consul's keyring operations can be found on the [keyring
|
||||||
|
command](/docs/commands/keyring.html) documentation page.
|
||||||
|
|
||||||
|
If your [`acl_default_policy`](/docs/agent/options.html#acl_default_policy) is
|
||||||
|
set to `deny`, then the `anonymous` token will not have access to read or write
|
||||||
|
to the encryption keyring. The keyring policy is yet another first-class citizen
|
||||||
|
in the ACL syntax. You can configure the anonymous token to have free reign over
|
||||||
|
the keyring using a policy like the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
keyring = "write"
|
||||||
|
```
|
||||||
|
|
||||||
|
Encryption keyring operations are sensitive and should be properly secured. It
|
||||||
|
is recommended that instead of configuring a wide-open policy like above, a
|
||||||
|
per-token policy is applied to maximize security.
|
||||||
|
|
||||||
### Bootstrapping ACLs
|
### Bootstrapping ACLs
|
||||||
|
|
||||||
Bootstrapping the ACL system is done by providing an initial [`acl_master_token`
|
Bootstrapping the ACL system is done by providing an initial [`acl_master_token`
|
||||||
|
@ -229,6 +250,9 @@ event "" {
|
||||||
event "destroy-" {
|
event "destroy-" {
|
||||||
policy = "deny"
|
policy = "deny"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Read-only mode for the encryption keyring by default (list only)
|
||||||
|
keyring = "read"
|
||||||
```
|
```
|
||||||
|
|
||||||
This is equivalent to the following JSON input:
|
This is equivalent to the following JSON input:
|
||||||
|
@ -261,7 +285,8 @@ This is equivalent to the following JSON input:
|
||||||
"destroy-": {
|
"destroy-": {
|
||||||
"policy": "deny"
|
"policy": "deny"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"keyring": "read"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue