mirror of https://github.com/status-im/consul.git
Merge pull request #2592 from hashicorp/acl-complete-node-session
Adds complete ACL coverage for nodes and sessions.
This commit is contained in:
commit
9853df3b8f
60
acl/acl.go
60
acl/acl.go
|
@ -95,6 +95,13 @@ type ACL interface {
|
||||||
// service
|
// service
|
||||||
ServiceWrite(string) bool
|
ServiceWrite(string) bool
|
||||||
|
|
||||||
|
// SessionRead checks for permission to read sessions for a given node.
|
||||||
|
SessionRead(string) bool
|
||||||
|
|
||||||
|
// SessionWrite checks for permission to create sessions for a given
|
||||||
|
// node.
|
||||||
|
SessionWrite(string) bool
|
||||||
|
|
||||||
// Snapshot checks for permission to take and restore snapshots.
|
// Snapshot checks for permission to take and restore snapshots.
|
||||||
Snapshot() bool
|
Snapshot() bool
|
||||||
}
|
}
|
||||||
|
@ -175,6 +182,14 @@ func (s *StaticACL) ServiceWrite(string) bool {
|
||||||
return s.defaultAllow
|
return s.defaultAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StaticACL) SessionRead(string) bool {
|
||||||
|
return s.defaultAllow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StaticACL) SessionWrite(string) bool {
|
||||||
|
return s.defaultAllow
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StaticACL) Snapshot() bool {
|
func (s *StaticACL) Snapshot() bool {
|
||||||
return s.allowManage
|
return s.allowManage
|
||||||
}
|
}
|
||||||
|
@ -224,6 +239,9 @@ type PolicyACL struct {
|
||||||
// serviceRules contains the service policies
|
// serviceRules contains the service policies
|
||||||
serviceRules *radix.Tree
|
serviceRules *radix.Tree
|
||||||
|
|
||||||
|
// sessionRules contains the session policies
|
||||||
|
sessionRules *radix.Tree
|
||||||
|
|
||||||
// eventRules contains the user event policies
|
// eventRules contains the user event policies
|
||||||
eventRules *radix.Tree
|
eventRules *radix.Tree
|
||||||
|
|
||||||
|
@ -247,6 +265,7 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
|
||||||
keyRules: radix.New(),
|
keyRules: radix.New(),
|
||||||
nodeRules: radix.New(),
|
nodeRules: radix.New(),
|
||||||
serviceRules: radix.New(),
|
serviceRules: radix.New(),
|
||||||
|
sessionRules: radix.New(),
|
||||||
eventRules: radix.New(),
|
eventRules: radix.New(),
|
||||||
preparedQueryRules: radix.New(),
|
preparedQueryRules: radix.New(),
|
||||||
}
|
}
|
||||||
|
@ -266,6 +285,11 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
|
||||||
p.serviceRules.Insert(sp.Name, sp.Policy)
|
p.serviceRules.Insert(sp.Name, sp.Policy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the session policy
|
||||||
|
for _, sp := range policy.Sessions {
|
||||||
|
p.sessionRules.Insert(sp.Node, sp.Policy)
|
||||||
|
}
|
||||||
|
|
||||||
// Load the event policy
|
// Load the event policy
|
||||||
for _, ep := range policy.Events {
|
for _, ep := range policy.Events {
|
||||||
p.eventRules.Insert(ep.Event, ep.Policy)
|
p.eventRules.Insert(ep.Event, ep.Policy)
|
||||||
|
@ -547,3 +571,39 @@ func (p *PolicyACL) ServiceWrite(name string) bool {
|
||||||
// No matching rule, use the parent.
|
// No matching rule, use the parent.
|
||||||
return p.parent.ServiceWrite(name)
|
return p.parent.ServiceWrite(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SessionRead checks for permission to read sessions for a given node.
|
||||||
|
func (p *PolicyACL) SessionRead(node string) bool {
|
||||||
|
// Check for an exact rule or catch-all
|
||||||
|
_, rule, ok := p.sessionRules.LongestPrefix(node)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
switch rule {
|
||||||
|
case PolicyRead, PolicyWrite:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matching rule, use the parent.
|
||||||
|
return p.parent.SessionRead(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionWrite checks for permission to create sessions for a given node.
|
||||||
|
func (p *PolicyACL) SessionWrite(node string) bool {
|
||||||
|
// Check for an exact rule or catch-all
|
||||||
|
_, rule, ok := p.sessionRules.LongestPrefix(node)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
switch rule {
|
||||||
|
case PolicyWrite:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matching rule, use the parent.
|
||||||
|
return p.parent.SessionWrite(node)
|
||||||
|
}
|
||||||
|
|
101
acl/acl_test.go
101
acl/acl_test.go
|
@ -83,6 +83,12 @@ func TestStaticACL(t *testing.T) {
|
||||||
if !all.ServiceWrite("foobar") {
|
if !all.ServiceWrite("foobar") {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
|
if !all.SessionRead("foobar") {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
|
if !all.SessionWrite("foobar") {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
if all.Snapshot() {
|
if all.Snapshot() {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
|
@ -141,6 +147,12 @@ func TestStaticACL(t *testing.T) {
|
||||||
if none.ServiceWrite("foobar") {
|
if none.ServiceWrite("foobar") {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
|
if none.SessionRead("foobar") {
|
||||||
|
t.Fatalf("should not allow")
|
||||||
|
}
|
||||||
|
if none.SessionWrite("foobar") {
|
||||||
|
t.Fatalf("should not allow")
|
||||||
|
}
|
||||||
if none.Snapshot() {
|
if none.Snapshot() {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
|
@ -193,6 +205,12 @@ func TestStaticACL(t *testing.T) {
|
||||||
if !manage.ServiceWrite("foobar") {
|
if !manage.ServiceWrite("foobar") {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
|
if !manage.SessionRead("foobar") {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
|
if !manage.SessionWrite("foobar") {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
if !manage.Snapshot() {
|
if !manage.Snapshot() {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
|
@ -661,3 +679,86 @@ func TestPolicyACL_Node(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPolicyACL_Session(t *testing.T) {
|
||||||
|
deny := DenyAll()
|
||||||
|
policyRoot := &Policy{
|
||||||
|
Sessions: []*SessionPolicy{
|
||||||
|
&SessionPolicy{
|
||||||
|
Node: "root-nope",
|
||||||
|
Policy: PolicyDeny,
|
||||||
|
},
|
||||||
|
&SessionPolicy{
|
||||||
|
Node: "root-ro",
|
||||||
|
Policy: PolicyRead,
|
||||||
|
},
|
||||||
|
&SessionPolicy{
|
||||||
|
Node: "root-rw",
|
||||||
|
Policy: PolicyWrite,
|
||||||
|
},
|
||||||
|
&SessionPolicy{
|
||||||
|
Node: "override",
|
||||||
|
Policy: PolicyDeny,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
root, err := New(deny, policyRoot)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
policy := &Policy{
|
||||||
|
Sessions: []*SessionPolicy{
|
||||||
|
&SessionPolicy{
|
||||||
|
Node: "child-nope",
|
||||||
|
Policy: PolicyDeny,
|
||||||
|
},
|
||||||
|
&SessionPolicy{
|
||||||
|
Node: "child-ro",
|
||||||
|
Policy: PolicyRead,
|
||||||
|
},
|
||||||
|
&SessionPolicy{
|
||||||
|
Node: "child-rw",
|
||||||
|
Policy: PolicyWrite,
|
||||||
|
},
|
||||||
|
&SessionPolicy{
|
||||||
|
Node: "override",
|
||||||
|
Policy: PolicyWrite,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
acl, err := New(root, policy)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sessioncase struct {
|
||||||
|
inp string
|
||||||
|
read bool
|
||||||
|
write bool
|
||||||
|
}
|
||||||
|
cases := []sessioncase{
|
||||||
|
{"nope", false, false},
|
||||||
|
{"root-nope", false, false},
|
||||||
|
{"root-ro", true, false},
|
||||||
|
{"root-rw", true, true},
|
||||||
|
{"root-nope-prefix", false, false},
|
||||||
|
{"root-ro-prefix", true, false},
|
||||||
|
{"root-rw-prefix", true, true},
|
||||||
|
{"child-nope", false, false},
|
||||||
|
{"child-ro", true, false},
|
||||||
|
{"child-rw", true, true},
|
||||||
|
{"child-nope-prefix", false, false},
|
||||||
|
{"child-ro-prefix", true, false},
|
||||||
|
{"child-rw-prefix", true, true},
|
||||||
|
{"override", true, true},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if c.read != acl.SessionRead(c.inp) {
|
||||||
|
t.Fatalf("Read fail: %#v", c)
|
||||||
|
}
|
||||||
|
if c.write != acl.SessionWrite(c.inp) {
|
||||||
|
t.Fatalf("Write fail: %#v", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ type Policy struct {
|
||||||
Keys []*KeyPolicy `hcl:"key,expand"`
|
Keys []*KeyPolicy `hcl:"key,expand"`
|
||||||
Nodes []*NodePolicy `hcl:"node,expand"`
|
Nodes []*NodePolicy `hcl:"node,expand"`
|
||||||
Services []*ServicePolicy `hcl:"service,expand"`
|
Services []*ServicePolicy `hcl:"service,expand"`
|
||||||
|
Sessions []*SessionPolicy `hcl:"session,expand"`
|
||||||
Events []*EventPolicy `hcl:"event,expand"`
|
Events []*EventPolicy `hcl:"event,expand"`
|
||||||
PreparedQueries []*PreparedQueryPolicy `hcl:"query,expand"`
|
PreparedQueries []*PreparedQueryPolicy `hcl:"query,expand"`
|
||||||
Keyring string `hcl:"keyring"`
|
Keyring string `hcl:"keyring"`
|
||||||
|
@ -55,6 +56,17 @@ func (s *ServicePolicy) GoString() string {
|
||||||
return fmt.Sprintf("%#v", *s)
|
return fmt.Sprintf("%#v", *s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SessionPolicy represents a policy for making sessions tied to specific node
|
||||||
|
// name prefixes.
|
||||||
|
type SessionPolicy struct {
|
||||||
|
Node string `hcl:",key"`
|
||||||
|
Policy string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SessionPolicy) GoString() string {
|
||||||
|
return fmt.Sprintf("%#v", *s)
|
||||||
|
}
|
||||||
|
|
||||||
// EventPolicy represents a user event policy.
|
// EventPolicy represents a user event policy.
|
||||||
type EventPolicy struct {
|
type EventPolicy struct {
|
||||||
Event string `hcl:",key"`
|
Event string `hcl:",key"`
|
||||||
|
@ -125,6 +137,13 @@ func Parse(rules string) (*Policy, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the session policies
|
||||||
|
for _, sp := range p.Sessions {
|
||||||
|
if !isPolicyValid(sp.Policy) {
|
||||||
|
return nil, fmt.Errorf("Invalid session policy: %#v", sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate the user event policies
|
// Validate the user event policies
|
||||||
for _, ep := range p.Events {
|
for _, ep := range p.Events {
|
||||||
if !isPolicyValid(ep.Policy) {
|
if !isPolicyValid(ep.Policy) {
|
||||||
|
|
|
@ -46,6 +46,12 @@ service "" {
|
||||||
service "foo" {
|
service "foo" {
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
|
session "foo" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
session "bar" {
|
||||||
|
policy = "deny"
|
||||||
|
}
|
||||||
query "" {
|
query "" {
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
|
@ -129,6 +135,16 @@ query "bar" {
|
||||||
Policy: PolicyRead,
|
Policy: PolicyRead,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Sessions: []*SessionPolicy{
|
||||||
|
&SessionPolicy{
|
||||||
|
Node: "foo",
|
||||||
|
Policy: PolicyWrite,
|
||||||
|
},
|
||||||
|
&SessionPolicy{
|
||||||
|
Node: "bar",
|
||||||
|
Policy: PolicyDeny,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := Parse(inp)
|
out, err := Parse(inp)
|
||||||
|
@ -199,6 +215,14 @@ func TestACLPolicy_Parse_JSON(t *testing.T) {
|
||||||
"foo": {
|
"foo": {
|
||||||
"policy": "read"
|
"policy": "read"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"session": {
|
||||||
|
"foo": {
|
||||||
|
"policy": "write"
|
||||||
|
},
|
||||||
|
"bar": {
|
||||||
|
"policy": "deny"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
exp := &Policy{
|
exp := &Policy{
|
||||||
|
@ -274,6 +298,16 @@ func TestACLPolicy_Parse_JSON(t *testing.T) {
|
||||||
Policy: PolicyRead,
|
Policy: PolicyRead,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Sessions: []*SessionPolicy{
|
||||||
|
&SessionPolicy{
|
||||||
|
Node: "foo",
|
||||||
|
Policy: PolicyWrite,
|
||||||
|
},
|
||||||
|
&SessionPolicy{
|
||||||
|
Node: "bar",
|
||||||
|
Policy: PolicyDeny,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := Parse(inp)
|
out, err := Parse(inp)
|
||||||
|
@ -331,6 +365,7 @@ func TestACLPolicy_Bad_Policy(t *testing.T) {
|
||||||
`operator = "nope"`,
|
`operator = "nope"`,
|
||||||
`query "" { policy = "nope" }`,
|
`query "" { policy = "nope" }`,
|
||||||
`service "" { policy = "nope" }`,
|
`service "" { policy = "nope" }`,
|
||||||
|
`session "" { policy = "nope" }`,
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
_, err := Parse(c)
|
_, err := Parse(c)
|
||||||
|
|
|
@ -45,6 +45,7 @@ func TestPreparedQuery(t *testing.T) {
|
||||||
|
|
||||||
// Create a simple prepared query.
|
// Create a simple prepared query.
|
||||||
def := &PreparedQueryDefinition{
|
def := &PreparedQueryDefinition{
|
||||||
|
Name: "test",
|
||||||
Service: ServiceQuery{
|
Service: ServiceQuery{
|
||||||
Service: "redis",
|
Service: "redis",
|
||||||
},
|
},
|
||||||
|
|
|
@ -227,8 +227,8 @@ func Create(config *Config, logOutput io.Writer, logWriter *logger.LogWriter,
|
||||||
Port: agent.config.Ports.Server,
|
Port: agent.config.Ports.Server,
|
||||||
Tags: []string{},
|
Tags: []string{},
|
||||||
}
|
}
|
||||||
// TODO (slackpad) - Plumb the "acl_agent_token" into here.
|
|
||||||
agent.state.AddService(&consulService, "")
|
agent.state.AddService(&consulService, agent.config.GetTokenForAgent())
|
||||||
} else {
|
} else {
|
||||||
err = agent.setupClient()
|
err = agent.setupClient()
|
||||||
agent.state.SetIface(agent.client)
|
agent.state.SetIface(agent.client)
|
||||||
|
@ -364,6 +364,9 @@ func (a *Agent) consulConfig() *consul.Config {
|
||||||
if a.config.ACLToken != "" {
|
if a.config.ACLToken != "" {
|
||||||
base.ACLToken = a.config.ACLToken
|
base.ACLToken = a.config.ACLToken
|
||||||
}
|
}
|
||||||
|
if a.config.ACLAgentToken != "" {
|
||||||
|
base.ACLAgentToken = a.config.ACLAgentToken
|
||||||
|
}
|
||||||
if a.config.ACLMasterToken != "" {
|
if a.config.ACLMasterToken != "" {
|
||||||
base.ACLMasterToken = a.config.ACLMasterToken
|
base.ACLMasterToken = a.config.ACLMasterToken
|
||||||
}
|
}
|
||||||
|
@ -815,14 +818,11 @@ func (a *Agent) sendCoordinate() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - Consider adding a distance check so we don't send
|
|
||||||
// an update if the position hasn't changed by more than a
|
|
||||||
// threshold.
|
|
||||||
req := structs.CoordinateUpdateRequest{
|
req := structs.CoordinateUpdateRequest{
|
||||||
Datacenter: a.config.Datacenter,
|
Datacenter: a.config.Datacenter,
|
||||||
Node: a.config.NodeName,
|
Node: a.config.NodeName,
|
||||||
Coord: c,
|
Coord: c,
|
||||||
WriteRequest: structs.WriteRequest{Token: a.config.ACLToken},
|
WriteRequest: structs.WriteRequest{Token: a.config.GetTokenForAgent()},
|
||||||
}
|
}
|
||||||
var reply struct{}
|
var reply struct{}
|
||||||
if err := a.RPC("Coordinate.Update", &req, &reply); err != nil {
|
if err := a.RPC("Coordinate.Update", &req, &reply); err != nil {
|
||||||
|
|
|
@ -488,6 +488,11 @@ type Config struct {
|
||||||
// token is not provided. If not configured the 'anonymous' token is used.
|
// token is not provided. If not configured the 'anonymous' token is used.
|
||||||
ACLToken string `mapstructure:"acl_token" json:"-"`
|
ACLToken string `mapstructure:"acl_token" json:"-"`
|
||||||
|
|
||||||
|
// ACLAgentToken is the default token used to make requests for the agent
|
||||||
|
// itself, such as for registering itself with the catalog. If not
|
||||||
|
// configured, the 'acl_token' will be used.
|
||||||
|
ACLAgentToken string `mapstructure:"acl_agent_token" json:"-"`
|
||||||
|
|
||||||
// ACLMasterToken is used to bootstrap the ACL system. It should be specified
|
// ACLMasterToken is used to bootstrap the ACL system. It should be specified
|
||||||
// on the servers in the ACLDatacenter. When the leader comes online, it ensures
|
// on the servers in the ACLDatacenter. When the leader comes online, it ensures
|
||||||
// that the Master token is available. This provides the initial token.
|
// that the Master token is available. This provides the initial token.
|
||||||
|
@ -756,6 +761,18 @@ func (c *Config) ClientListener(override string, port int) (net.Addr, error) {
|
||||||
return &net.TCPAddr{IP: ip, Port: port}, nil
|
return &net.TCPAddr{IP: ip, Port: port}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTokenForAgent returns the token the agent should use for its own internal
|
||||||
|
// operations, such as registering itself with the catalog.
|
||||||
|
func (c *Config) GetTokenForAgent() string {
|
||||||
|
if c.ACLAgentToken != "" {
|
||||||
|
return c.ACLAgentToken
|
||||||
|
} else if c.ACLToken != "" {
|
||||||
|
return c.ACLToken
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeConfig reads the configuration from the given reader in JSON
|
// DecodeConfig reads the configuration from the given reader in JSON
|
||||||
// format and decodes it into a proper Config structure.
|
// format and decodes it into a proper Config structure.
|
||||||
func DecodeConfig(r io.Reader) (*Config, error) {
|
func DecodeConfig(r io.Reader) (*Config, error) {
|
||||||
|
@ -1466,6 +1483,9 @@ func MergeConfig(a, b *Config) *Config {
|
||||||
if b.ACLToken != "" {
|
if b.ACLToken != "" {
|
||||||
result.ACLToken = b.ACLToken
|
result.ACLToken = b.ACLToken
|
||||||
}
|
}
|
||||||
|
if b.ACLAgentToken != "" {
|
||||||
|
result.ACLAgentToken = b.ACLAgentToken
|
||||||
|
}
|
||||||
if b.ACLMasterToken != "" {
|
if b.ACLMasterToken != "" {
|
||||||
result.ACLMasterToken = b.ACLMasterToken
|
result.ACLMasterToken = b.ACLMasterToken
|
||||||
}
|
}
|
||||||
|
|
|
@ -643,7 +643,7 @@ func TestDecodeConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACLs
|
// ACLs
|
||||||
input = `{"acl_token": "1234", "acl_datacenter": "dc2",
|
input = `{"acl_token": "1234", "acl_agent_token": "5678", "acl_datacenter": "dc2",
|
||||||
"acl_ttl": "60s", "acl_down_policy": "deny",
|
"acl_ttl": "60s", "acl_down_policy": "deny",
|
||||||
"acl_default_policy": "deny", "acl_master_token": "2345",
|
"acl_default_policy": "deny", "acl_master_token": "2345",
|
||||||
"acl_replication_token": "8675309"}`
|
"acl_replication_token": "8675309"}`
|
||||||
|
@ -655,6 +655,9 @@ func TestDecodeConfig(t *testing.T) {
|
||||||
if config.ACLToken != "1234" {
|
if config.ACLToken != "1234" {
|
||||||
t.Fatalf("bad: %#v", config)
|
t.Fatalf("bad: %#v", config)
|
||||||
}
|
}
|
||||||
|
if config.ACLAgentToken != "5678" {
|
||||||
|
t.Fatalf("bad: %#v", config)
|
||||||
|
}
|
||||||
if config.ACLMasterToken != "2345" {
|
if config.ACLMasterToken != "2345" {
|
||||||
t.Fatalf("bad: %#v", config)
|
t.Fatalf("bad: %#v", config)
|
||||||
}
|
}
|
||||||
|
@ -674,6 +677,32 @@ func TestDecodeConfig(t *testing.T) {
|
||||||
t.Fatalf("bad: %#v", config)
|
t.Fatalf("bad: %#v", config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ACL token precedence.
|
||||||
|
input = `{}`
|
||||||
|
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if token := config.GetTokenForAgent(); token != "" {
|
||||||
|
t.Fatalf("bad: %s", token)
|
||||||
|
}
|
||||||
|
input = `{"acl_token": "hello"}`
|
||||||
|
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if token := config.GetTokenForAgent(); token != "hello" {
|
||||||
|
t.Fatalf("bad: %s", token)
|
||||||
|
}
|
||||||
|
input = `{"acl_agent_token": "world", "acl_token": "hello"}`
|
||||||
|
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if token := config.GetTokenForAgent(); token != "world" {
|
||||||
|
t.Fatalf("bad: %s", token)
|
||||||
|
}
|
||||||
|
|
||||||
// ACL flag for Consul version 0.8 features (broken out since we will
|
// ACL flag for Consul version 0.8 features (broken out since we will
|
||||||
// eventually remove this). We first verify this is opt-out.
|
// eventually remove this). We first verify this is opt-out.
|
||||||
config = DefaultConfig()
|
config = DefaultConfig()
|
||||||
|
@ -1561,6 +1590,7 @@ func TestMergeConfig(t *testing.T) {
|
||||||
CheckUpdateInterval: 8 * time.Minute,
|
CheckUpdateInterval: 8 * time.Minute,
|
||||||
CheckUpdateIntervalRaw: "8m",
|
CheckUpdateIntervalRaw: "8m",
|
||||||
ACLToken: "1234",
|
ACLToken: "1234",
|
||||||
|
ACLAgentToken: "5678",
|
||||||
ACLMasterToken: "2345",
|
ACLMasterToken: "2345",
|
||||||
ACLDatacenter: "dc2",
|
ACLDatacenter: "dc2",
|
||||||
ACLTTL: 15 * time.Second,
|
ACLTTL: 15 * time.Second,
|
||||||
|
|
|
@ -569,6 +569,7 @@ func TestDNS_ServiceLookup(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "db",
|
Service: "db",
|
||||||
},
|
},
|
||||||
|
@ -1021,6 +1022,7 @@ func TestDNS_ServiceLookup_ServiceAddress(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "db",
|
Service: "db",
|
||||||
},
|
},
|
||||||
|
@ -1115,6 +1117,7 @@ func TestDNS_ServiceLookup_ServiceAddressIPV6(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "db",
|
Service: "db",
|
||||||
},
|
},
|
||||||
|
@ -1236,6 +1239,7 @@ func TestDNS_ServiceLookup_WanAddress(t *testing.T) {
|
||||||
Datacenter: "dc2",
|
Datacenter: "dc2",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "db",
|
Service: "db",
|
||||||
},
|
},
|
||||||
|
@ -1641,6 +1645,7 @@ func TestDNS_ServiceLookup_Dedup(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "db",
|
Service: "db",
|
||||||
},
|
},
|
||||||
|
@ -1745,6 +1750,7 @@ func TestDNS_ServiceLookup_Dedup_SRV(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "db",
|
Service: "db",
|
||||||
},
|
},
|
||||||
|
@ -2040,6 +2046,7 @@ func TestDNS_ServiceLookup_FilterCritical(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "db",
|
Service: "db",
|
||||||
},
|
},
|
||||||
|
@ -2163,6 +2170,7 @@ func TestDNS_ServiceLookup_OnlyFailing(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "db",
|
Service: "db",
|
||||||
},
|
},
|
||||||
|
@ -2283,6 +2291,7 @@ func TestDNS_ServiceLookup_OnlyPassing(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "db",
|
Service: "db",
|
||||||
OnlyPassing: true,
|
OnlyPassing: true,
|
||||||
|
@ -2356,6 +2365,7 @@ func TestDNS_ServiceLookup_Randomize(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "web",
|
Service: "web",
|
||||||
},
|
},
|
||||||
|
@ -2450,6 +2460,7 @@ func TestDNS_ServiceLookup_Truncate(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "web",
|
Service: "web",
|
||||||
},
|
},
|
||||||
|
@ -2770,6 +2781,7 @@ func TestDNS_ServiceLookup_CNAME(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "search",
|
Service: "search",
|
||||||
},
|
},
|
||||||
|
@ -4342,6 +4354,7 @@ func TestDNS_Compression_Query(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "db",
|
Service: "db",
|
||||||
},
|
},
|
||||||
|
|
|
@ -400,7 +400,7 @@ func (l *localState) setSyncState() error {
|
||||||
req := structs.NodeSpecificRequest{
|
req := structs.NodeSpecificRequest{
|
||||||
Datacenter: l.config.Datacenter,
|
Datacenter: l.config.Datacenter,
|
||||||
Node: l.config.NodeName,
|
Node: l.config.NodeName,
|
||||||
QueryOptions: structs.QueryOptions{Token: l.config.ACLToken},
|
QueryOptions: structs.QueryOptions{Token: l.config.GetTokenForAgent()},
|
||||||
}
|
}
|
||||||
var out1 structs.IndexedNodeServices
|
var out1 structs.IndexedNodeServices
|
||||||
var out2 structs.IndexedHealthChecks
|
var out2 structs.IndexedHealthChecks
|
||||||
|
@ -709,7 +709,7 @@ func (l *localState) syncNodeInfo() error {
|
||||||
Node: l.config.NodeName,
|
Node: l.config.NodeName,
|
||||||
Address: l.config.AdvertiseAddr,
|
Address: l.config.AdvertiseAddr,
|
||||||
TaggedAddresses: l.config.TaggedAddresses,
|
TaggedAddresses: l.config.TaggedAddresses,
|
||||||
WriteRequest: structs.WriteRequest{Token: l.config.ACLToken},
|
WriteRequest: structs.WriteRequest{Token: l.config.GetTokenForAgent()},
|
||||||
}
|
}
|
||||||
var out struct{}
|
var out struct{}
|
||||||
err := l.iface.RPC("Catalog.Register", &req, &out)
|
err := l.iface.RPC("Catalog.Register", &req, &out)
|
||||||
|
|
|
@ -46,6 +46,7 @@ func (s *HTTPServer) SessionCreate(resp http.ResponseWriter, req *http.Request)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
s.parseDC(req, &args.Datacenter)
|
s.parseDC(req, &args.Datacenter)
|
||||||
|
s.parseToken(req, &args.Token)
|
||||||
|
|
||||||
// Handle optional request body
|
// Handle optional request body
|
||||||
if req.ContentLength > 0 {
|
if req.ContentLength > 0 {
|
||||||
|
@ -117,6 +118,7 @@ func (s *HTTPServer) SessionDestroy(resp http.ResponseWriter, req *http.Request)
|
||||||
Op: structs.SessionDestroy,
|
Op: structs.SessionDestroy,
|
||||||
}
|
}
|
||||||
s.parseDC(req, &args.Datacenter)
|
s.parseDC(req, &args.Datacenter)
|
||||||
|
s.parseToken(req, &args.Token)
|
||||||
|
|
||||||
// Pull out the session id
|
// Pull out the session id
|
||||||
args.Session.ID = strings.TrimPrefix(req.URL.Path, "/v1/session/destroy/")
|
args.Session.ID = strings.TrimPrefix(req.URL.Path, "/v1/session/destroy/")
|
||||||
|
|
|
@ -344,13 +344,22 @@ func (f *aclFilter) allowService(service string) bool {
|
||||||
return f.acl.ServiceRead(service)
|
return f.acl.ServiceRead(service)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allowSession is used to determine if a session for a node is accessible for
|
||||||
|
// an ACL.
|
||||||
|
func (f *aclFilter) allowSession(node string) bool {
|
||||||
|
if !f.enforceVersion8 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return f.acl.SessionRead(node)
|
||||||
|
}
|
||||||
|
|
||||||
// filterHealthChecks is used to filter a set of health checks down based on
|
// filterHealthChecks is used to filter a set of health checks down based on
|
||||||
// the configured ACL rules for a token.
|
// the configured ACL rules for a token.
|
||||||
func (f *aclFilter) filterHealthChecks(checks *structs.HealthChecks) {
|
func (f *aclFilter) filterHealthChecks(checks *structs.HealthChecks) {
|
||||||
hc := *checks
|
hc := *checks
|
||||||
for i := 0; i < len(hc); i++ {
|
for i := 0; i < len(hc); i++ {
|
||||||
check := hc[i]
|
check := hc[i]
|
||||||
if f.allowService(check.ServiceName) {
|
if f.allowNode(check.Node) && f.allowService(check.ServiceName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f.logger.Printf("[DEBUG] consul: dropping check %q from result due to ACLs", check.CheckID)
|
f.logger.Printf("[DEBUG] consul: dropping check %q from result due to ACLs", check.CheckID)
|
||||||
|
@ -388,13 +397,22 @@ func (f *aclFilter) filterServiceNodes(nodes *structs.ServiceNodes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterNodeServices is used to filter services on a given node base on ACLs.
|
// filterNodeServices is used to filter services on a given node base on ACLs.
|
||||||
func (f *aclFilter) filterNodeServices(services *structs.NodeServices) {
|
func (f *aclFilter) filterNodeServices(services **structs.NodeServices) {
|
||||||
for svc, _ := range services.Services {
|
if *services == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.allowNode((*services).Node.Node) {
|
||||||
|
*services = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for svc, _ := range (*services).Services {
|
||||||
if f.allowService(svc) {
|
if f.allowService(svc) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
|
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
|
||||||
delete(services.Services, svc)
|
delete((*services).Services, svc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,7 +421,7 @@ func (f *aclFilter) filterCheckServiceNodes(nodes *structs.CheckServiceNodes) {
|
||||||
csn := *nodes
|
csn := *nodes
|
||||||
for i := 0; i < len(csn); i++ {
|
for i := 0; i < len(csn); i++ {
|
||||||
node := csn[i]
|
node := csn[i]
|
||||||
if f.allowService(node.Service.Service) {
|
if f.allowNode(node.Node.Node) && f.allowService(node.Service.Service) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node.Node.Node)
|
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node.Node.Node)
|
||||||
|
@ -413,6 +431,37 @@ func (f *aclFilter) filterCheckServiceNodes(nodes *structs.CheckServiceNodes) {
|
||||||
*nodes = csn
|
*nodes = csn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filterSessions is used to filter a set of sessions based on ACLs.
|
||||||
|
func (f *aclFilter) filterSessions(sessions *structs.Sessions) {
|
||||||
|
s := *sessions
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
session := s[i]
|
||||||
|
if f.allowSession(session.Node) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.logger.Printf("[DEBUG] consul: dropping session %q from result due to ACLs", session.ID)
|
||||||
|
s = append(s[:i], s[i+1:]...)
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
*sessions = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterCoordinates is used to filter nodes in a coordinate dump based on ACL
|
||||||
|
// rules.
|
||||||
|
func (f *aclFilter) filterCoordinates(coords *structs.Coordinates) {
|
||||||
|
c := *coords
|
||||||
|
for i := 0; i < len(c); i++ {
|
||||||
|
node := c[i].Node
|
||||||
|
if f.allowNode(node) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node)
|
||||||
|
c = append(c[:i], c[i+1:]...)
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
*coords = c
|
||||||
|
}
|
||||||
|
|
||||||
// filterNodeDump is used to filter through all parts of a node dump and
|
// filterNodeDump is used to filter through all parts of a node dump and
|
||||||
// remove elements the provided ACL token cannot access.
|
// remove elements the provided ACL token cannot access.
|
||||||
func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
|
func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
|
||||||
|
@ -420,26 +469,34 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
|
||||||
for i := 0; i < len(nd); i++ {
|
for i := 0; i < len(nd); i++ {
|
||||||
info := nd[i]
|
info := nd[i]
|
||||||
|
|
||||||
|
// Filter nodes
|
||||||
|
if node := info.Node; !f.allowNode(node) {
|
||||||
|
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node)
|
||||||
|
nd = append(nd[:i], nd[i+1:]...)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Filter services
|
// Filter services
|
||||||
for i := 0; i < len(info.Services); i++ {
|
for j := 0; j < len(info.Services); j++ {
|
||||||
svc := info.Services[i].Service
|
svc := info.Services[j].Service
|
||||||
if f.allowService(svc) {
|
if f.allowService(svc) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
|
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
|
||||||
info.Services = append(info.Services[:i], info.Services[i+1:]...)
|
info.Services = append(info.Services[:j], info.Services[j+1:]...)
|
||||||
i--
|
j--
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter checks
|
// Filter checks
|
||||||
for i := 0; i < len(info.Checks); i++ {
|
for j := 0; j < len(info.Checks); j++ {
|
||||||
chk := info.Checks[i]
|
chk := info.Checks[j]
|
||||||
if f.allowService(chk.ServiceName) {
|
if f.allowService(chk.ServiceName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f.logger.Printf("[DEBUG] consul: dropping check %q from result due to ACLs", chk.CheckID)
|
f.logger.Printf("[DEBUG] consul: dropping check %q from result due to ACLs", chk.CheckID)
|
||||||
info.Checks = append(info.Checks[:i], info.Checks[i+1:]...)
|
info.Checks = append(info.Checks[:j], info.Checks[j+1:]...)
|
||||||
i--
|
j--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*dump = nd
|
*dump = nd
|
||||||
|
@ -448,14 +505,10 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
|
||||||
// filterNodes is used to filter through all parts of a node list and remove
|
// filterNodes is used to filter through all parts of a node list and remove
|
||||||
// elements the provided ACL token cannot access.
|
// elements the provided ACL token cannot access.
|
||||||
func (f *aclFilter) filterNodes(nodes *structs.Nodes) {
|
func (f *aclFilter) filterNodes(nodes *structs.Nodes) {
|
||||||
if !f.enforceVersion8 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n := *nodes
|
n := *nodes
|
||||||
for i := 0; i < len(n); i++ {
|
for i := 0; i < len(n); i++ {
|
||||||
node := n[i].Node
|
node := n[i].Node
|
||||||
if f.acl.NodeRead(node) {
|
if f.allowNode(node) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node)
|
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node)
|
||||||
|
@ -548,6 +601,9 @@ func (s *Server) filterACL(token string, subj interface{}) error {
|
||||||
case *structs.IndexedCheckServiceNodes:
|
case *structs.IndexedCheckServiceNodes:
|
||||||
filt.filterCheckServiceNodes(&v.Nodes)
|
filt.filterCheckServiceNodes(&v.Nodes)
|
||||||
|
|
||||||
|
case *structs.IndexedCoordinates:
|
||||||
|
filt.filterCoordinates(&v.Coordinates)
|
||||||
|
|
||||||
case *structs.IndexedHealthChecks:
|
case *structs.IndexedHealthChecks:
|
||||||
filt.filterHealthChecks(&v.HealthChecks)
|
filt.filterHealthChecks(&v.HealthChecks)
|
||||||
|
|
||||||
|
@ -558,9 +614,7 @@ func (s *Server) filterACL(token string, subj interface{}) error {
|
||||||
filt.filterNodes(&v.Nodes)
|
filt.filterNodes(&v.Nodes)
|
||||||
|
|
||||||
case *structs.IndexedNodeServices:
|
case *structs.IndexedNodeServices:
|
||||||
if v.NodeServices != nil {
|
filt.filterNodeServices(&v.NodeServices)
|
||||||
filt.filterNodeServices(v.NodeServices)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *structs.IndexedServiceNodes:
|
case *structs.IndexedServiceNodes:
|
||||||
filt.filterServiceNodes(&v.ServiceNodes)
|
filt.filterServiceNodes(&v.ServiceNodes)
|
||||||
|
@ -568,6 +622,9 @@ func (s *Server) filterACL(token string, subj interface{}) error {
|
||||||
case *structs.IndexedServices:
|
case *structs.IndexedServices:
|
||||||
filt.filterServices(v.Services)
|
filt.filterServices(v.Services)
|
||||||
|
|
||||||
|
case *structs.IndexedSessions:
|
||||||
|
filt.filterSessions(&v.Sessions)
|
||||||
|
|
||||||
case *structs.IndexedPreparedQueries:
|
case *structs.IndexedPreparedQueries:
|
||||||
filt.filterPreparedQueries(&v.Queries)
|
filt.filterPreparedQueries(&v.Queries)
|
||||||
|
|
||||||
|
|
|
@ -808,27 +808,93 @@ func TestACL_MultiDC_Found(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestACL_filterHealthChecks(t *testing.T) {
|
func TestACL_filterHealthChecks(t *testing.T) {
|
||||||
// Create some health checks
|
// Create some health checks.
|
||||||
hc := structs.HealthChecks{
|
fill := func() structs.HealthChecks {
|
||||||
&structs.HealthCheck{
|
return structs.HealthChecks{
|
||||||
Node: "node1",
|
&structs.HealthCheck{
|
||||||
CheckID: "check1",
|
Node: "node1",
|
||||||
ServiceName: "foo",
|
CheckID: "check1",
|
||||||
},
|
ServiceName: "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try permissive filtering
|
// Try permissive filtering.
|
||||||
filt := newAclFilter(acl.AllowAll(), nil, false)
|
{
|
||||||
filt.filterHealthChecks(&hc)
|
hc := fill()
|
||||||
if len(hc) != 1 {
|
filt := newAclFilter(acl.AllowAll(), nil, false)
|
||||||
t.Fatalf("bad: %#v", hc)
|
filt.filterHealthChecks(&hc)
|
||||||
|
if len(hc) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", hc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try restrictive filtering
|
// Try restrictive filtering.
|
||||||
filt = newAclFilter(acl.DenyAll(), nil, false)
|
{
|
||||||
filt.filterHealthChecks(&hc)
|
hc := fill()
|
||||||
if len(hc) != 0 {
|
filt := newAclFilter(acl.DenyAll(), nil, false)
|
||||||
t.Fatalf("bad: %#v", hc)
|
filt.filterHealthChecks(&hc)
|
||||||
|
if len(hc) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", hc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowed to see the service but not the node.
|
||||||
|
policy, err := acl.Parse(`
|
||||||
|
service "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %v", err)
|
||||||
|
}
|
||||||
|
perms, err := acl.New(acl.DenyAll(), policy)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will work because version 8 ACLs aren't being enforced.
|
||||||
|
{
|
||||||
|
hc := fill()
|
||||||
|
filt := newAclFilter(perms, nil, false)
|
||||||
|
filt.filterHealthChecks(&hc)
|
||||||
|
if len(hc) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", hc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// But with version 8 the node will block it.
|
||||||
|
{
|
||||||
|
hc := fill()
|
||||||
|
filt := newAclFilter(perms, nil, true)
|
||||||
|
filt.filterHealthChecks(&hc)
|
||||||
|
if len(hc) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", hc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chain on access to the node.
|
||||||
|
policy, err = acl.Parse(`
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %v", err)
|
||||||
|
}
|
||||||
|
perms, err = acl.New(perms, policy)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now it should go through.
|
||||||
|
{
|
||||||
|
hc := fill()
|
||||||
|
filt := newAclFilter(perms, nil, true)
|
||||||
|
filt.filterHealthChecks(&hc)
|
||||||
|
if len(hc) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", hc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -899,7 +965,7 @@ service "foo" {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This will work because version 8 ACLs aren't being enforced.
|
// This will work because version 8 ACLs aren't being enforced.
|
||||||
{
|
{
|
||||||
nodes := fill()
|
nodes := fill()
|
||||||
filt := newAclFilter(perms, nil, false)
|
filt := newAclFilter(perms, nil, false)
|
||||||
|
@ -945,118 +1011,412 @@ node "node1" {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestACL_filterNodeServices(t *testing.T) {
|
func TestACL_filterNodeServices(t *testing.T) {
|
||||||
// Create some node services
|
// Create some node services.
|
||||||
services := structs.NodeServices{
|
fill := func() *structs.NodeServices {
|
||||||
Node: &structs.Node{
|
return &structs.NodeServices{
|
||||||
Node: "node1",
|
|
||||||
},
|
|
||||||
Services: map[string]*structs.NodeService{
|
|
||||||
"foo": &structs.NodeService{
|
|
||||||
ID: "foo",
|
|
||||||
Service: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try permissive filtering
|
|
||||||
filt := newAclFilter(acl.AllowAll(), nil, false)
|
|
||||||
filt.filterNodeServices(&services)
|
|
||||||
if len(services.Services) != 1 {
|
|
||||||
t.Fatalf("bad: %#v", services.Services)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try restrictive filtering
|
|
||||||
filt = newAclFilter(acl.DenyAll(), nil, false)
|
|
||||||
filt.filterNodeServices(&services)
|
|
||||||
if len(services.Services) != 0 {
|
|
||||||
t.Fatalf("bad: %#v", services.Services)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestACL_filterCheckServiceNodes(t *testing.T) {
|
|
||||||
// Create some nodes
|
|
||||||
nodes := structs.CheckServiceNodes{
|
|
||||||
structs.CheckServiceNode{
|
|
||||||
Node: &structs.Node{
|
Node: &structs.Node{
|
||||||
Node: "node1",
|
Node: "node1",
|
||||||
},
|
},
|
||||||
Service: &structs.NodeService{
|
Services: map[string]*structs.NodeService{
|
||||||
ID: "foo",
|
"foo": &structs.NodeService{
|
||||||
Service: "foo",
|
|
||||||
},
|
|
||||||
Checks: structs.HealthChecks{
|
|
||||||
&structs.HealthCheck{
|
|
||||||
Node: "node1",
|
|
||||||
CheckID: "check1",
|
|
||||||
ServiceName: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try permissive filtering
|
|
||||||
filt := newAclFilter(acl.AllowAll(), nil, false)
|
|
||||||
filt.filterCheckServiceNodes(&nodes)
|
|
||||||
if len(nodes) != 1 {
|
|
||||||
t.Fatalf("bad: %#v", nodes)
|
|
||||||
}
|
|
||||||
if len(nodes[0].Checks) != 1 {
|
|
||||||
t.Fatalf("bad: %#v", nodes[0].Checks)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try restrictive filtering
|
|
||||||
filt = newAclFilter(acl.DenyAll(), nil, false)
|
|
||||||
filt.filterCheckServiceNodes(&nodes)
|
|
||||||
if len(nodes) != 0 {
|
|
||||||
t.Fatalf("bad: %#v", nodes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestACL_filterNodeDump(t *testing.T) {
|
|
||||||
// Create a node dump
|
|
||||||
dump := structs.NodeDump{
|
|
||||||
&structs.NodeInfo{
|
|
||||||
Node: "node1",
|
|
||||||
Services: []*structs.NodeService{
|
|
||||||
&structs.NodeService{
|
|
||||||
ID: "foo",
|
ID: "foo",
|
||||||
Service: "foo",
|
Service: "foo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Checks: []*structs.HealthCheck{
|
}
|
||||||
&structs.HealthCheck{
|
}
|
||||||
Node: "node1",
|
|
||||||
CheckID: "check1",
|
// Try nil, which is a possible input.
|
||||||
ServiceName: "foo",
|
{
|
||||||
|
var services *structs.NodeServices
|
||||||
|
filt := newAclFilter(acl.AllowAll(), nil, false)
|
||||||
|
filt.filterNodeServices(&services)
|
||||||
|
if services != nil {
|
||||||
|
t.Fatalf("bad: %#v", services)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try permissive filtering.
|
||||||
|
{
|
||||||
|
services := fill()
|
||||||
|
filt := newAclFilter(acl.AllowAll(), nil, false)
|
||||||
|
filt.filterNodeServices(&services)
|
||||||
|
if len(services.Services) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", services.Services)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try restrictive filtering.
|
||||||
|
{
|
||||||
|
services := fill()
|
||||||
|
filt := newAclFilter(acl.DenyAll(), nil, false)
|
||||||
|
filt.filterNodeServices(&services)
|
||||||
|
if len((*services).Services) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", (*services).Services)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowed to see the service but not the node.
|
||||||
|
policy, err := acl.Parse(`
|
||||||
|
service "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %v", err)
|
||||||
|
}
|
||||||
|
perms, err := acl.New(acl.DenyAll(), policy)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will work because version 8 ACLs aren't being enforced.
|
||||||
|
{
|
||||||
|
services := fill()
|
||||||
|
filt := newAclFilter(perms, nil, false)
|
||||||
|
filt.filterNodeServices(&services)
|
||||||
|
if len((*services).Services) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", (*services).Services)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// But with version 8 the node will block it.
|
||||||
|
{
|
||||||
|
services := fill()
|
||||||
|
filt := newAclFilter(perms, nil, true)
|
||||||
|
filt.filterNodeServices(&services)
|
||||||
|
if services != nil {
|
||||||
|
t.Fatalf("bad: %#v", services)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chain on access to the node.
|
||||||
|
policy, err = acl.Parse(`
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %v", err)
|
||||||
|
}
|
||||||
|
perms, err = acl.New(perms, policy)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now it should go through.
|
||||||
|
{
|
||||||
|
services := fill()
|
||||||
|
filt := newAclFilter(perms, nil, true)
|
||||||
|
filt.filterNodeServices(&services)
|
||||||
|
if len((*services).Services) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", (*services).Services)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestACL_filterCheckServiceNodes(t *testing.T) {
|
||||||
|
// Create some nodes.
|
||||||
|
fill := func() structs.CheckServiceNodes {
|
||||||
|
return structs.CheckServiceNodes{
|
||||||
|
structs.CheckServiceNode{
|
||||||
|
Node: &structs.Node{
|
||||||
|
Node: "node1",
|
||||||
|
},
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
ID: "foo",
|
||||||
|
Service: "foo",
|
||||||
|
},
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "node1",
|
||||||
|
CheckID: "check1",
|
||||||
|
ServiceName: "foo",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try permissive filtering.
|
||||||
|
{
|
||||||
|
nodes := fill()
|
||||||
|
filt := newAclFilter(acl.AllowAll(), nil, false)
|
||||||
|
filt.filterCheckServiceNodes(&nodes)
|
||||||
|
if len(nodes) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", nodes)
|
||||||
|
}
|
||||||
|
if len(nodes[0].Checks) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", nodes[0].Checks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try restrictive filtering.
|
||||||
|
{
|
||||||
|
nodes := fill()
|
||||||
|
filt := newAclFilter(acl.DenyAll(), nil, false)
|
||||||
|
filt.filterCheckServiceNodes(&nodes)
|
||||||
|
if len(nodes) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", nodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowed to see the service but not the node.
|
||||||
|
policy, err := acl.Parse(`
|
||||||
|
service "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %v", err)
|
||||||
|
}
|
||||||
|
perms, err := acl.New(acl.DenyAll(), policy)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will work because version 8 ACLs aren't being enforced.
|
||||||
|
{
|
||||||
|
nodes := fill()
|
||||||
|
filt := newAclFilter(perms, nil, false)
|
||||||
|
filt.filterCheckServiceNodes(&nodes)
|
||||||
|
if len(nodes) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", nodes)
|
||||||
|
}
|
||||||
|
if len(nodes[0].Checks) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", nodes[0].Checks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// But with version 8 the node will block it.
|
||||||
|
{
|
||||||
|
nodes := fill()
|
||||||
|
filt := newAclFilter(perms, nil, true)
|
||||||
|
filt.filterCheckServiceNodes(&nodes)
|
||||||
|
if len(nodes) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", nodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chain on access to the node.
|
||||||
|
policy, err = acl.Parse(`
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %v", err)
|
||||||
|
}
|
||||||
|
perms, err = acl.New(perms, policy)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now it should go through.
|
||||||
|
{
|
||||||
|
nodes := fill()
|
||||||
|
filt := newAclFilter(perms, nil, true)
|
||||||
|
filt.filterCheckServiceNodes(&nodes)
|
||||||
|
if len(nodes) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", nodes)
|
||||||
|
}
|
||||||
|
if len(nodes[0].Checks) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", nodes[0].Checks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestACL_filterCoordinates(t *testing.T) {
|
||||||
|
// Create some coordinates.
|
||||||
|
coords := structs.Coordinates{
|
||||||
|
&structs.Coordinate{
|
||||||
|
Node: "node1",
|
||||||
|
Coord: generateRandomCoordinate(),
|
||||||
|
},
|
||||||
|
&structs.Coordinate{
|
||||||
|
Node: "node2",
|
||||||
|
Coord: generateRandomCoordinate(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try permissive filtering
|
// Try permissive filtering.
|
||||||
filt := newAclFilter(acl.AllowAll(), nil, false)
|
filt := newAclFilter(acl.AllowAll(), nil, false)
|
||||||
filt.filterNodeDump(&dump)
|
filt.filterCoordinates(&coords)
|
||||||
if len(dump) != 1 {
|
if len(coords) != 2 {
|
||||||
t.Fatalf("bad: %#v", dump)
|
t.Fatalf("bad: %#v", coords)
|
||||||
}
|
|
||||||
if len(dump[0].Services) != 1 {
|
|
||||||
t.Fatalf("bad: %#v", dump[0].Services)
|
|
||||||
}
|
|
||||||
if len(dump[0].Checks) != 1 {
|
|
||||||
t.Fatalf("bad: %#v", dump[0].Checks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try restrictive filtering
|
// Try restrictive filtering without version 8 ACL enforcement.
|
||||||
filt = newAclFilter(acl.DenyAll(), nil, false)
|
filt = newAclFilter(acl.DenyAll(), nil, false)
|
||||||
filt.filterNodeDump(&dump)
|
filt.filterCoordinates(&coords)
|
||||||
if len(dump) != 1 {
|
if len(coords) != 2 {
|
||||||
t.Fatalf("bad: %#v", dump)
|
t.Fatalf("bad: %#v", coords)
|
||||||
}
|
}
|
||||||
if len(dump[0].Services) != 0 {
|
|
||||||
t.Fatalf("bad: %#v", dump[0].Services)
|
// Try restrictive filtering with version 8 ACL enforcement.
|
||||||
|
filt = newAclFilter(acl.DenyAll(), nil, true)
|
||||||
|
filt.filterCoordinates(&coords)
|
||||||
|
if len(coords) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", coords)
|
||||||
}
|
}
|
||||||
if len(dump[0].Checks) != 0 {
|
}
|
||||||
t.Fatalf("bad: %#v", dump[0].Checks)
|
|
||||||
|
func TestACL_filterSessions(t *testing.T) {
|
||||||
|
// Create a session list.
|
||||||
|
sessions := structs.Sessions{
|
||||||
|
&structs.Session{
|
||||||
|
Node: "foo",
|
||||||
|
},
|
||||||
|
&structs.Session{
|
||||||
|
Node: "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try permissive filtering.
|
||||||
|
filt := newAclFilter(acl.AllowAll(), nil, true)
|
||||||
|
filt.filterSessions(&sessions)
|
||||||
|
if len(sessions) != 2 {
|
||||||
|
t.Fatalf("bad: %#v", sessions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try restrictive filtering but with version 8 enforcement turned off.
|
||||||
|
filt = newAclFilter(acl.DenyAll(), nil, false)
|
||||||
|
filt.filterSessions(&sessions)
|
||||||
|
if len(sessions) != 2 {
|
||||||
|
t.Fatalf("bad: %#v", sessions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try restrictive filtering with version 8 enforcement turned on.
|
||||||
|
filt = newAclFilter(acl.DenyAll(), nil, true)
|
||||||
|
filt.filterSessions(&sessions)
|
||||||
|
if len(sessions) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", sessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestACL_filterNodeDump(t *testing.T) {
|
||||||
|
// Create a node dump.
|
||||||
|
fill := func() structs.NodeDump {
|
||||||
|
return structs.NodeDump{
|
||||||
|
&structs.NodeInfo{
|
||||||
|
Node: "node1",
|
||||||
|
Services: []*structs.NodeService{
|
||||||
|
&structs.NodeService{
|
||||||
|
ID: "foo",
|
||||||
|
Service: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Checks: []*structs.HealthCheck{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "node1",
|
||||||
|
CheckID: "check1",
|
||||||
|
ServiceName: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try permissive filtering.
|
||||||
|
{
|
||||||
|
dump := fill()
|
||||||
|
filt := newAclFilter(acl.AllowAll(), nil, false)
|
||||||
|
filt.filterNodeDump(&dump)
|
||||||
|
if len(dump) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", dump)
|
||||||
|
}
|
||||||
|
if len(dump[0].Services) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", dump[0].Services)
|
||||||
|
}
|
||||||
|
if len(dump[0].Checks) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", dump[0].Checks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try restrictive filtering.
|
||||||
|
{
|
||||||
|
dump := fill()
|
||||||
|
filt := newAclFilter(acl.DenyAll(), nil, false)
|
||||||
|
filt.filterNodeDump(&dump)
|
||||||
|
if len(dump) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", dump)
|
||||||
|
}
|
||||||
|
if len(dump[0].Services) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", dump[0].Services)
|
||||||
|
}
|
||||||
|
if len(dump[0].Checks) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", dump[0].Checks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowed to see the service but not the node.
|
||||||
|
policy, err := acl.Parse(`
|
||||||
|
service "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %v", err)
|
||||||
|
}
|
||||||
|
perms, err := acl.New(acl.DenyAll(), policy)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will work because version 8 ACLs aren't being enforced.
|
||||||
|
{
|
||||||
|
dump := fill()
|
||||||
|
filt := newAclFilter(perms, nil, false)
|
||||||
|
filt.filterNodeDump(&dump)
|
||||||
|
if len(dump) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", dump)
|
||||||
|
}
|
||||||
|
if len(dump[0].Services) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", dump[0].Services)
|
||||||
|
}
|
||||||
|
if len(dump[0].Checks) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", dump[0].Checks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// But with version 8 the node will block it.
|
||||||
|
{
|
||||||
|
dump := fill()
|
||||||
|
filt := newAclFilter(perms, nil, true)
|
||||||
|
filt.filterNodeDump(&dump)
|
||||||
|
if len(dump) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", dump)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chain on access to the node.
|
||||||
|
policy, err = acl.Parse(`
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %v", err)
|
||||||
|
}
|
||||||
|
perms, err = acl.New(perms, policy)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now it should go through.
|
||||||
|
{
|
||||||
|
dump := fill()
|
||||||
|
filt := newAclFilter(perms, nil, true)
|
||||||
|
filt.filterNodeDump(&dump)
|
||||||
|
if len(dump) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", dump)
|
||||||
|
}
|
||||||
|
if len(dump[0].Services) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", dump[0].Services)
|
||||||
|
}
|
||||||
|
if len(dump[0].Checks) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", dump[0].Checks)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -271,20 +271,6 @@ func (c *Catalog) NodeServices(args *structs.NodeSpecificRequest, reply *structs
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node read access is required with version 8 ACLs. We
|
|
||||||
// just return the same response as if the node doesn't
|
|
||||||
// exist, which is consistent with how the rest of the
|
|
||||||
// catalog filtering works and doesn't disclose the node.
|
|
||||||
acl, err := c.srv.resolveToken(args.Token)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if acl != nil && c.srv.config.ACLEnforceVersion8 {
|
|
||||||
if !acl.NodeRead(args.Node) {
|
|
||||||
return permissionDeniedErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reply.Index, reply.NodeServices = index, services
|
reply.Index, reply.NodeServices = index, services
|
||||||
return c.srv.filterACL(args.Token, reply)
|
return c.srv.filterACL(args.Token, reply)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1537,10 +1537,12 @@ func TestCatalog_NodeServices_ACLDeny(t *testing.T) {
|
||||||
|
|
||||||
// Now turn on version 8 enforcement and try again.
|
// Now turn on version 8 enforcement and try again.
|
||||||
s1.config.ACLEnforceVersion8 = true
|
s1.config.ACLEnforceVersion8 = true
|
||||||
err := msgpackrpc.CallWithCodec(codec, "Catalog.NodeServices", &args, &reply)
|
if err := msgpackrpc.CallWithCodec(codec, "Catalog.NodeServices", &args, &reply); err != nil {
|
||||||
if err == nil || !strings.Contains(err.Error(), permissionDenied) {
|
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
if reply.NodeServices != nil {
|
||||||
|
t.Fatalf("should not nil")
|
||||||
|
}
|
||||||
|
|
||||||
// Create an ACL that can read the node.
|
// Create an ACL that can read the node.
|
||||||
arg := structs.ACLRequest{
|
arg := structs.ACLRequest{
|
||||||
|
@ -1570,6 +1572,15 @@ node "%s" {
|
||||||
if reply.NodeServices == nil {
|
if reply.NodeServices == nil {
|
||||||
t.Fatalf("should not be nil")
|
t.Fatalf("should not be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure an unknown node doesn't cause trouble.
|
||||||
|
args.Node = "nope"
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Catalog.NodeServices", &args, &reply); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if reply.NodeServices != nil {
|
||||||
|
t.Fatalf("should not nil")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCatalog_NodeServices_FilterACL(t *testing.T) {
|
func TestCatalog_NodeServices_FilterACL(t *testing.T) {
|
||||||
|
|
|
@ -155,6 +155,11 @@ type Config struct {
|
||||||
// backwards compatibility as well.
|
// backwards compatibility as well.
|
||||||
ACLToken string
|
ACLToken string
|
||||||
|
|
||||||
|
// ACLAgentToken is the default token used to make requests for the agent
|
||||||
|
// itself, such as for registering itself with the catalog. If not
|
||||||
|
// configured, the ACLToken will be used.
|
||||||
|
ACLAgentToken string
|
||||||
|
|
||||||
// ACLMasterToken is used to bootstrap the ACL system. It should be specified
|
// ACLMasterToken is used to bootstrap the ACL system. It should be specified
|
||||||
// on the servers in the ACLDatacenter. When the leader comes online, it ensures
|
// on the servers in the ACLDatacenter. When the leader comes online, it ensures
|
||||||
// that the Master token is available. This provides the initial token.
|
// that the Master token is available. This provides the initial token.
|
||||||
|
@ -373,6 +378,7 @@ func (c *Config) ScaleRaft(raftMultRaw uint) {
|
||||||
c.RaftConfig.LeaderLeaseTimeout = raftMult * def.LeaderLeaseTimeout
|
c.RaftConfig.LeaderLeaseTimeout = raftMult * def.LeaderLeaseTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tlsConfig maps this config into a tlsutil config.
|
||||||
func (c *Config) tlsConfig() *tlsutil.Config {
|
func (c *Config) tlsConfig() *tlsutil.Config {
|
||||||
tlsConf := &tlsutil.Config{
|
tlsConf := &tlsutil.Config{
|
||||||
VerifyIncoming: c.VerifyIncoming,
|
VerifyIncoming: c.VerifyIncoming,
|
||||||
|
@ -387,3 +393,15 @@ func (c *Config) tlsConfig() *tlsutil.Config {
|
||||||
}
|
}
|
||||||
return tlsConf
|
return tlsConf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTokenForAgent returns the token the agent should use for its own internal
|
||||||
|
// operations, such as registering itself with the catalog.
|
||||||
|
func (c *Config) GetTokenForAgent() string {
|
||||||
|
if c.ACLAgentToken != "" {
|
||||||
|
return c.ACLAgentToken
|
||||||
|
} else if c.ACLToken != "" {
|
||||||
|
return c.ACLToken
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package consul
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig_GetTokenForAgent(t *testing.T) {
|
||||||
|
config := DefaultConfig()
|
||||||
|
if token := config.GetTokenForAgent(); token != "" {
|
||||||
|
t.Fatalf("bad: %s", token)
|
||||||
|
}
|
||||||
|
config.ACLToken = "hello"
|
||||||
|
if token := config.GetTokenForAgent(); token != "hello" {
|
||||||
|
t.Fatalf("bad: %s", token)
|
||||||
|
}
|
||||||
|
config.ACLAgentToken = "world"
|
||||||
|
if token := config.GetTokenForAgent(); token != "world" {
|
||||||
|
t.Fatalf("bad: %s", token)
|
||||||
|
}
|
||||||
|
}
|
|
@ -119,6 +119,17 @@ func (c *Coordinate) Update(args *structs.CoordinateUpdateRequest, reply *struct
|
||||||
return fmt.Errorf("rejected bad coordinate: %v", args.Coord)
|
return fmt.Errorf("rejected bad coordinate: %v", args.Coord)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch the ACL token, if any, and enforce the node policy if enabled.
|
||||||
|
acl, err := c.srv.resolveToken(args.Token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if acl != nil && c.srv.config.ACLEnforceVersion8 {
|
||||||
|
if !acl.NodeWrite(args.Node) {
|
||||||
|
return permissionDeniedErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add the coordinate to the map of pending updates.
|
// Add the coordinate to the map of pending updates.
|
||||||
c.updatesLock.Lock()
|
c.updatesLock.Lock()
|
||||||
c.updates[args.Node] = args.Coord
|
c.updates[args.Node] = args.Coord
|
||||||
|
@ -173,6 +184,9 @@ func (c *Coordinate) ListNodes(args *structs.DCSpecificRequest, reply *structs.I
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Index, reply.Coordinates = index, coords
|
reply.Index, reply.Coordinates = index, coords
|
||||||
|
if err := c.srv.filterACL(args.Token, reply); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,6 +187,87 @@ func TestCoordinate_Update(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCoordinate_Update_ACLDeny(t *testing.T) {
|
||||||
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
|
c.ACLDatacenter = "dc1"
|
||||||
|
c.ACLMasterToken = "root"
|
||||||
|
c.ACLDefaultPolicy = "deny"
|
||||||
|
c.ACLEnforceVersion8 = false
|
||||||
|
})
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer s1.Shutdown()
|
||||||
|
codec := rpcClient(t, s1)
|
||||||
|
defer codec.Close()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, s1.RPC, "dc1")
|
||||||
|
|
||||||
|
// Register some nodes.
|
||||||
|
nodes := []string{"node1", "node2"}
|
||||||
|
for _, node := range nodes {
|
||||||
|
req := structs.RegisterRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: node,
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
}
|
||||||
|
var reply struct{}
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send an update for the first node. This should go through since we
|
||||||
|
// don't have version 8 ACLs enforced yet.
|
||||||
|
req := structs.CoordinateUpdateRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "node1",
|
||||||
|
Coord: generateRandomCoordinate(),
|
||||||
|
}
|
||||||
|
var out struct{}
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now turn on version 8 enforcement and try again.
|
||||||
|
s1.config.ACLEnforceVersion8 = true
|
||||||
|
err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), permissionDenied) {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an ACL that can write to the node.
|
||||||
|
arg := structs.ACLRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.ACLSet,
|
||||||
|
ACL: structs.ACL{
|
||||||
|
Name: "User token",
|
||||||
|
Type: structs.ACLTypeClient,
|
||||||
|
Rules: `
|
||||||
|
node "node1" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
||||||
|
}
|
||||||
|
var id string
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &id); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the token, it should now go through.
|
||||||
|
req.Token = id
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// But it should be blocked for the other node.
|
||||||
|
req.Node = "node2"
|
||||||
|
err = msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), permissionDenied) {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCoordinate_ListDatacenters(t *testing.T) {
|
func TestCoordinate_ListDatacenters(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -240,8 +321,7 @@ func TestCoordinate_ListNodes(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send coordinate updates for a few nodes, waiting a little while for
|
// Send coordinate updates for a few nodes.
|
||||||
// the batch update to run.
|
|
||||||
arg1 := structs.CoordinateUpdateRequest{
|
arg1 := structs.CoordinateUpdateRequest{
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Node: "foo",
|
Node: "foo",
|
||||||
|
@ -269,9 +349,120 @@ func TestCoordinate_ListNodes(t *testing.T) {
|
||||||
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg3, &out); err != nil {
|
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg3, &out); err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
time.Sleep(3 * s1.config.CoordinateUpdatePeriod)
|
|
||||||
|
|
||||||
// Now query back for all the nodes.
|
// Now query back for all the nodes.
|
||||||
|
testutil.WaitForResult(func() (bool, error) {
|
||||||
|
arg := structs.DCSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
}
|
||||||
|
resp := structs.IndexedCoordinates{}
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.ListNodes", &arg, &resp); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(resp.Coordinates) != 3 ||
|
||||||
|
resp.Coordinates[0].Node != "bar" ||
|
||||||
|
resp.Coordinates[1].Node != "baz" ||
|
||||||
|
resp.Coordinates[2].Node != "foo" {
|
||||||
|
return false, fmt.Errorf("bad: %v", resp.Coordinates)
|
||||||
|
}
|
||||||
|
verifyCoordinatesEqual(t, resp.Coordinates[0].Coord, arg2.Coord) // bar
|
||||||
|
verifyCoordinatesEqual(t, resp.Coordinates[1].Coord, arg3.Coord) // baz
|
||||||
|
verifyCoordinatesEqual(t, resp.Coordinates[2].Coord, arg1.Coord) // foo
|
||||||
|
return true, nil
|
||||||
|
}, func(err error) { t.Fatalf("err: %v", err) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoordinate_ListNodes_ACLFilter(t *testing.T) {
|
||||||
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
|
c.ACLDatacenter = "dc1"
|
||||||
|
c.ACLMasterToken = "root"
|
||||||
|
c.ACLDefaultPolicy = "deny"
|
||||||
|
c.ACLEnforceVersion8 = false
|
||||||
|
})
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer s1.Shutdown()
|
||||||
|
codec := rpcClient(t, s1)
|
||||||
|
defer codec.Close()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, s1.RPC, "dc1")
|
||||||
|
|
||||||
|
// Register some nodes.
|
||||||
|
nodes := []string{"foo", "bar", "baz"}
|
||||||
|
for _, node := range nodes {
|
||||||
|
req := structs.RegisterRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: node,
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
WriteRequest: structs.WriteRequest{
|
||||||
|
Token: "root",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var reply struct{}
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send coordinate updates for a few nodes.
|
||||||
|
arg1 := structs.CoordinateUpdateRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
Coord: generateRandomCoordinate(),
|
||||||
|
WriteRequest: structs.WriteRequest{
|
||||||
|
Token: "root",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var out struct{}
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg1, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
arg2 := structs.CoordinateUpdateRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "bar",
|
||||||
|
Coord: generateRandomCoordinate(),
|
||||||
|
WriteRequest: structs.WriteRequest{
|
||||||
|
Token: "root",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg2, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
arg3 := structs.CoordinateUpdateRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "baz",
|
||||||
|
Coord: generateRandomCoordinate(),
|
||||||
|
WriteRequest: structs.WriteRequest{
|
||||||
|
Token: "root",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg3, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all the coordinate updates to apply. Since we aren't
|
||||||
|
// enforcing version 8 ACLs, this should also allow us to read
|
||||||
|
// everything back without a token.
|
||||||
|
testutil.WaitForResult(func() (bool, error) {
|
||||||
|
arg := structs.DCSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
}
|
||||||
|
resp := structs.IndexedCoordinates{}
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.ListNodes", &arg, &resp); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(resp.Coordinates) == 3 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("bad: %v", resp.Coordinates)
|
||||||
|
}, func(err error) { t.Fatalf("err: %v", err) })
|
||||||
|
|
||||||
|
// Now that we've waited for the batch processing to ingest the
|
||||||
|
// coordinates we can do the rest of the requests without the loop. We
|
||||||
|
// will start by turning on version 8 ACL support which should block
|
||||||
|
// everything.
|
||||||
|
s1.config.ACLEnforceVersion8 = true
|
||||||
arg := structs.DCSpecificRequest{
|
arg := structs.DCSpecificRequest{
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
}
|
}
|
||||||
|
@ -279,13 +470,38 @@ func TestCoordinate_ListNodes(t *testing.T) {
|
||||||
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.ListNodes", &arg, &resp); err != nil {
|
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.ListNodes", &arg, &resp); err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
if len(resp.Coordinates) != 3 ||
|
if len(resp.Coordinates) != 0 {
|
||||||
resp.Coordinates[0].Node != "bar" ||
|
t.Fatalf("bad: %#v", resp.Coordinates)
|
||||||
resp.Coordinates[1].Node != "baz" ||
|
}
|
||||||
resp.Coordinates[2].Node != "foo" {
|
|
||||||
t.Fatalf("bad: %v", resp.Coordinates)
|
// Create an ACL that can read one of the nodes.
|
||||||
|
var id string
|
||||||
|
{
|
||||||
|
req := structs.ACLRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.ACLSet,
|
||||||
|
ACL: structs.ACL{
|
||||||
|
Name: "User token",
|
||||||
|
Type: structs.ACLTypeClient,
|
||||||
|
Rules: `
|
||||||
|
node "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
||||||
|
}
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &id); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the token, it should now go through.
|
||||||
|
arg.Token = id
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.ListNodes", &arg, &resp); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(resp.Coordinates) != 1 || resp.Coordinates[0].Node != "foo" {
|
||||||
|
t.Fatalf("bad: %#v", resp.Coordinates)
|
||||||
}
|
}
|
||||||
verifyCoordinatesEqual(t, resp.Coordinates[0].Coord, arg2.Coord) // bar
|
|
||||||
verifyCoordinatesEqual(t, resp.Coordinates[1].Coord, arg3.Coord) // baz
|
|
||||||
verifyCoordinatesEqual(t, resp.Coordinates[2].Coord, arg1.Coord) // foo
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -507,6 +507,12 @@ func TestHealth_NodeChecks_FilterACL(t *testing.T) {
|
||||||
if !found {
|
if !found {
|
||||||
t.Fatalf("bad: %#v", reply.HealthChecks)
|
t.Fatalf("bad: %#v", reply.HealthChecks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We've already proven that we call the ACL filtering function so we
|
||||||
|
// test node filtering down in acl.go for node cases. This also proves
|
||||||
|
// that we respect the version 8 ACL flag, since the test server sets
|
||||||
|
// that to false (the regression value of *not* changing this is better
|
||||||
|
// for now until we change the sense of the version 8 ACL flag).
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHealth_ServiceChecks_FilterACL(t *testing.T) {
|
func TestHealth_ServiceChecks_FilterACL(t *testing.T) {
|
||||||
|
@ -543,6 +549,12 @@ func TestHealth_ServiceChecks_FilterACL(t *testing.T) {
|
||||||
if len(reply.HealthChecks) != 0 {
|
if len(reply.HealthChecks) != 0 {
|
||||||
t.Fatalf("bad: %#v", reply.HealthChecks)
|
t.Fatalf("bad: %#v", reply.HealthChecks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We've already proven that we call the ACL filtering function so we
|
||||||
|
// test node filtering down in acl.go for node cases. This also proves
|
||||||
|
// that we respect the version 8 ACL flag, since the test server sets
|
||||||
|
// that to false (the regression value of *not* changing this is better
|
||||||
|
// for now until we change the sense of the version 8 ACL flag).
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHealth_ServiceNodes_FilterACL(t *testing.T) {
|
func TestHealth_ServiceNodes_FilterACL(t *testing.T) {
|
||||||
|
@ -572,6 +584,12 @@ func TestHealth_ServiceNodes_FilterACL(t *testing.T) {
|
||||||
if len(reply.Nodes) != 0 {
|
if len(reply.Nodes) != 0 {
|
||||||
t.Fatalf("bad: %#v", reply.Nodes)
|
t.Fatalf("bad: %#v", reply.Nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We've already proven that we call the ACL filtering function so we
|
||||||
|
// test node filtering down in acl.go for node cases. This also proves
|
||||||
|
// that we respect the version 8 ACL flag, since the test server sets
|
||||||
|
// that to false (the regression value of *not* changing this is better
|
||||||
|
// for now until we change the sense of the version 8 ACL flag).
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHealth_ChecksInState_FilterACL(t *testing.T) {
|
func TestHealth_ChecksInState_FilterACL(t *testing.T) {
|
||||||
|
@ -602,4 +620,10 @@ func TestHealth_ChecksInState_FilterACL(t *testing.T) {
|
||||||
if !found {
|
if !found {
|
||||||
t.Fatalf("missing service 'foo': %#v", reply.HealthChecks)
|
t.Fatalf("missing service 'foo': %#v", reply.HealthChecks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We've already proven that we call the ACL filtering function so we
|
||||||
|
// test node filtering down in acl.go for node cases. This also proves
|
||||||
|
// that we respect the version 8 ACL flag, since the test server sets
|
||||||
|
// that to false (the regression value of *not* changing this is better
|
||||||
|
// for now until we change the sense of the version 8 ACL flag).
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,6 +284,12 @@ func TestInternal_NodeInfo_FilterACL(t *testing.T) {
|
||||||
t.Fatalf("bad: %#v", info.Services)
|
t.Fatalf("bad: %#v", info.Services)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We've already proven that we call the ACL filtering function so we
|
||||||
|
// test node filtering down in acl.go for node cases. This also proves
|
||||||
|
// that we respect the version 8 ACL flag, since the test server sets
|
||||||
|
// that to false (the regression value of *not* changing this is better
|
||||||
|
// for now until we change the sense of the version 8 ACL flag).
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInternal_NodeDump_FilterACL(t *testing.T) {
|
func TestInternal_NodeDump_FilterACL(t *testing.T) {
|
||||||
|
@ -327,6 +333,12 @@ func TestInternal_NodeDump_FilterACL(t *testing.T) {
|
||||||
t.Fatalf("bad: %#v", info.Services)
|
t.Fatalf("bad: %#v", info.Services)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We've already proven that we call the ACL filtering function so we
|
||||||
|
// test node filtering down in acl.go for node cases. This also proves
|
||||||
|
// that we respect the version 8 ACL flag, since the test server sets
|
||||||
|
// that to false (the regression value of *not* changing this is better
|
||||||
|
// for now until we change the sense of the version 8 ACL flag).
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInternal_EventFire_Token(t *testing.T) {
|
func TestInternal_EventFire_Token(t *testing.T) {
|
||||||
|
|
|
@ -428,7 +428,7 @@ AFTER_CHECK:
|
||||||
Status: structs.HealthPassing,
|
Status: structs.HealthPassing,
|
||||||
Output: SerfCheckAliveOutput,
|
Output: SerfCheckAliveOutput,
|
||||||
},
|
},
|
||||||
WriteRequest: structs.WriteRequest{Token: s.config.ACLToken},
|
WriteRequest: structs.WriteRequest{Token: s.config.GetTokenForAgent()},
|
||||||
}
|
}
|
||||||
var out struct{}
|
var out struct{}
|
||||||
return s.endpoints.Catalog.Register(&req, &out)
|
return s.endpoints.Catalog.Register(&req, &out)
|
||||||
|
@ -469,7 +469,7 @@ func (s *Server) handleFailedMember(member serf.Member) error {
|
||||||
Status: structs.HealthCritical,
|
Status: structs.HealthCritical,
|
||||||
Output: SerfCheckFailedOutput,
|
Output: SerfCheckFailedOutput,
|
||||||
},
|
},
|
||||||
WriteRequest: structs.WriteRequest{Token: s.config.ACLToken},
|
WriteRequest: structs.WriteRequest{Token: s.config.GetTokenForAgent()},
|
||||||
}
|
}
|
||||||
var out struct{}
|
var out struct{}
|
||||||
return s.endpoints.Catalog.Register(&req, &out)
|
return s.endpoints.Catalog.Register(&req, &out)
|
||||||
|
@ -612,7 +612,7 @@ func (s *Server) reapTombstones(index uint64) {
|
||||||
Datacenter: s.config.Datacenter,
|
Datacenter: s.config.Datacenter,
|
||||||
Op: structs.TombstoneReap,
|
Op: structs.TombstoneReap,
|
||||||
ReapIndex: index,
|
ReapIndex: index,
|
||||||
WriteRequest: structs.WriteRequest{Token: s.config.ACLToken},
|
WriteRequest: structs.WriteRequest{Token: s.config.GetTokenForAgent()},
|
||||||
}
|
}
|
||||||
_, err := s.raftApply(structs.TombstoneRequestType, &req)
|
_, err := s.raftApply(structs.TombstoneRequestType, &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -96,7 +96,7 @@ func (p *PreparedQuery) Apply(args *structs.PreparedQueryRequest, reply *string)
|
||||||
// Parse the query and prep it for the state store.
|
// Parse the query and prep it for the state store.
|
||||||
switch args.Op {
|
switch args.Op {
|
||||||
case structs.PreparedQueryCreate, structs.PreparedQueryUpdate:
|
case structs.PreparedQueryCreate, structs.PreparedQueryUpdate:
|
||||||
if err := parseQuery(args.Query); err != nil {
|
if err := parseQuery(args.Query, p.srv.config.ACLEnforceVersion8); err != nil {
|
||||||
return fmt.Errorf("Invalid prepared query: %v", err)
|
return fmt.Errorf("Invalid prepared query: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ func (p *PreparedQuery) Apply(args *structs.PreparedQueryRequest, reply *string)
|
||||||
// update operation. Some of the fields are not checked or are partially
|
// update operation. Some of the fields are not checked or are partially
|
||||||
// checked, as noted in the comments below. This also updates all the parsed
|
// checked, as noted in the comments below. This also updates all the parsed
|
||||||
// fields of the query.
|
// fields of the query.
|
||||||
func parseQuery(query *structs.PreparedQuery) error {
|
func parseQuery(query *structs.PreparedQuery, enforceVersion8 bool) error {
|
||||||
// We skip a few fields:
|
// We skip a few fields:
|
||||||
// - ID is checked outside this fn.
|
// - ID is checked outside this fn.
|
||||||
// - Name is optional with no restrictions, except for uniqueness which
|
// - Name is optional with no restrictions, except for uniqueness which
|
||||||
|
@ -133,10 +133,16 @@ func parseQuery(query *structs.PreparedQuery) error {
|
||||||
// names do not overlap with IDs, which is also checked during the
|
// names do not overlap with IDs, which is also checked during the
|
||||||
// transaction. Otherwise, people could "steal" queries that they don't
|
// transaction. Otherwise, people could "steal" queries that they don't
|
||||||
// have proper ACL rights to change.
|
// have proper ACL rights to change.
|
||||||
// - Session is optional and checked for integrity during the transaction.
|
|
||||||
// - Template is checked during the transaction since that's where we
|
// - Template is checked during the transaction since that's where we
|
||||||
// compile it.
|
// compile it.
|
||||||
|
|
||||||
|
// Anonymous queries require a session or need to be part of a template.
|
||||||
|
if enforceVersion8 {
|
||||||
|
if query.Name == "" && query.Template.Type == "" && query.Session == "" {
|
||||||
|
return fmt.Errorf("Must be bound to a session")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Token is checked when the query is executed, but we do make sure the
|
// Token is checked when the query is executed, but we do make sure the
|
||||||
// user hasn't accidentally pasted-in the special redacted token name,
|
// user hasn't accidentally pasted-in the special redacted token name,
|
||||||
// which if we allowed in would be super hard to debug and understand.
|
// which if we allowed in would be super hard to debug and understand.
|
||||||
|
|
|
@ -32,6 +32,7 @@ func TestPreparedQuery_Apply(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "redis",
|
Service: "redis",
|
||||||
},
|
},
|
||||||
|
@ -515,6 +516,7 @@ func TestPreparedQuery_Apply_ForwardLeader(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "redis",
|
Service: "redis",
|
||||||
},
|
},
|
||||||
|
@ -531,53 +533,77 @@ func TestPreparedQuery_Apply_ForwardLeader(t *testing.T) {
|
||||||
func TestPreparedQuery_parseQuery(t *testing.T) {
|
func TestPreparedQuery_parseQuery(t *testing.T) {
|
||||||
query := &structs.PreparedQuery{}
|
query := &structs.PreparedQuery{}
|
||||||
|
|
||||||
err := parseQuery(query)
|
err := parseQuery(query, true)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "Must be bound to a session") {
|
||||||
|
t.Fatalf("bad: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.Session = "adf4238a-882b-9ddc-4a9d-5b6758e4159e"
|
||||||
|
err = parseQuery(query, true)
|
||||||
if err == nil || !strings.Contains(err.Error(), "Must provide a Service") {
|
if err == nil || !strings.Contains(err.Error(), "Must provide a Service") {
|
||||||
t.Fatalf("bad: %v", err)
|
t.Fatalf("bad: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
query.Service.Service = "foo"
|
query.Session = ""
|
||||||
if err := parseQuery(query); err != nil {
|
query.Template.Type = "some-kind-of-template"
|
||||||
t.Fatalf("err: %v", err)
|
err = parseQuery(query, true)
|
||||||
}
|
if err == nil || !strings.Contains(err.Error(), "Must provide a Service") {
|
||||||
|
|
||||||
query.Token = redactedToken
|
|
||||||
err = parseQuery(query)
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "Bad Token") {
|
|
||||||
t.Fatalf("bad: %v", err)
|
t.Fatalf("bad: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
query.Token = "adf4238a-882b-9ddc-4a9d-5b6758e4159e"
|
query.Template.Type = ""
|
||||||
if err := parseQuery(query); err != nil {
|
err = parseQuery(query, false)
|
||||||
t.Fatalf("err: %v", err)
|
if err == nil || !strings.Contains(err.Error(), "Must provide a Service") {
|
||||||
}
|
|
||||||
|
|
||||||
query.Service.Failover.NearestN = -1
|
|
||||||
err = parseQuery(query)
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "Bad NearestN") {
|
|
||||||
t.Fatalf("bad: %v", err)
|
t.Fatalf("bad: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
query.Service.Failover.NearestN = 3
|
// None of the rest of these care about version 8 ACL enforcement.
|
||||||
if err := parseQuery(query); err != nil {
|
for _, version8 := range []bool{true, false} {
|
||||||
t.Fatalf("err: %v", err)
|
query = &structs.PreparedQuery{}
|
||||||
}
|
query.Session = "adf4238a-882b-9ddc-4a9d-5b6758e4159e"
|
||||||
|
query.Service.Service = "foo"
|
||||||
|
if err := parseQuery(query, version8); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
query.DNS.TTL = "two fortnights"
|
query.Token = redactedToken
|
||||||
err = parseQuery(query)
|
err = parseQuery(query, version8)
|
||||||
if err == nil || !strings.Contains(err.Error(), "Bad DNS TTL") {
|
if err == nil || !strings.Contains(err.Error(), "Bad Token") {
|
||||||
t.Fatalf("bad: %v", err)
|
t.Fatalf("bad: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
query.DNS.TTL = "-3s"
|
query.Token = "adf4238a-882b-9ddc-4a9d-5b6758e4159e"
|
||||||
err = parseQuery(query)
|
if err := parseQuery(query, version8); err != nil {
|
||||||
if err == nil || !strings.Contains(err.Error(), "must be >=0") {
|
t.Fatalf("err: %v", err)
|
||||||
t.Fatalf("bad: %v", err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
query.DNS.TTL = "3s"
|
query.Service.Failover.NearestN = -1
|
||||||
if err := parseQuery(query); err != nil {
|
err = parseQuery(query, version8)
|
||||||
t.Fatalf("err: %v", err)
|
if err == nil || !strings.Contains(err.Error(), "Bad NearestN") {
|
||||||
|
t.Fatalf("bad: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.Service.Failover.NearestN = 3
|
||||||
|
if err := parseQuery(query, version8); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.DNS.TTL = "two fortnights"
|
||||||
|
err = parseQuery(query, version8)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "Bad DNS TTL") {
|
||||||
|
t.Fatalf("bad: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.DNS.TTL = "-3s"
|
||||||
|
err = parseQuery(query, version8)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "must be >=0") {
|
||||||
|
t.Fatalf("bad: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.DNS.TTL = "3s"
|
||||||
|
if err := parseQuery(query, version8); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -917,9 +943,25 @@ func TestPreparedQuery_Get(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a session.
|
||||||
|
var session string
|
||||||
|
{
|
||||||
|
req := structs.SessionRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.SessionCreate,
|
||||||
|
Session: structs.Session{
|
||||||
|
Node: s1.config.NodeName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &req, &session); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now update the query to take away its name.
|
// Now update the query to take away its name.
|
||||||
query.Op = structs.PreparedQueryUpdate
|
query.Op = structs.PreparedQueryUpdate
|
||||||
query.Query.Name = ""
|
query.Query.Name = ""
|
||||||
|
query.Query.Session = session
|
||||||
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1170,9 +1212,25 @@ func TestPreparedQuery_List(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a session.
|
||||||
|
var session string
|
||||||
|
{
|
||||||
|
req := structs.SessionRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.SessionCreate,
|
||||||
|
Session: structs.Session{
|
||||||
|
Node: s1.config.NodeName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &req, &session); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now take away the query name.
|
// Now take away the query name.
|
||||||
query.Op = structs.PreparedQueryUpdate
|
query.Op = structs.PreparedQueryUpdate
|
||||||
query.Query.Name = ""
|
query.Query.Name = ""
|
||||||
|
query.Query.Session = session
|
||||||
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1359,6 +1417,7 @@ func TestPreparedQuery_Execute(t *testing.T) {
|
||||||
c.ACLDatacenter = "dc1"
|
c.ACLDatacenter = "dc1"
|
||||||
c.ACLMasterToken = "root"
|
c.ACLMasterToken = "root"
|
||||||
c.ACLDefaultPolicy = "deny"
|
c.ACLDefaultPolicy = "deny"
|
||||||
|
c.ACLEnforceVersion8 = false
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -1451,6 +1510,7 @@ func TestPreparedQuery_Execute(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "foo",
|
Service: "foo",
|
||||||
},
|
},
|
||||||
|
@ -2079,6 +2139,58 @@ func TestPreparedQuery_Execute(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Turn on version 8 ACLs, which will start to filter even with the exec
|
||||||
|
// token.
|
||||||
|
s1.config.ACLEnforceVersion8 = true
|
||||||
|
{
|
||||||
|
req := structs.PreparedQueryExecuteRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
QueryIDOrName: query.Query.ID,
|
||||||
|
QueryOptions: structs.QueryOptions{Token: execToken},
|
||||||
|
}
|
||||||
|
|
||||||
|
var reply structs.PreparedQueryExecuteResponse
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Execute", &req, &reply); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reply.Nodes) != 0 ||
|
||||||
|
reply.Datacenter != "dc1" || reply.Failovers != 0 ||
|
||||||
|
reply.Service != query.Query.Service.Service ||
|
||||||
|
!reflect.DeepEqual(reply.DNS, query.Query.DNS) ||
|
||||||
|
!reply.QueryMeta.KnownLeader {
|
||||||
|
t.Fatalf("bad: %v", reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revert version 8 ACLs and make sure the query works again.
|
||||||
|
s1.config.ACLEnforceVersion8 = false
|
||||||
|
{
|
||||||
|
req := structs.PreparedQueryExecuteRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
QueryIDOrName: query.Query.ID,
|
||||||
|
QueryOptions: structs.QueryOptions{Token: execToken},
|
||||||
|
}
|
||||||
|
|
||||||
|
var reply structs.PreparedQueryExecuteResponse
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Execute", &req, &reply); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reply.Nodes) != 8 ||
|
||||||
|
reply.Datacenter != "dc1" || reply.Failovers != 0 ||
|
||||||
|
reply.Service != query.Query.Service.Service ||
|
||||||
|
!reflect.DeepEqual(reply.DNS, query.Query.DNS) ||
|
||||||
|
!reply.QueryMeta.KnownLeader {
|
||||||
|
t.Fatalf("bad: %v", reply)
|
||||||
|
}
|
||||||
|
for _, node := range reply.Nodes {
|
||||||
|
if node.Node.Node == "node1" || node.Node.Node == "node3" {
|
||||||
|
t.Fatalf("bad: %v", node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now fail everything in dc1 and we should get an empty list back.
|
// Now fail everything in dc1 and we should get an empty list back.
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
setHealth(fmt.Sprintf("node%d", i+1), structs.HealthCritical)
|
setHealth(fmt.Sprintf("node%d", i+1), structs.HealthCritical)
|
||||||
|
@ -2314,6 +2426,7 @@ func TestPreparedQuery_Execute_ForwardLeader(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.PreparedQueryCreate,
|
Op: structs.PreparedQueryCreate,
|
||||||
Query: &structs.PreparedQuery{
|
Query: &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "redis",
|
Service: "redis",
|
||||||
},
|
},
|
||||||
|
@ -2577,6 +2690,7 @@ func (m *mockQueryServer) ForwardDC(method, dc string, args interface{}, reply i
|
||||||
|
|
||||||
func TestPreparedQuery_queryFailover(t *testing.T) {
|
func TestPreparedQuery_queryFailover(t *testing.T) {
|
||||||
query := &structs.PreparedQuery{
|
query := &structs.PreparedQuery{
|
||||||
|
Name: "test",
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Failover: structs.QueryDatacenterOptions{
|
Failover: structs.QueryDatacenterOptions{
|
||||||
NearestN: 0,
|
NearestN: 0,
|
||||||
|
|
|
@ -30,6 +30,33 @@ func (s *Session) Apply(args *structs.SessionRequest, reply *string) error {
|
||||||
return fmt.Errorf("Must provide Node")
|
return fmt.Errorf("Must provide Node")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch the ACL token, if any, and apply the policy.
|
||||||
|
acl, err := s.srv.resolveToken(args.Token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if acl != nil && s.srv.config.ACLEnforceVersion8 {
|
||||||
|
switch args.Op {
|
||||||
|
case structs.SessionDestroy:
|
||||||
|
state := s.srv.fsm.State()
|
||||||
|
_, existing, err := state.SessionGet(args.Session.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unknown session %q", args.Session.ID)
|
||||||
|
}
|
||||||
|
if !acl.SessionWrite(existing.Node) {
|
||||||
|
return permissionDeniedErr
|
||||||
|
}
|
||||||
|
|
||||||
|
case structs.SessionCreate:
|
||||||
|
if !acl.SessionWrite(args.Session.Node) {
|
||||||
|
return permissionDeniedErr
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Invalid session operation %q, args.Op")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that the specified behavior is allowed
|
// Ensure that the specified behavior is allowed
|
||||||
switch args.Session.Behavior {
|
switch args.Session.Behavior {
|
||||||
case "":
|
case "":
|
||||||
|
@ -130,6 +157,9 @@ func (s *Session) Get(args *structs.SessionSpecificRequest,
|
||||||
} else {
|
} else {
|
||||||
reply.Sessions = nil
|
reply.Sessions = nil
|
||||||
}
|
}
|
||||||
|
if err := s.srv.filterACL(args.Token, reply); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -154,6 +184,9 @@ func (s *Session) List(args *structs.DCSpecificRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Index, reply.Sessions = index, sessions
|
reply.Index, reply.Sessions = index, sessions
|
||||||
|
if err := s.srv.filterACL(args.Token, reply); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -178,6 +211,9 @@ func (s *Session) NodeSessions(args *structs.NodeSpecificRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Index, reply.Sessions = index, sessions
|
reply.Index, reply.Sessions = index, sessions
|
||||||
|
if err := s.srv.filterACL(args.Token, reply); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -190,21 +226,35 @@ func (s *Session) Renew(args *structs.SessionSpecificRequest,
|
||||||
}
|
}
|
||||||
defer metrics.MeasureSince([]string{"consul", "session", "renew"}, time.Now())
|
defer metrics.MeasureSince([]string{"consul", "session", "renew"}, time.Now())
|
||||||
|
|
||||||
// Get the session, from local state
|
// Get the session, from local state.
|
||||||
state := s.srv.fsm.State()
|
state := s.srv.fsm.State()
|
||||||
index, session, err := state.SessionGet(args.Session)
|
index, session, err := state.SessionGet(args.Session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the session TTL timer
|
|
||||||
reply.Index = index
|
reply.Index = index
|
||||||
if session != nil {
|
if session == nil {
|
||||||
reply.Sessions = structs.Sessions{session}
|
return nil
|
||||||
if err := s.srv.resetSessionTimer(args.Session, session); err != nil {
|
}
|
||||||
s.srv.logger.Printf("[ERR] consul.session: Session renew failed: %v", err)
|
|
||||||
return err
|
// Fetch the ACL token, if any, and apply the policy.
|
||||||
|
acl, err := s.srv.resolveToken(args.Token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if acl != nil && s.srv.config.ACLEnforceVersion8 {
|
||||||
|
if !acl.SessionWrite(session.Node) {
|
||||||
|
return permissionDeniedErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the session TTL timer.
|
||||||
|
reply.Sessions = structs.Sessions{session}
|
||||||
|
if err := s.srv.resetSessionTimer(args.Session, session); err != nil {
|
||||||
|
s.srv.logger.Printf("[ERR] consul.session: Session renew failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package consul
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -11,7 +12,7 @@ import (
|
||||||
"github.com/hashicorp/net-rpc-msgpackrpc"
|
"github.com/hashicorp/net-rpc-msgpackrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSessionEndpoint_Apply(t *testing.T) {
|
func TestSession_Apply(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -70,7 +71,7 @@ func TestSessionEndpoint_Apply(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionEndpoint_DeleteApply(t *testing.T) {
|
func TestSession_DeleteApply(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -133,7 +134,101 @@ func TestSessionEndpoint_DeleteApply(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionEndpoint_Get(t *testing.T) {
|
func TestSession_Apply_ACLDeny(t *testing.T) {
|
||||||
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
|
c.ACLDatacenter = "dc1"
|
||||||
|
c.ACLMasterToken = "root"
|
||||||
|
c.ACLDefaultPolicy = "deny"
|
||||||
|
c.ACLEnforceVersion8 = false
|
||||||
|
})
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer s1.Shutdown()
|
||||||
|
codec := rpcClient(t, s1)
|
||||||
|
defer codec.Close()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, s1.RPC, "dc1")
|
||||||
|
|
||||||
|
// Create the ACL.
|
||||||
|
req := structs.ACLRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.ACLSet,
|
||||||
|
ACL: structs.ACL{
|
||||||
|
Name: "User token",
|
||||||
|
Type: structs.ACLTypeClient,
|
||||||
|
Rules: `
|
||||||
|
session "foo" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var token string
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just add a node.
|
||||||
|
s1.fsm.State().EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"})
|
||||||
|
|
||||||
|
// Try to create without a token, which will go through since version 8
|
||||||
|
// enforcement isn't enabled.
|
||||||
|
arg := structs.SessionRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.SessionCreate,
|
||||||
|
Session: structs.Session{
|
||||||
|
Node: "foo",
|
||||||
|
Name: "my-session",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var id1 string
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &arg, &id1); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now turn on version 8 enforcement and try again, it should be denied.
|
||||||
|
var id2 string
|
||||||
|
s1.config.ACLEnforceVersion8 = true
|
||||||
|
err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &arg, &id2)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), permissionDenied) {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now set a token and try again. This should go through.
|
||||||
|
arg.Token = token
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &arg, &id2); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a delete on the first session with version 8 enforcement off and
|
||||||
|
// no token. This should go through.
|
||||||
|
var out string
|
||||||
|
s1.config.ACLEnforceVersion8 = false
|
||||||
|
arg.Op = structs.SessionDestroy
|
||||||
|
arg.Token = ""
|
||||||
|
arg.Session.ID = id1
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn on version 8 enforcement and make sure the delete of the second
|
||||||
|
// session fails.
|
||||||
|
s1.config.ACLEnforceVersion8 = true
|
||||||
|
arg.Session.ID = id2
|
||||||
|
err = msgpackrpc.CallWithCodec(codec, "Session.Apply", &arg, &out)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), permissionDenied) {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now set a token and try again. This should go through.
|
||||||
|
arg.Token = token
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSession_Get(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -176,7 +271,7 @@ func TestSessionEndpoint_Get(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionEndpoint_List(t *testing.T) {
|
func TestSession_List(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -227,7 +322,175 @@ func TestSessionEndpoint_List(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionEndpoint_ApplyTimers(t *testing.T) {
|
func TestSession_Get_List_NodeSessions_ACLFilter(t *testing.T) {
|
||||||
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
|
c.ACLDatacenter = "dc1"
|
||||||
|
c.ACLMasterToken = "root"
|
||||||
|
c.ACLDefaultPolicy = "deny"
|
||||||
|
c.ACLEnforceVersion8 = false
|
||||||
|
})
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer s1.Shutdown()
|
||||||
|
codec := rpcClient(t, s1)
|
||||||
|
defer codec.Close()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, s1.RPC, "dc1")
|
||||||
|
|
||||||
|
// Create the ACL.
|
||||||
|
req := structs.ACLRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.ACLSet,
|
||||||
|
ACL: structs.ACL{
|
||||||
|
Name: "User token",
|
||||||
|
Type: structs.ACLTypeClient,
|
||||||
|
Rules: `
|
||||||
|
session "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var token string
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a node and a session.
|
||||||
|
s1.fsm.State().EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"})
|
||||||
|
arg := structs.SessionRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.SessionCreate,
|
||||||
|
Session: structs.Session{
|
||||||
|
Node: "foo",
|
||||||
|
},
|
||||||
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
||||||
|
}
|
||||||
|
var out string
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform all the read operations, which should go through since version
|
||||||
|
// 8 ACL enforcement isn't enabled.
|
||||||
|
getR := structs.SessionSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Session: out,
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Get", &getR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 1 {
|
||||||
|
t.Fatalf("bad: %v", sessions.Sessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listR := structs.DCSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.List", &listR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 1 {
|
||||||
|
t.Fatalf("bad: %v", sessions.Sessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodeR := structs.NodeSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.NodeSessions", &nodeR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 1 {
|
||||||
|
t.Fatalf("bad: %v", sessions.Sessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now turn on version 8 enforcement and make sure everything is empty.
|
||||||
|
s1.config.ACLEnforceVersion8 = true
|
||||||
|
{
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Get", &getR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 0 {
|
||||||
|
t.Fatalf("bad: %v", sessions.Sessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.List", &listR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 0 {
|
||||||
|
t.Fatalf("bad: %v", sessions.Sessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.NodeSessions", &nodeR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 0 {
|
||||||
|
t.Fatalf("bad: %v", sessions.Sessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, supply the token and make sure the reads are allowed.
|
||||||
|
getR.Token = token
|
||||||
|
{
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Get", &getR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 1 {
|
||||||
|
t.Fatalf("bad: %v", sessions.Sessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listR.Token = token
|
||||||
|
{
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.List", &listR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 1 {
|
||||||
|
t.Fatalf("bad: %v", sessions.Sessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodeR.Token = token
|
||||||
|
{
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.NodeSessions", &nodeR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 1 {
|
||||||
|
t.Fatalf("bad: %v", sessions.Sessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get a session that doesn't exist to make sure that's handled
|
||||||
|
// correctly by the filter (it will get passed a nil slice).
|
||||||
|
getR.Session = "adf4238a-882b-9ddc-4a9d-5b6758e4159e"
|
||||||
|
{
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Get", &getR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 0 {
|
||||||
|
t.Fatalf("bad: %v", sessions.Sessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSession_ApplyTimers(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -268,7 +531,7 @@ func TestSessionEndpoint_ApplyTimers(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionEndpoint_Renew(t *testing.T) {
|
func TestSession_Renew(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -428,7 +691,85 @@ func TestSessionEndpoint_Renew(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionEndpoint_NodeSessions(t *testing.T) {
|
func TestSession_Renew_ACLDeny(t *testing.T) {
|
||||||
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
|
c.ACLDatacenter = "dc1"
|
||||||
|
c.ACLMasterToken = "root"
|
||||||
|
c.ACLDefaultPolicy = "deny"
|
||||||
|
c.ACLEnforceVersion8 = false
|
||||||
|
})
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer s1.Shutdown()
|
||||||
|
codec := rpcClient(t, s1)
|
||||||
|
defer codec.Close()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, s1.RPC, "dc1")
|
||||||
|
|
||||||
|
// Create the ACL.
|
||||||
|
req := structs.ACLRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.ACLSet,
|
||||||
|
ACL: structs.ACL{
|
||||||
|
Name: "User token",
|
||||||
|
Type: structs.ACLTypeClient,
|
||||||
|
Rules: `
|
||||||
|
session "foo" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var token string
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just add a node.
|
||||||
|
s1.fsm.State().EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"})
|
||||||
|
|
||||||
|
// Create a session. The token won't matter here since we don't have
|
||||||
|
// version 8 ACL enforcement on yet.
|
||||||
|
arg := structs.SessionRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.SessionCreate,
|
||||||
|
Session: structs.Session{
|
||||||
|
Node: "foo",
|
||||||
|
Name: "my-session",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var id string
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &arg, &id); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renew without a token should go through without version 8 ACL
|
||||||
|
// enforcement.
|
||||||
|
renewR := structs.SessionSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Session: id,
|
||||||
|
}
|
||||||
|
var session structs.IndexedSessions
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Renew", &renewR, &session); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now turn on version 8 enforcement and the renew should be rejected.
|
||||||
|
s1.config.ACLEnforceVersion8 = true
|
||||||
|
err := msgpackrpc.CallWithCodec(codec, "Session.Renew", &renewR, &session)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), permissionDenied) {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the token and it should go through.
|
||||||
|
renewR.Token = token
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Renew", &renewR, &session); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSession_NodeSessions(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -486,7 +827,7 @@ func TestSessionEndpoint_NodeSessions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionEndpoint_Apply_BadTTL(t *testing.T) {
|
func TestSession_Apply_BadTTL(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
|
|
@ -377,6 +377,14 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
|
||||||
all operations, and "extend-cache" allows any cached ACLs to be used, ignoring their TTL
|
all operations, and "extend-cache" allows any cached ACLs to be used, ignoring their TTL
|
||||||
values. If a non-cached ACL is used, "extend-cache" acts like "deny".
|
values. If a non-cached ACL is used, "extend-cache" acts like "deny".
|
||||||
|
|
||||||
|
* <a name"acl_agent_token"></a><a href="#acl_agent_token">`acl_agent_token`</a> - Used for clients
|
||||||
|
and servers to perform internal operations to the service catalog. If this isn't specified, then
|
||||||
|
the <a href="#acl_token">`acl_token`</a> will be used. This was added in Consul 0.7.2.
|
||||||
|
<br><br>
|
||||||
|
For clients, this token must at least have write access to the node name it will register as. For
|
||||||
|
servers, this must have write access to all nodes that are expected to join the cluster, as well
|
||||||
|
as write access to the "consul" service, which will be registered automatically on its behalf.
|
||||||
|
|
||||||
* <a name="acl_enforce_version_8"></a><a href="#acl_enforce_version_8">`acl_enforce_version_8`</a> -
|
* <a name="acl_enforce_version_8"></a><a href="#acl_enforce_version_8">`acl_enforce_version_8`</a> -
|
||||||
Used for clients and servers to determine if enforcement should occur for new ACL policies being
|
Used for clients and servers to determine if enforcement should occur for new ACL policies being
|
||||||
previewed before Consul 0.8. Added in Consul 0.7.2, this will default to false in versions of
|
previewed before Consul 0.8. Added in Consul 0.7.2, this will default to false in versions of
|
||||||
|
|
Loading…
Reference in New Issue