mirror of https://github.com/status-im/consul.git
Add default intention policy (#20544)
This commit is contained in:
parent
7c3a379e48
commit
26661a1c3b
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:feature
|
||||||
|
agent: Introduces a new agent config default_intention_policy to decouple the default intention behavior from ACLs
|
||||||
|
```
|
|
@ -93,6 +93,10 @@ type Authorizer interface {
|
||||||
|
|
||||||
// IntentionDefaultAllow determines the default authorized behavior
|
// IntentionDefaultAllow determines the default authorized behavior
|
||||||
// when no intentions match a Connect request.
|
// when no intentions match a Connect request.
|
||||||
|
//
|
||||||
|
// Deprecated: Use DefaultIntentionPolicy under agent configuration.
|
||||||
|
// Moving forwards, intentions will not inherit default allow behavior
|
||||||
|
// from the ACL system.
|
||||||
IntentionDefaultAllow(*AuthorizerContext) EnforcementDecision
|
IntentionDefaultAllow(*AuthorizerContext) EnforcementDecision
|
||||||
|
|
||||||
// IntentionRead determines if a specific intention can be read.
|
// IntentionRead determines if a specific intention can be read.
|
||||||
|
@ -297,17 +301,6 @@ func (a AllowAuthorizer) IdentityWriteAnyAllowed(ctx *AuthorizerContext) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntentionDefaultAllowAllowed determines the default authorized behavior
|
|
||||||
// when no intentions match a Connect request.
|
|
||||||
func (a AllowAuthorizer) IntentionDefaultAllowAllowed(ctx *AuthorizerContext) error {
|
|
||||||
if a.Authorizer.IntentionDefaultAllow(ctx) != Allow {
|
|
||||||
// This is a bit nuanced, in that this isn't set by a rule, but inherited globally
|
|
||||||
// TODO(acl-error-enhancements) revisit when we have full accessor info
|
|
||||||
return PermissionDeniedError{Cause: "Denied by intention default"}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionReadAllowed determines if a specific intention can be read.
|
// IntentionReadAllowed determines if a specific intention can be read.
|
||||||
func (a AllowAuthorizer) IntentionReadAllowed(name string, ctx *AuthorizerContext) error {
|
func (a AllowAuthorizer) IntentionReadAllowed(name string, ctx *AuthorizerContext) error {
|
||||||
if a.Authorizer.IntentionRead(name, ctx) != Allow {
|
if a.Authorizer.IntentionRead(name, ctx) != Allow {
|
||||||
|
|
|
@ -113,6 +113,7 @@ func (c *ChainedAuthorizer) IdentityWriteAny(entCtx *AuthorizerContext) Enforcem
|
||||||
// when no intentions match a Connect request.
|
// when no intentions match a Connect request.
|
||||||
func (c *ChainedAuthorizer) IntentionDefaultAllow(entCtx *AuthorizerContext) EnforcementDecision {
|
func (c *ChainedAuthorizer) IntentionDefaultAllow(entCtx *AuthorizerContext) EnforcementDecision {
|
||||||
return c.executeChain(func(authz Authorizer) EnforcementDecision {
|
return c.executeChain(func(authz Authorizer) EnforcementDecision {
|
||||||
|
//nolint:staticcheck
|
||||||
return authz.IntentionDefaultAllow(entCtx)
|
return authz.IntentionDefaultAllow(entCtx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -813,6 +813,15 @@ func (a *Agent) Start(ctx context.Context) error {
|
||||||
return fmt.Errorf("unexpected ACL default policy value of %q", a.config.ACLResolverSettings.ACLDefaultPolicy)
|
return fmt.Errorf("unexpected ACL default policy value of %q", a.config.ACLResolverSettings.ACLDefaultPolicy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If DefaultIntentionPolicy is defined, it should override
|
||||||
|
// the values inherited from ACLDefaultPolicy.
|
||||||
|
switch a.config.DefaultIntentionPolicy {
|
||||||
|
case "allow":
|
||||||
|
intentionDefaultAllow = true
|
||||||
|
case "deny":
|
||||||
|
intentionDefaultAllow = false
|
||||||
|
}
|
||||||
|
|
||||||
go a.baseDeps.ViewStore.Run(&lib.StopChannelContext{StopCh: a.shutdownCh})
|
go a.baseDeps.ViewStore.Run(&lib.StopChannelContext{StopCh: a.shutdownCh})
|
||||||
|
|
||||||
// Start the proxy config manager.
|
// Start the proxy config manager.
|
||||||
|
@ -4747,8 +4756,8 @@ func (a *Agent) proxyDataSources(server *consul.Server) proxycfg.DataSources {
|
||||||
sources.Health = proxycfgglue.ServerHealthBlocking(deps, proxycfgglue.ClientHealth(a.rpcClientHealth))
|
sources.Health = proxycfgglue.ServerHealthBlocking(deps, proxycfgglue.ClientHealth(a.rpcClientHealth))
|
||||||
sources.HTTPChecks = proxycfgglue.ServerHTTPChecks(deps, a.config.NodeName, proxycfgglue.CacheHTTPChecks(a.cache), a.State)
|
sources.HTTPChecks = proxycfgglue.ServerHTTPChecks(deps, a.config.NodeName, proxycfgglue.CacheHTTPChecks(a.cache), a.State)
|
||||||
sources.Intentions = proxycfgglue.ServerIntentions(deps)
|
sources.Intentions = proxycfgglue.ServerIntentions(deps)
|
||||||
sources.IntentionUpstreams = proxycfgglue.ServerIntentionUpstreams(deps)
|
sources.IntentionUpstreams = proxycfgglue.ServerIntentionUpstreams(deps, a.config.DefaultIntentionPolicy)
|
||||||
sources.IntentionUpstreamsDestination = proxycfgglue.ServerIntentionUpstreamsDestination(deps)
|
sources.IntentionUpstreamsDestination = proxycfgglue.ServerIntentionUpstreamsDestination(deps, a.config.DefaultIntentionPolicy)
|
||||||
sources.InternalServiceDump = proxycfgglue.ServerInternalServiceDump(deps, proxycfgglue.CacheInternalServiceDump(a.cache))
|
sources.InternalServiceDump = proxycfgglue.ServerInternalServiceDump(deps, proxycfgglue.CacheInternalServiceDump(a.cache))
|
||||||
sources.PeeringList = proxycfgglue.ServerPeeringList(deps)
|
sources.PeeringList = proxycfgglue.ServerPeeringList(deps)
|
||||||
sources.PeeredUpstreams = proxycfgglue.ServerPeeredUpstreams(deps)
|
sources.PeeredUpstreams = proxycfgglue.ServerPeeredUpstreams(deps)
|
||||||
|
|
|
@ -1760,8 +1760,12 @@ func (s *HTTPHandlers) AgentConnectAuthorize(resp http.ResponseWriter, req *http
|
||||||
// This is an L7 intention, so DENY.
|
// This is an L7 intention, so DENY.
|
||||||
authorized = false
|
authorized = false
|
||||||
}
|
}
|
||||||
|
} else if s.agent.config.DefaultIntentionPolicy != "" {
|
||||||
|
reason = "Default intention policy"
|
||||||
|
authorized = s.agent.config.DefaultIntentionPolicy == structs.IntentionDefaultPolicyAllow
|
||||||
} else {
|
} else {
|
||||||
reason = "Default behavior configured by ACLs"
|
reason = "Default behavior configured by ACLs"
|
||||||
|
//nolint:staticcheck
|
||||||
authorized = authz.IntentionDefaultAllow(nil) == acl.Allow
|
authorized = authz.IntentionDefaultAllow(nil) == acl.Allow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8015,76 +8015,104 @@ func TestAgentConnectAuthorize_serviceWrite(t *testing.T) {
|
||||||
assert.Equal(t, http.StatusForbidden, resp.Code)
|
assert.Equal(t, http.StatusForbidden, resp.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test when no intentions match w/ a default deny policy
|
func TestAgentConnectAuthorize_DefaultIntentionPolicy(t *testing.T) {
|
||||||
func TestAgentConnectAuthorize_defaultDeny(t *testing.T) {
|
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too slow for testing.Short")
|
t.Skip("too slow for testing.Short")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
a := NewTestAgent(t, TestACLConfig())
|
agentConfig := `primary_datacenter = "dc1"
|
||||||
defer a.Shutdown()
|
default_intention_policy = "%s"
|
||||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
`
|
||||||
|
aclBlock := `acl {
|
||||||
args := &structs.ConnectAuthorizeRequest{
|
enabled = true
|
||||||
Target: "foo",
|
default_policy = "%s"
|
||||||
ClientCertURI: connect.TestSpiffeIDService(t, "web").URI().String(),
|
tokens {
|
||||||
|
initial_management = "root"
|
||||||
|
agent = "root"
|
||||||
|
agent_recovery = "towel"
|
||||||
}
|
}
|
||||||
req, _ := http.NewRequest("POST", "/v1/agent/connect/authorize", jsonReader(args))
|
|
||||||
req.Header.Add("X-Consul-Token", "root")
|
|
||||||
resp := httptest.NewRecorder()
|
|
||||||
a.srv.h.ServeHTTP(resp, req)
|
|
||||||
assert.Equal(t, 200, resp.Code)
|
|
||||||
|
|
||||||
dec := json.NewDecoder(resp.Body)
|
|
||||||
obj := &connectAuthorizeResp{}
|
|
||||||
require.NoError(t, dec.Decode(obj))
|
|
||||||
assert.False(t, obj.Authorized)
|
|
||||||
assert.Contains(t, obj.Reason, "Default behavior")
|
|
||||||
}
|
}
|
||||||
|
`
|
||||||
|
|
||||||
// Test when no intentions match w/ a default allow policy
|
type testcase struct {
|
||||||
func TestAgentConnectAuthorize_defaultAllow(t *testing.T) {
|
aclsEnabled bool
|
||||||
if testing.Short() {
|
defaultACL string
|
||||||
t.Skip("too slow for testing.Short")
|
defaultIxn string
|
||||||
|
expectAuthz bool
|
||||||
|
expectReason string
|
||||||
}
|
}
|
||||||
|
tcs := map[string]testcase{
|
||||||
|
"no ACLs, default intention allow": {
|
||||||
|
aclsEnabled: false,
|
||||||
|
defaultIxn: "allow",
|
||||||
|
expectAuthz: true,
|
||||||
|
expectReason: "Default intention policy",
|
||||||
|
},
|
||||||
|
"no ACLs, default intention deny": {
|
||||||
|
aclsEnabled: false,
|
||||||
|
defaultIxn: "deny",
|
||||||
|
expectAuthz: false,
|
||||||
|
expectReason: "Default intention policy",
|
||||||
|
},
|
||||||
|
"ACL deny, no intention policy": {
|
||||||
|
aclsEnabled: true,
|
||||||
|
defaultACL: "deny",
|
||||||
|
expectAuthz: false,
|
||||||
|
expectReason: "Default behavior configured by ACLs",
|
||||||
|
},
|
||||||
|
"ACL allow, no intention policy": {
|
||||||
|
aclsEnabled: true,
|
||||||
|
defaultACL: "allow",
|
||||||
|
expectAuthz: true,
|
||||||
|
expectReason: "Default behavior configured by ACLs",
|
||||||
|
},
|
||||||
|
"ACL deny, default intentions allow": {
|
||||||
|
aclsEnabled: true,
|
||||||
|
defaultACL: "deny",
|
||||||
|
defaultIxn: "allow",
|
||||||
|
expectAuthz: true,
|
||||||
|
expectReason: "Default intention policy",
|
||||||
|
},
|
||||||
|
"ACL allow, default intentions deny": {
|
||||||
|
aclsEnabled: true,
|
||||||
|
defaultACL: "allow",
|
||||||
|
defaultIxn: "deny",
|
||||||
|
expectAuthz: false,
|
||||||
|
expectReason: "Default intention policy",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, tc := range tcs {
|
||||||
|
tc := tc
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
t.Parallel()
|
conf := fmt.Sprintf(agentConfig, tc.defaultIxn)
|
||||||
|
if tc.aclsEnabled {
|
||||||
dc1 := "dc1"
|
conf += fmt.Sprintf(aclBlock, tc.defaultACL)
|
||||||
a := NewTestAgent(t, `
|
|
||||||
primary_datacenter = "`+dc1+`"
|
|
||||||
|
|
||||||
acl {
|
|
||||||
enabled = true
|
|
||||||
default_policy = "allow"
|
|
||||||
|
|
||||||
tokens {
|
|
||||||
initial_management = "root"
|
|
||||||
agent = "root"
|
|
||||||
agent_recovery = "towel"
|
|
||||||
}
|
}
|
||||||
}
|
a := NewTestAgent(t, conf)
|
||||||
`)
|
|
||||||
defer a.Shutdown()
|
|
||||||
testrpc.WaitForTestAgent(t, a.RPC, dc1)
|
|
||||||
|
|
||||||
args := &structs.ConnectAuthorizeRequest{
|
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||||
Target: "foo",
|
|
||||||
ClientCertURI: connect.TestSpiffeIDService(t, "web").URI().String(),
|
args := &structs.ConnectAuthorizeRequest{
|
||||||
|
Target: "foo",
|
||||||
|
ClientCertURI: connect.TestSpiffeIDService(t, "web").URI().String(),
|
||||||
|
}
|
||||||
|
req, _ := http.NewRequest("POST", "/v1/agent/connect/authorize", jsonReader(args))
|
||||||
|
req.Header.Add("X-Consul-Token", "root")
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
a.srv.h.ServeHTTP(resp, req)
|
||||||
|
assert.Equal(t, 200, resp.Code)
|
||||||
|
|
||||||
|
dec := json.NewDecoder(resp.Body)
|
||||||
|
obj := &connectAuthorizeResp{}
|
||||||
|
require.NoError(t, dec.Decode(obj))
|
||||||
|
assert.Equal(t, tc.expectAuthz, obj.Authorized)
|
||||||
|
assert.Contains(t, obj.Reason, tc.expectReason)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
req, _ := http.NewRequest("POST", "/v1/agent/connect/authorize", jsonReader(args))
|
|
||||||
req.Header.Add("X-Consul-Token", "root")
|
|
||||||
resp := httptest.NewRecorder()
|
|
||||||
a.srv.h.ServeHTTP(resp, req)
|
|
||||||
assert.Equal(t, 200, resp.Code)
|
|
||||||
|
|
||||||
dec := json.NewDecoder(resp.Body)
|
|
||||||
obj := &connectAuthorizeResp{}
|
|
||||||
require.NoError(t, dec.Decode(obj))
|
|
||||||
assert.True(t, obj.Authorized)
|
|
||||||
assert.Contains(t, obj.Reason, "Default behavior")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAgent_Host(t *testing.T) {
|
func TestAgent_Host(t *testing.T) {
|
||||||
|
|
|
@ -1000,6 +1000,7 @@ func (b *builder) build() (rt RuntimeConfig, err error) {
|
||||||
DataDir: dataDir,
|
DataDir: dataDir,
|
||||||
Datacenter: datacenter,
|
Datacenter: datacenter,
|
||||||
DefaultQueryTime: b.durationVal("default_query_time", c.DefaultQueryTime),
|
DefaultQueryTime: b.durationVal("default_query_time", c.DefaultQueryTime),
|
||||||
|
DefaultIntentionPolicy: stringVal(c.DefaultIntentionPolicy),
|
||||||
DevMode: boolVal(b.opts.DevMode),
|
DevMode: boolVal(b.opts.DevMode),
|
||||||
DisableAnonymousSignature: boolVal(c.DisableAnonymousSignature),
|
DisableAnonymousSignature: boolVal(c.DisableAnonymousSignature),
|
||||||
DisableCoordinates: boolVal(c.DisableCoordinates),
|
DisableCoordinates: boolVal(c.DisableCoordinates),
|
||||||
|
|
|
@ -165,6 +165,7 @@ type Config struct {
|
||||||
DataDir *string `mapstructure:"data_dir" json:"data_dir,omitempty"`
|
DataDir *string `mapstructure:"data_dir" json:"data_dir,omitempty"`
|
||||||
Datacenter *string `mapstructure:"datacenter" json:"datacenter,omitempty"`
|
Datacenter *string `mapstructure:"datacenter" json:"datacenter,omitempty"`
|
||||||
DefaultQueryTime *string `mapstructure:"default_query_time" json:"default_query_time,omitempty"`
|
DefaultQueryTime *string `mapstructure:"default_query_time" json:"default_query_time,omitempty"`
|
||||||
|
DefaultIntentionPolicy *string `mapstructure:"default_intention_policy" json:"default_intention_policy,omitempty"`
|
||||||
DisableAnonymousSignature *bool `mapstructure:"disable_anonymous_signature" json:"disable_anonymous_signature,omitempty"`
|
DisableAnonymousSignature *bool `mapstructure:"disable_anonymous_signature" json:"disable_anonymous_signature,omitempty"`
|
||||||
DisableCoordinates *bool `mapstructure:"disable_coordinates" json:"disable_coordinates,omitempty"`
|
DisableCoordinates *bool `mapstructure:"disable_coordinates" json:"disable_coordinates,omitempty"`
|
||||||
DisableHostNodeID *bool `mapstructure:"disable_host_node_id" json:"disable_host_node_id,omitempty"`
|
DisableHostNodeID *bool `mapstructure:"disable_host_node_id" json:"disable_host_node_id,omitempty"`
|
||||||
|
|
|
@ -564,6 +564,15 @@ type RuntimeConfig struct {
|
||||||
// flag: -data-dir string
|
// flag: -data-dir string
|
||||||
DataDir string
|
DataDir string
|
||||||
|
|
||||||
|
// DefaultIntentionPolicy is used to define a default intention action for all
|
||||||
|
// sources and destinations. Possible values are "allow", "deny", or "" (blank).
|
||||||
|
// For compatibility, falls back to ACLResolverSettings.ACLDefaultPolicy (which
|
||||||
|
// itself has a default of "allow") if left blank. Future versions of Consul
|
||||||
|
// will default this field to "deny" to be secure by default.
|
||||||
|
//
|
||||||
|
// hcl: default_intention_policy = string
|
||||||
|
DefaultIntentionPolicy string
|
||||||
|
|
||||||
// DefaultQueryTime is the amount of time a blocking query will wait before
|
// DefaultQueryTime is the amount of time a blocking query will wait before
|
||||||
// Consul will force a response. This value can be overridden by the 'wait'
|
// Consul will force a response. This value can be overridden by the 'wait'
|
||||||
// query parameter.
|
// query parameter.
|
||||||
|
|
|
@ -134,11 +134,11 @@
|
||||||
"ClientSecret": "hidden",
|
"ClientSecret": "hidden",
|
||||||
"Hostname": "",
|
"Hostname": "",
|
||||||
"ManagementToken": "hidden",
|
"ManagementToken": "hidden",
|
||||||
|
"NodeID": "",
|
||||||
|
"NodeName": "",
|
||||||
"ResourceID": "cluster1",
|
"ResourceID": "cluster1",
|
||||||
"ScadaAddress": "",
|
"ScadaAddress": "",
|
||||||
"TLSConfig": null,
|
"TLSConfig": null
|
||||||
"NodeID": "",
|
|
||||||
"NodeName": ""
|
|
||||||
},
|
},
|
||||||
"ConfigEntryBootstrap": [],
|
"ConfigEntryBootstrap": [],
|
||||||
"ConnectCAConfig": {},
|
"ConnectCAConfig": {},
|
||||||
|
@ -185,6 +185,7 @@
|
||||||
"DNSUseCache": false,
|
"DNSUseCache": false,
|
||||||
"DataDir": "",
|
"DataDir": "",
|
||||||
"Datacenter": "",
|
"Datacenter": "",
|
||||||
|
"DefaultIntentionPolicy": "",
|
||||||
"DefaultQueryTime": "0s",
|
"DefaultQueryTime": "0s",
|
||||||
"DevMode": false,
|
"DevMode": false,
|
||||||
"DisableAnonymousSignature": false,
|
"DisableAnonymousSignature": false,
|
||||||
|
|
|
@ -221,6 +221,23 @@ type ACLResolverSettings struct {
|
||||||
ACLDefaultPolicy string
|
ACLDefaultPolicy string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a ACLResolverSettings) CheckACLs() error {
|
||||||
|
switch a.ACLDefaultPolicy {
|
||||||
|
case "allow":
|
||||||
|
case "deny":
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unsupported default ACL policy: %s", a.ACLDefaultPolicy)
|
||||||
|
}
|
||||||
|
switch a.ACLDownPolicy {
|
||||||
|
case "allow":
|
||||||
|
case "deny":
|
||||||
|
case "async-cache", "extend-cache":
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unsupported down ACL policy: %s", a.ACLDownPolicy)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s ACLResolverSettings) IsDefaultAllow() (bool, error) {
|
func (s ACLResolverSettings) IsDefaultAllow() (bool, error) {
|
||||||
switch s.ACLDefaultPolicy {
|
switch s.ACLDefaultPolicy {
|
||||||
case "allow":
|
case "allow":
|
||||||
|
|
|
@ -107,7 +107,7 @@ func NewClient(config *Config, deps Deps) (*Client, error) {
|
||||||
if config.DataDir == "" {
|
if config.DataDir == "" {
|
||||||
return nil, fmt.Errorf("Config must provide a DataDir")
|
return nil, fmt.Errorf("Config must provide a DataDir")
|
||||||
}
|
}
|
||||||
if err := config.CheckACL(); err != nil {
|
if err := config.CheckEnumStrings(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -411,6 +411,13 @@ type Config struct {
|
||||||
// datacenters should exclusively traverse mesh gateways.
|
// datacenters should exclusively traverse mesh gateways.
|
||||||
ConnectMeshGatewayWANFederationEnabled bool
|
ConnectMeshGatewayWANFederationEnabled bool
|
||||||
|
|
||||||
|
// DefaultIntentionPolicy is used to define a default intention action for all
|
||||||
|
// sources and destinations. Possible values are "allow", "deny", or "" (blank).
|
||||||
|
// For compatibility, falls back to ACLResolverSettings.ACLDefaultPolicy (which
|
||||||
|
// itself has a default of "allow") if left blank. Future versions of Consul
|
||||||
|
// will default this field to "deny" to be secure by default.
|
||||||
|
DefaultIntentionPolicy string
|
||||||
|
|
||||||
// DisableFederationStateAntiEntropy solely exists for use in unit tests to
|
// DisableFederationStateAntiEntropy solely exists for use in unit tests to
|
||||||
// disable a background routine.
|
// disable a background routine.
|
||||||
DisableFederationStateAntiEntropy bool
|
DisableFederationStateAntiEntropy bool
|
||||||
|
@ -470,21 +477,17 @@ func (c *Config) CheckProtocolVersion() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckACL validates the ACL configuration.
|
// CheckEnumStrings validates string configuration which must be specific values.
|
||||||
// TODO: move this to ACLResolverSettings
|
func (c *Config) CheckEnumStrings() error {
|
||||||
func (c *Config) CheckACL() error {
|
if err := c.ACLResolverSettings.CheckACLs(); err != nil {
|
||||||
switch c.ACLResolverSettings.ACLDefaultPolicy {
|
return err
|
||||||
case "allow":
|
|
||||||
case "deny":
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unsupported default ACL policy: %s", c.ACLResolverSettings.ACLDefaultPolicy)
|
|
||||||
}
|
}
|
||||||
switch c.ACLResolverSettings.ACLDownPolicy {
|
switch c.DefaultIntentionPolicy {
|
||||||
case "allow":
|
case structs.IntentionDefaultPolicyAllow:
|
||||||
case "deny":
|
case structs.IntentionDefaultPolicyDeny:
|
||||||
case "async-cache", "extend-cache":
|
case "":
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unsupported down ACL policy: %s", c.ACLResolverSettings.ACLDownPolicy)
|
return fmt.Errorf("Unsupported default intention policy: %s", c.DefaultIntentionPolicy)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -752,19 +752,7 @@ func (s *Intention) Check(args *structs.IntentionQueryRequest, reply *structs.In
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: the default intention policy is like an intention with a
|
defaultAllow := DefaultIntentionAllow(authz, s.srv.config.DefaultIntentionPolicy)
|
||||||
// wildcarded destination in that it is limited to L4-only.
|
|
||||||
|
|
||||||
// No match, we need to determine the default behavior. We do this by
|
|
||||||
// fetching the default intention behavior from the resolved authorizer.
|
|
||||||
// The default behavior if ACLs are disabled is to allow connections
|
|
||||||
// to mimic the behavior of Consul itself: everything is allowed if
|
|
||||||
// ACLs are disabled.
|
|
||||||
//
|
|
||||||
// NOTE(mitchellh): This is the same behavior as the agent authorize
|
|
||||||
// endpoint. If this behavior is incorrect, we should also change it there
|
|
||||||
// which is much more important.
|
|
||||||
defaultDecision := authz.IntentionDefaultAllow(nil)
|
|
||||||
|
|
||||||
store := s.srv.fsm.State()
|
store := s.srv.fsm.State()
|
||||||
|
|
||||||
|
@ -784,7 +772,7 @@ func (s *Intention) Check(args *structs.IntentionQueryRequest, reply *structs.In
|
||||||
Partition: query.DestinationPartition,
|
Partition: query.DestinationPartition,
|
||||||
Intentions: intentions,
|
Intentions: intentions,
|
||||||
MatchType: structs.IntentionMatchDestination,
|
MatchType: structs.IntentionMatchDestination,
|
||||||
DefaultDecision: defaultDecision,
|
DefaultAllow: defaultAllow,
|
||||||
AllowPermissions: false,
|
AllowPermissions: false,
|
||||||
}
|
}
|
||||||
decision, err := store.IntentionDecision(opts)
|
decision, err := store.IntentionDecision(opts)
|
||||||
|
|
|
@ -1960,106 +1960,89 @@ func TestIntentionMatch_acl(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the Check method defaults to allow with no ACL set.
|
func TestIntentionCheck(t *testing.T) {
|
||||||
func TestIntentionCheck_defaultNoACL(t *testing.T) {
|
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too slow for testing.Short")
|
t.Skip("too slow for testing.Short")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
dir1, s1 := testServer(t)
|
type testcase struct {
|
||||||
defer os.RemoveAll(dir1)
|
aclsEnabled bool
|
||||||
defer s1.Shutdown()
|
defaultACL string
|
||||||
codec := rpcClient(t, s1)
|
defaultIxn string
|
||||||
defer codec.Close()
|
expectAllowed bool
|
||||||
|
}
|
||||||
waitForLeaderEstablishment(t, s1)
|
tcs := map[string]testcase{
|
||||||
|
"acls disabled, no default intention policy": {
|
||||||
// Test
|
aclsEnabled: false,
|
||||||
req := &structs.IntentionQueryRequest{
|
expectAllowed: true,
|
||||||
Datacenter: "dc1",
|
},
|
||||||
Check: &structs.IntentionQueryCheck{
|
"acls disabled, default intention allow": {
|
||||||
SourceName: "bar",
|
aclsEnabled: false,
|
||||||
DestinationName: "qux",
|
defaultIxn: "allow",
|
||||||
SourceType: structs.IntentionSourceConsul,
|
expectAllowed: true,
|
||||||
|
},
|
||||||
|
"acls disabled, default intention deny": {
|
||||||
|
aclsEnabled: false,
|
||||||
|
defaultIxn: "deny",
|
||||||
|
expectAllowed: false,
|
||||||
|
},
|
||||||
|
"acls deny, no default intention policy": {
|
||||||
|
aclsEnabled: true,
|
||||||
|
defaultACL: "deny",
|
||||||
|
expectAllowed: false,
|
||||||
|
},
|
||||||
|
"acls allow, no default intention policy": {
|
||||||
|
aclsEnabled: true,
|
||||||
|
defaultACL: "allow",
|
||||||
|
expectAllowed: true,
|
||||||
|
},
|
||||||
|
"acls deny, default intention allow": {
|
||||||
|
aclsEnabled: true,
|
||||||
|
defaultACL: "deny",
|
||||||
|
defaultIxn: "allow",
|
||||||
|
expectAllowed: true,
|
||||||
|
},
|
||||||
|
"acls allow, default intention deny": {
|
||||||
|
aclsEnabled: true,
|
||||||
|
defaultACL: "allow",
|
||||||
|
defaultIxn: "deny",
|
||||||
|
expectAllowed: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var resp structs.IntentionQueryCheckResponse
|
for name, tc := range tcs {
|
||||||
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
|
tc := tc
|
||||||
require.True(t, resp.Allowed)
|
t.Run(name, func(t *testing.T) {
|
||||||
}
|
t.Parallel()
|
||||||
|
_, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
|
if tc.aclsEnabled {
|
||||||
|
c.PrimaryDatacenter = "dc1"
|
||||||
|
c.ACLsEnabled = true
|
||||||
|
c.ACLInitialManagementToken = "root"
|
||||||
|
c.ACLResolverSettings.ACLDefaultPolicy = tc.defaultACL
|
||||||
|
}
|
||||||
|
c.DefaultIntentionPolicy = tc.defaultIxn
|
||||||
|
})
|
||||||
|
codec := rpcClient(t, s1)
|
||||||
|
|
||||||
// Test the Check method defaults to deny with allowlist ACLs.
|
waitForLeaderEstablishment(t, s1)
|
||||||
func TestIntentionCheck_defaultACLDeny(t *testing.T) {
|
|
||||||
if testing.Short() {
|
req := &structs.IntentionQueryRequest{
|
||||||
t.Skip("too slow for testing.Short")
|
Datacenter: "dc1",
|
||||||
|
Check: &structs.IntentionQueryCheck{
|
||||||
|
SourceName: "bar",
|
||||||
|
DestinationName: "qux",
|
||||||
|
SourceType: structs.IntentionSourceConsul,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
req.Token = "root"
|
||||||
|
|
||||||
|
var resp structs.IntentionQueryCheckResponse
|
||||||
|
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
|
||||||
|
require.Equal(t, tc.expectAllowed, resp.Allowed)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
||||||
c.PrimaryDatacenter = "dc1"
|
|
||||||
c.ACLsEnabled = true
|
|
||||||
c.ACLInitialManagementToken = "root"
|
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
|
||||||
})
|
|
||||||
defer os.RemoveAll(dir1)
|
|
||||||
defer s1.Shutdown()
|
|
||||||
codec := rpcClient(t, s1)
|
|
||||||
defer codec.Close()
|
|
||||||
|
|
||||||
waitForLeaderEstablishment(t, s1)
|
|
||||||
|
|
||||||
// Check
|
|
||||||
req := &structs.IntentionQueryRequest{
|
|
||||||
Datacenter: "dc1",
|
|
||||||
Check: &structs.IntentionQueryCheck{
|
|
||||||
SourceName: "bar",
|
|
||||||
DestinationName: "qux",
|
|
||||||
SourceType: structs.IntentionSourceConsul,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
req.Token = "root"
|
|
||||||
var resp structs.IntentionQueryCheckResponse
|
|
||||||
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
|
|
||||||
require.False(t, resp.Allowed)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the Check method defaults to deny with denylist ACLs.
|
|
||||||
func TestIntentionCheck_defaultACLAllow(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("too slow for testing.Short")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
||||||
c.PrimaryDatacenter = "dc1"
|
|
||||||
c.ACLsEnabled = true
|
|
||||||
c.ACLInitialManagementToken = "root"
|
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
|
||||||
})
|
|
||||||
defer os.RemoveAll(dir1)
|
|
||||||
defer s1.Shutdown()
|
|
||||||
codec := rpcClient(t, s1)
|
|
||||||
defer codec.Close()
|
|
||||||
|
|
||||||
waitForLeaderEstablishment(t, s1)
|
|
||||||
|
|
||||||
// Check
|
|
||||||
req := &structs.IntentionQueryRequest{
|
|
||||||
Datacenter: "dc1",
|
|
||||||
Check: &structs.IntentionQueryCheck{
|
|
||||||
SourceName: "bar",
|
|
||||||
DestinationName: "qux",
|
|
||||||
SourceType: structs.IntentionSourceConsul,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
req.Token = "root"
|
|
||||||
var resp structs.IntentionQueryCheckResponse
|
|
||||||
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Check", req, &resp))
|
|
||||||
require.True(t, resp.Allowed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the Check method requires service:read permission.
|
// Test the Check method requires service:read permission.
|
||||||
|
|
|
@ -306,7 +306,7 @@ func (m *Internal) ServiceTopology(args *structs.ServiceSpecificRequest, reply *
|
||||||
&args.QueryOptions,
|
&args.QueryOptions,
|
||||||
&reply.QueryMeta,
|
&reply.QueryMeta,
|
||||||
func(ws memdb.WatchSet, state *state.Store) error {
|
func(ws memdb.WatchSet, state *state.Store) error {
|
||||||
defaultAllow := authz.IntentionDefaultAllow(nil)
|
defaultAllow := DefaultIntentionAllow(authz, m.srv.config.DefaultIntentionPolicy)
|
||||||
|
|
||||||
index, topology, err := state.ServiceTopology(ws, args.Datacenter, args.ServiceName, args.ServiceKind, defaultAllow, &args.EnterpriseMeta)
|
index, topology, err := state.ServiceTopology(ws, args.Datacenter, args.ServiceName, args.ServiceKind, defaultAllow, &args.EnterpriseMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -375,10 +375,10 @@ func (m *Internal) internalUpstreams(args *structs.ServiceSpecificRequest, reply
|
||||||
&args.QueryOptions,
|
&args.QueryOptions,
|
||||||
&reply.QueryMeta,
|
&reply.QueryMeta,
|
||||||
func(ws memdb.WatchSet, state *state.Store) error {
|
func(ws memdb.WatchSet, state *state.Store) error {
|
||||||
defaultDecision := authz.IntentionDefaultAllow(nil)
|
defaultAllow := DefaultIntentionAllow(authz, m.srv.config.DefaultIntentionPolicy)
|
||||||
|
|
||||||
sn := structs.NewServiceName(args.ServiceName, &args.EnterpriseMeta)
|
sn := structs.NewServiceName(args.ServiceName, &args.EnterpriseMeta)
|
||||||
index, services, err := state.IntentionTopology(ws, sn, false, defaultDecision, intentionTarget)
|
index, services, err := state.IntentionTopology(ws, sn, false, defaultAllow, intentionTarget)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2384,14 +2384,12 @@ func TestInternal_ServiceTopology_ACL(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
_, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLInitialManagementToken = TestDefaultInitialManagementToken
|
c.ACLInitialManagementToken = TestDefaultInitialManagementToken
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
|
||||||
defer s1.Shutdown()
|
|
||||||
|
|
||||||
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
||||||
|
|
||||||
|
@ -2473,6 +2471,40 @@ service "web" { policy = "read" }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that default intention deny policy overrides the ACL allow policy.
|
||||||
|
// More comprehensive tests are done at the state store so this is minimal
|
||||||
|
// coverage to be confident that the override happens.
|
||||||
|
func TestInternal_ServiceTopology_DefaultIntentionPolicy(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
_, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
|
c.PrimaryDatacenter = "dc1"
|
||||||
|
c.ACLsEnabled = true
|
||||||
|
c.ACLInitialManagementToken = TestDefaultInitialManagementToken
|
||||||
|
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||||
|
c.DefaultIntentionPolicy = "deny"
|
||||||
|
})
|
||||||
|
|
||||||
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
||||||
|
codec := rpcClient(t, s1)
|
||||||
|
|
||||||
|
registerTestTopologyEntries(t, codec, TestDefaultInitialManagementToken)
|
||||||
|
|
||||||
|
args := structs.ServiceSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
ServiceName: "redis",
|
||||||
|
QueryOptions: structs.QueryOptions{Token: TestDefaultInitialManagementToken},
|
||||||
|
}
|
||||||
|
var out structs.IndexedServiceTopology
|
||||||
|
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out))
|
||||||
|
|
||||||
|
webSN := structs.NewServiceName("web", acl.DefaultEnterpriseMeta())
|
||||||
|
require.False(t, out.ServiceTopology.DownstreamDecisions[webSN.String()].DefaultAllow)
|
||||||
|
}
|
||||||
|
|
||||||
func TestInternal_IntentionUpstreams(t *testing.T) {
|
func TestInternal_IntentionUpstreams(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too slow for testing.Short")
|
t.Skip("too slow for testing.Short")
|
||||||
|
|
|
@ -526,7 +526,7 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server,
|
||||||
if config.DataDir == "" && !config.DevMode {
|
if config.DataDir == "" && !config.DevMode {
|
||||||
return nil, fmt.Errorf("Config must provide a DataDir")
|
return nil, fmt.Errorf("Config must provide a DataDir")
|
||||||
}
|
}
|
||||||
if err := config.CheckACL(); err != nil {
|
if err := config.CheckEnumStrings(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4315,7 +4315,7 @@ func (s *Store) ServiceTopology(
|
||||||
ws memdb.WatchSet,
|
ws memdb.WatchSet,
|
||||||
dc, service string,
|
dc, service string,
|
||||||
kind structs.ServiceKind,
|
kind structs.ServiceKind,
|
||||||
defaultAllow acl.EnforcementDecision,
|
defaultAllow bool,
|
||||||
entMeta *acl.EnterpriseMeta,
|
entMeta *acl.EnterpriseMeta,
|
||||||
) (uint64, *structs.ServiceTopology, error) {
|
) (uint64, *structs.ServiceTopology, error) {
|
||||||
tx := s.db.ReadTxn()
|
tx := s.db.ReadTxn()
|
||||||
|
@ -4466,7 +4466,7 @@ func (s *Store) ServiceTopology(
|
||||||
Partition: un.PartitionOrDefault(),
|
Partition: un.PartitionOrDefault(),
|
||||||
Intentions: srcIntentions,
|
Intentions: srcIntentions,
|
||||||
MatchType: structs.IntentionMatchDestination,
|
MatchType: structs.IntentionMatchDestination,
|
||||||
DefaultDecision: defaultAllow,
|
DefaultAllow: defaultAllow,
|
||||||
AllowPermissions: false,
|
AllowPermissions: false,
|
||||||
}
|
}
|
||||||
decision, err := s.IntentionDecision(opts)
|
decision, err := s.IntentionDecision(opts)
|
||||||
|
@ -4590,7 +4590,7 @@ func (s *Store) ServiceTopology(
|
||||||
Partition: dn.PartitionOrDefault(),
|
Partition: dn.PartitionOrDefault(),
|
||||||
Intentions: dstIntentions,
|
Intentions: dstIntentions,
|
||||||
MatchType: structs.IntentionMatchSource,
|
MatchType: structs.IntentionMatchSource,
|
||||||
DefaultDecision: defaultAllow,
|
DefaultAllow: defaultAllow,
|
||||||
AllowPermissions: false,
|
AllowPermissions: false,
|
||||||
}
|
}
|
||||||
decision, err := s.IntentionDecision(opts)
|
decision, err := s.IntentionDecision(opts)
|
||||||
|
|
|
@ -743,7 +743,7 @@ type IntentionDecisionOpts struct {
|
||||||
Peer string
|
Peer string
|
||||||
Intentions structs.SimplifiedIntentions
|
Intentions structs.SimplifiedIntentions
|
||||||
MatchType structs.IntentionMatchType
|
MatchType structs.IntentionMatchType
|
||||||
DefaultDecision acl.EnforcementDecision
|
DefaultAllow bool
|
||||||
AllowPermissions bool
|
AllowPermissions bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -763,7 +763,7 @@ func (s *Store) IntentionDecision(opts IntentionDecisionOpts) (structs.Intention
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := structs.IntentionDecisionSummary{
|
resp := structs.IntentionDecisionSummary{
|
||||||
DefaultAllow: opts.DefaultDecision == acl.Allow,
|
DefaultAllow: opts.DefaultAllow,
|
||||||
}
|
}
|
||||||
if ixnMatch == nil {
|
if ixnMatch == nil {
|
||||||
// No intention found, fall back to default
|
// No intention found, fall back to default
|
||||||
|
@ -1029,13 +1029,13 @@ func (s *Store) IntentionTopology(
|
||||||
ws memdb.WatchSet,
|
ws memdb.WatchSet,
|
||||||
target structs.ServiceName,
|
target structs.ServiceName,
|
||||||
downstreams bool,
|
downstreams bool,
|
||||||
defaultDecision acl.EnforcementDecision,
|
defaultAllow bool,
|
||||||
intentionTarget structs.IntentionTargetType,
|
intentionTarget structs.IntentionTargetType,
|
||||||
) (uint64, structs.ServiceList, error) {
|
) (uint64, structs.ServiceList, error) {
|
||||||
tx := s.db.ReadTxn()
|
tx := s.db.ReadTxn()
|
||||||
defer tx.Abort()
|
defer tx.Abort()
|
||||||
|
|
||||||
idx, services, err := s.intentionTopologyTxn(tx, ws, target, downstreams, defaultDecision, intentionTarget)
|
idx, services, err := s.intentionTopologyTxn(tx, ws, target, downstreams, defaultAllow, intentionTarget)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
requested := "upstreams"
|
requested := "upstreams"
|
||||||
if downstreams {
|
if downstreams {
|
||||||
|
@ -1055,7 +1055,7 @@ func (s *Store) intentionTopologyTxn(
|
||||||
tx ReadTxn, ws memdb.WatchSet,
|
tx ReadTxn, ws memdb.WatchSet,
|
||||||
target structs.ServiceName,
|
target structs.ServiceName,
|
||||||
downstreams bool,
|
downstreams bool,
|
||||||
defaultDecision acl.EnforcementDecision,
|
defaultAllow bool,
|
||||||
intentionTarget structs.IntentionTargetType,
|
intentionTarget structs.IntentionTargetType,
|
||||||
) (uint64, []ServiceWithDecision, error) {
|
) (uint64, []ServiceWithDecision, error) {
|
||||||
|
|
||||||
|
@ -1163,7 +1163,7 @@ func (s *Store) intentionTopologyTxn(
|
||||||
Partition: candidate.PartitionOrDefault(),
|
Partition: candidate.PartitionOrDefault(),
|
||||||
Intentions: intentions,
|
Intentions: intentions,
|
||||||
MatchType: decisionMatchType,
|
MatchType: decisionMatchType,
|
||||||
DefaultDecision: defaultDecision,
|
DefaultAllow: defaultAllow,
|
||||||
AllowPermissions: true,
|
AllowPermissions: true,
|
||||||
}
|
}
|
||||||
decision, err := s.IntentionDecision(opts)
|
decision, err := s.IntentionDecision(opts)
|
||||||
|
|
|
@ -1966,27 +1966,27 @@ func TestStore_IntentionDecision(t *testing.T) {
|
||||||
src string
|
src string
|
||||||
dst string
|
dst string
|
||||||
matchType structs.IntentionMatchType
|
matchType structs.IntentionMatchType
|
||||||
defaultDecision acl.EnforcementDecision
|
defaultAllow bool
|
||||||
allowPermissions bool
|
allowPermissions bool
|
||||||
expect structs.IntentionDecisionSummary
|
expect structs.IntentionDecisionSummary
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no matching intention and default deny",
|
name: "no matching intention and default deny",
|
||||||
src: "does-not-exist",
|
src: "does-not-exist",
|
||||||
dst: "ditto",
|
dst: "ditto",
|
||||||
matchType: structs.IntentionMatchDestination,
|
matchType: structs.IntentionMatchDestination,
|
||||||
defaultDecision: acl.Deny,
|
defaultAllow: false,
|
||||||
expect: structs.IntentionDecisionSummary{
|
expect: structs.IntentionDecisionSummary{
|
||||||
Allowed: false,
|
Allowed: false,
|
||||||
DefaultAllow: false,
|
DefaultAllow: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no matching intention and default allow",
|
name: "no matching intention and default allow",
|
||||||
src: "does-not-exist",
|
src: "does-not-exist",
|
||||||
dst: "ditto",
|
dst: "ditto",
|
||||||
matchType: structs.IntentionMatchDestination,
|
matchType: structs.IntentionMatchDestination,
|
||||||
defaultDecision: acl.Allow,
|
defaultAllow: true,
|
||||||
expect: structs.IntentionDecisionSummary{
|
expect: structs.IntentionDecisionSummary{
|
||||||
Allowed: true,
|
Allowed: true,
|
||||||
DefaultAllow: true,
|
DefaultAllow: true,
|
||||||
|
@ -2079,7 +2079,7 @@ func TestStore_IntentionDecision(t *testing.T) {
|
||||||
Partition: acl.DefaultPartitionName,
|
Partition: acl.DefaultPartitionName,
|
||||||
Intentions: intentions,
|
Intentions: intentions,
|
||||||
MatchType: tc.matchType,
|
MatchType: tc.matchType,
|
||||||
DefaultDecision: tc.defaultDecision,
|
DefaultAllow: tc.defaultAllow,
|
||||||
AllowPermissions: tc.allowPermissions,
|
AllowPermissions: tc.allowPermissions,
|
||||||
}
|
}
|
||||||
decision, err := s.IntentionDecision(opts)
|
decision, err := s.IntentionDecision(opts)
|
||||||
|
@ -2161,7 +2161,7 @@ func TestStore_IntentionTopology(t *testing.T) {
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
defaultDecision acl.EnforcementDecision
|
defaultAllow bool
|
||||||
intentions []structs.ServiceIntentionsConfigEntry
|
intentions []structs.ServiceIntentionsConfigEntry
|
||||||
discoveryChains []structs.ConfigEntry
|
discoveryChains []structs.ConfigEntry
|
||||||
target structs.ServiceName
|
target structs.ServiceName
|
||||||
|
@ -2169,8 +2169,8 @@ func TestStore_IntentionTopology(t *testing.T) {
|
||||||
expect expect
|
expect expect
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "(upstream) acl allow all but intentions deny one",
|
name: "(upstream) default allow all but intentions deny one",
|
||||||
defaultDecision: acl.Allow,
|
defaultAllow: true,
|
||||||
intentions: []structs.ServiceIntentionsConfigEntry{
|
intentions: []structs.ServiceIntentionsConfigEntry{
|
||||||
{
|
{
|
||||||
Kind: structs.ServiceIntentions,
|
Kind: structs.ServiceIntentions,
|
||||||
|
@ -2196,8 +2196,8 @@ func TestStore_IntentionTopology(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "(upstream) acl allow includes virtual service",
|
name: "(upstream) default allow includes virtual service",
|
||||||
defaultDecision: acl.Allow,
|
defaultAllow: true,
|
||||||
discoveryChains: []structs.ConfigEntry{
|
discoveryChains: []structs.ConfigEntry{
|
||||||
&structs.ServiceResolverConfigEntry{
|
&structs.ServiceResolverConfigEntry{
|
||||||
Kind: structs.ServiceResolver,
|
Kind: structs.ServiceResolver,
|
||||||
|
@ -2225,8 +2225,8 @@ func TestStore_IntentionTopology(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "(upstream) acl deny all intentions allow virtual service",
|
name: "(upstream) default deny intentions allow virtual service",
|
||||||
defaultDecision: acl.Deny,
|
defaultAllow: false,
|
||||||
discoveryChains: []structs.ConfigEntry{
|
discoveryChains: []structs.ConfigEntry{
|
||||||
&structs.ServiceResolverConfigEntry{
|
&structs.ServiceResolverConfigEntry{
|
||||||
Kind: structs.ServiceResolver,
|
Kind: structs.ServiceResolver,
|
||||||
|
@ -2258,8 +2258,8 @@ func TestStore_IntentionTopology(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "(upstream) acl deny all intentions allow one",
|
name: "(upstream) default deny intentions allow one",
|
||||||
defaultDecision: acl.Deny,
|
defaultAllow: false,
|
||||||
intentions: []structs.ServiceIntentionsConfigEntry{
|
intentions: []structs.ServiceIntentionsConfigEntry{
|
||||||
{
|
{
|
||||||
Kind: structs.ServiceIntentions,
|
Kind: structs.ServiceIntentions,
|
||||||
|
@ -2285,8 +2285,8 @@ func TestStore_IntentionTopology(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "(downstream) acl allow all but intentions deny one",
|
name: "(downstream) default allow but intentions deny one",
|
||||||
defaultDecision: acl.Allow,
|
defaultAllow: true,
|
||||||
intentions: []structs.ServiceIntentionsConfigEntry{
|
intentions: []structs.ServiceIntentionsConfigEntry{
|
||||||
{
|
{
|
||||||
Kind: structs.ServiceIntentions,
|
Kind: structs.ServiceIntentions,
|
||||||
|
@ -2316,8 +2316,8 @@ func TestStore_IntentionTopology(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "(downstream) acl deny all intentions allow one",
|
name: "(downstream) default deny all intentions allow one",
|
||||||
defaultDecision: acl.Deny,
|
defaultAllow: false,
|
||||||
intentions: []structs.ServiceIntentionsConfigEntry{
|
intentions: []structs.ServiceIntentionsConfigEntry{
|
||||||
{
|
{
|
||||||
Kind: structs.ServiceIntentions,
|
Kind: structs.ServiceIntentions,
|
||||||
|
@ -2343,8 +2343,8 @@ func TestStore_IntentionTopology(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "acl deny but intention allow all overrides it",
|
name: "default deny but intention allow all overrides it",
|
||||||
defaultDecision: acl.Deny,
|
defaultAllow: false,
|
||||||
intentions: []structs.ServiceIntentionsConfigEntry{
|
intentions: []structs.ServiceIntentionsConfigEntry{
|
||||||
{
|
{
|
||||||
Kind: structs.ServiceIntentions,
|
Kind: structs.ServiceIntentions,
|
||||||
|
@ -2374,8 +2374,8 @@ func TestStore_IntentionTopology(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "acl allow but intention deny all overrides it",
|
name: "default allow but intention deny all overrides it",
|
||||||
defaultDecision: acl.Allow,
|
defaultAllow: true,
|
||||||
intentions: []structs.ServiceIntentionsConfigEntry{
|
intentions: []structs.ServiceIntentionsConfigEntry{
|
||||||
{
|
{
|
||||||
Kind: structs.ServiceIntentions,
|
Kind: structs.ServiceIntentions,
|
||||||
|
@ -2396,8 +2396,8 @@ func TestStore_IntentionTopology(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "acl deny but intention allow all overrides it",
|
name: "default deny but intention allow all overrides it",
|
||||||
defaultDecision: acl.Deny,
|
defaultAllow: false,
|
||||||
intentions: []structs.ServiceIntentionsConfigEntry{
|
intentions: []structs.ServiceIntentionsConfigEntry{
|
||||||
{
|
{
|
||||||
Kind: structs.ServiceIntentions,
|
Kind: structs.ServiceIntentions,
|
||||||
|
@ -2448,7 +2448,7 @@ func TestStore_IntentionTopology(t *testing.T) {
|
||||||
idx++
|
idx++
|
||||||
}
|
}
|
||||||
|
|
||||||
idx, got, err := s.IntentionTopology(nil, tt.target, tt.downstreams, tt.defaultDecision, structs.IntentionTargetService)
|
idx, got, err := s.IntentionTopology(nil, tt.target, tt.downstreams, tt.defaultAllow, structs.IntentionTargetService)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tt.expect.idx, idx)
|
require.Equal(t, tt.expect.idx, idx)
|
||||||
|
|
||||||
|
@ -2502,16 +2502,16 @@ func TestStore_IntentionTopology_Destination(t *testing.T) {
|
||||||
services structs.ServiceList
|
services structs.ServiceList
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
defaultDecision acl.EnforcementDecision
|
defaultAllow bool
|
||||||
intentions []structs.ServiceIntentionsConfigEntry
|
intentions []structs.ServiceIntentionsConfigEntry
|
||||||
target structs.ServiceName
|
target structs.ServiceName
|
||||||
downstreams bool
|
downstreams bool
|
||||||
expect expect
|
expect expect
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "(upstream) acl allow all but intentions deny one, destination target",
|
name: "(upstream) default allow all but intentions deny one, destination target",
|
||||||
defaultDecision: acl.Allow,
|
defaultAllow: true,
|
||||||
intentions: []structs.ServiceIntentionsConfigEntry{
|
intentions: []structs.ServiceIntentionsConfigEntry{
|
||||||
{
|
{
|
||||||
Kind: structs.ServiceIntentions,
|
Kind: structs.ServiceIntentions,
|
||||||
|
@ -2537,8 +2537,8 @@ func TestStore_IntentionTopology_Destination(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "(upstream) acl deny all intentions allow one, destination target",
|
name: "(upstream) default deny intentions allow one, destination target",
|
||||||
defaultDecision: acl.Deny,
|
defaultAllow: false,
|
||||||
intentions: []structs.ServiceIntentionsConfigEntry{
|
intentions: []structs.ServiceIntentionsConfigEntry{
|
||||||
{
|
{
|
||||||
Kind: structs.ServiceIntentions,
|
Kind: structs.ServiceIntentions,
|
||||||
|
@ -2564,8 +2564,8 @@ func TestStore_IntentionTopology_Destination(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "(upstream) acl deny all check only destinations show, service target",
|
name: "(upstream) default deny check only destinations show, service target",
|
||||||
defaultDecision: acl.Deny,
|
defaultAllow: false,
|
||||||
intentions: []structs.ServiceIntentionsConfigEntry{
|
intentions: []structs.ServiceIntentionsConfigEntry{
|
||||||
{
|
{
|
||||||
Kind: structs.ServiceIntentions,
|
Kind: structs.ServiceIntentions,
|
||||||
|
@ -2586,8 +2586,8 @@ func TestStore_IntentionTopology_Destination(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "(upstream) acl allow all check only destinations show, service target",
|
name: "(upstream) default allow check only destinations show, service target",
|
||||||
defaultDecision: acl.Allow,
|
defaultAllow: true,
|
||||||
intentions: []structs.ServiceIntentionsConfigEntry{
|
intentions: []structs.ServiceIntentionsConfigEntry{
|
||||||
{
|
{
|
||||||
Kind: structs.ServiceIntentions,
|
Kind: structs.ServiceIntentions,
|
||||||
|
@ -2638,7 +2638,7 @@ func TestStore_IntentionTopology_Destination(t *testing.T) {
|
||||||
idx++
|
idx++
|
||||||
}
|
}
|
||||||
|
|
||||||
idx, got, err := s.IntentionTopology(nil, tt.target, tt.downstreams, tt.defaultDecision, structs.IntentionTargetDestination)
|
idx, got, err := s.IntentionTopology(nil, tt.target, tt.downstreams, tt.defaultAllow, structs.IntentionTargetDestination)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tt.expect.idx, idx)
|
require.Equal(t, tt.expect.idx, idx)
|
||||||
|
|
||||||
|
@ -2665,7 +2665,7 @@ func TestStore_IntentionTopology_Watches(t *testing.T) {
|
||||||
target := structs.NewServiceName("web", structs.DefaultEnterpriseMetaInDefaultPartition())
|
target := structs.NewServiceName("web", structs.DefaultEnterpriseMetaInDefaultPartition())
|
||||||
|
|
||||||
ws := memdb.NewWatchSet()
|
ws := memdb.NewWatchSet()
|
||||||
index, got, err := s.IntentionTopology(ws, target, false, acl.Deny, structs.IntentionTargetService)
|
index, got, err := s.IntentionTopology(ws, target, false, false, structs.IntentionTargetService)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, uint64(0), index)
|
require.Equal(t, uint64(0), index)
|
||||||
require.Empty(t, got)
|
require.Empty(t, got)
|
||||||
|
@ -2687,7 +2687,7 @@ func TestStore_IntentionTopology_Watches(t *testing.T) {
|
||||||
|
|
||||||
// Reset the WatchSet
|
// Reset the WatchSet
|
||||||
ws = memdb.NewWatchSet()
|
ws = memdb.NewWatchSet()
|
||||||
index, got, err = s.IntentionTopology(ws, target, false, acl.Deny, structs.IntentionTargetService)
|
index, got, err = s.IntentionTopology(ws, target, false, false, structs.IntentionTargetService)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, uint64(2), index)
|
require.Equal(t, uint64(2), index)
|
||||||
// Because API is a virtual service, it is included in this output.
|
// Because API is a virtual service, it is included in this output.
|
||||||
|
@ -2709,7 +2709,7 @@ func TestStore_IntentionTopology_Watches(t *testing.T) {
|
||||||
// require.False(t, watchFired(ws))
|
// require.False(t, watchFired(ws))
|
||||||
|
|
||||||
// Result should not have changed
|
// Result should not have changed
|
||||||
index, got, err = s.IntentionTopology(ws, target, false, acl.Deny, structs.IntentionTargetService)
|
index, got, err = s.IntentionTopology(ws, target, false, false, structs.IntentionTargetService)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, uint64(3), index)
|
require.Equal(t, uint64(3), index)
|
||||||
require.Equal(t, structs.ServiceList{structs.NewServiceName("api", nil)}, got)
|
require.Equal(t, structs.ServiceList{structs.NewServiceName("api", nil)}, got)
|
||||||
|
@ -2724,7 +2724,7 @@ func TestStore_IntentionTopology_Watches(t *testing.T) {
|
||||||
require.True(t, watchFired(ws))
|
require.True(t, watchFired(ws))
|
||||||
|
|
||||||
// Reset the WatchSet
|
// Reset the WatchSet
|
||||||
index, got, err = s.IntentionTopology(nil, target, false, acl.Deny, structs.IntentionTargetService)
|
index, got, err = s.IntentionTopology(nil, target, false, false, structs.IntentionTargetService)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, uint64(4), index)
|
require.Equal(t, uint64(4), index)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,9 @@ import (
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/serf/serf"
|
"github.com/hashicorp/serf/serf"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/metadata"
|
"github.com/hashicorp/consul/agent/metadata"
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CanServersUnderstandProtocol checks to see if all the servers in the given
|
// CanServersUnderstandProtocol checks to see if all the servers in the given
|
||||||
|
@ -171,3 +173,14 @@ func isSerfMember(s *serf.Serf, nodeName string) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DefaultIntentionAllow(authz acl.Authorizer, defaultIntentionPolicy string) bool {
|
||||||
|
// The default intention policy inherits from ACLs but
|
||||||
|
// is overridden by the agent's DefaultIntentionPolicy.
|
||||||
|
//nolint:staticcheck
|
||||||
|
defaultAllow := authz.IntentionDefaultAllow(nil) == acl.Allow
|
||||||
|
if defaultIntentionPolicy != "" {
|
||||||
|
defaultAllow = defaultIntentionPolicy == structs.IntentionDefaultPolicyAllow
|
||||||
|
}
|
||||||
|
return defaultAllow
|
||||||
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ type Store interface {
|
||||||
FederationStateList(ws memdb.WatchSet) (uint64, []*structs.FederationState, error)
|
FederationStateList(ws memdb.WatchSet) (uint64, []*structs.FederationState, error)
|
||||||
GatewayServices(ws memdb.WatchSet, gateway string, entMeta *acl.EnterpriseMeta) (uint64, structs.GatewayServices, error)
|
GatewayServices(ws memdb.WatchSet, gateway string, entMeta *acl.EnterpriseMeta) (uint64, structs.GatewayServices, error)
|
||||||
IntentionMatchOne(ws memdb.WatchSet, entry structs.IntentionMatchEntry, matchType structs.IntentionMatchType, destinationType structs.IntentionTargetType) (uint64, structs.SimplifiedIntentions, error)
|
IntentionMatchOne(ws memdb.WatchSet, entry structs.IntentionMatchEntry, matchType structs.IntentionMatchType, destinationType structs.IntentionTargetType) (uint64, structs.SimplifiedIntentions, error)
|
||||||
IntentionTopology(ws memdb.WatchSet, target structs.ServiceName, downstreams bool, defaultDecision acl.EnforcementDecision, intentionTarget structs.IntentionTargetType) (uint64, structs.ServiceList, error)
|
IntentionTopology(ws memdb.WatchSet, target structs.ServiceName, downstreams bool, defaultDecision bool, intentionTarget structs.IntentionTargetType) (uint64, structs.ServiceList, error)
|
||||||
ReadResolvedServiceConfigEntries(ws memdb.WatchSet, serviceName string, entMeta *acl.EnterpriseMeta, upstreamIDs []structs.ServiceID, proxyMode structs.ProxyMode) (uint64, *configentry.ResolvedServiceConfigSet, error)
|
ReadResolvedServiceConfigEntries(ws memdb.WatchSet, serviceName string, entMeta *acl.EnterpriseMeta, upstreamIDs []structs.ServiceID, proxyMode structs.ProxyMode) (uint64, *configentry.ResolvedServiceConfigSet, error)
|
||||||
ServiceDiscoveryChain(ws memdb.WatchSet, serviceName string, entMeta *acl.EnterpriseMeta, req discoverychain.CompileRequest) (uint64, *structs.CompiledDiscoveryChain, *configentry.DiscoveryChainSet, error)
|
ServiceDiscoveryChain(ws memdb.WatchSet, serviceName string, entMeta *acl.EnterpriseMeta, req discoverychain.CompileRequest) (uint64, *structs.CompiledDiscoveryChain, *configentry.DiscoveryChainSet, error)
|
||||||
ServiceDump(ws memdb.WatchSet, kind structs.ServiceKind, useKind bool, entMeta *acl.EnterpriseMeta, peerName string) (uint64, structs.CheckServiceNodes, error)
|
ServiceDump(ws memdb.WatchSet, kind structs.ServiceKind, useKind bool, entMeta *acl.EnterpriseMeta, peerName string) (uint64, structs.CheckServiceNodes, error)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/cache"
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
cachetype "github.com/hashicorp/consul/agent/cache-types"
|
cachetype "github.com/hashicorp/consul/agent/cache-types"
|
||||||
|
"github.com/hashicorp/consul/agent/consul"
|
||||||
"github.com/hashicorp/consul/agent/consul/watch"
|
"github.com/hashicorp/consul/agent/consul/watch"
|
||||||
"github.com/hashicorp/consul/agent/proxycfg"
|
"github.com/hashicorp/consul/agent/proxycfg"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
@ -33,20 +34,21 @@ func CacheIntentionUpstreamsDestination(c *cache.Cache) proxycfg.IntentionUpstre
|
||||||
// ServerIntentionUpstreams satisfies the proxycfg.IntentionUpstreams interface
|
// ServerIntentionUpstreams satisfies the proxycfg.IntentionUpstreams interface
|
||||||
// by sourcing upstreams for the given service, inferred from intentions, from
|
// by sourcing upstreams for the given service, inferred from intentions, from
|
||||||
// the server's state store.
|
// the server's state store.
|
||||||
func ServerIntentionUpstreams(deps ServerDataSourceDeps) proxycfg.IntentionUpstreams {
|
func ServerIntentionUpstreams(deps ServerDataSourceDeps, defaultIntentionPolicy string) proxycfg.IntentionUpstreams {
|
||||||
return serverIntentionUpstreams{deps, structs.IntentionTargetService}
|
return serverIntentionUpstreams{deps, structs.IntentionTargetService, defaultIntentionPolicy}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerIntentionUpstreamsDestination satisfies the proxycfg.IntentionUpstreams
|
// ServerIntentionUpstreamsDestination satisfies the proxycfg.IntentionUpstreams
|
||||||
// interface by sourcing upstreams for the given destination, inferred from
|
// interface by sourcing upstreams for the given destination, inferred from
|
||||||
// intentions, from the server's state store.
|
// intentions, from the server's state store.
|
||||||
func ServerIntentionUpstreamsDestination(deps ServerDataSourceDeps) proxycfg.IntentionUpstreams {
|
func ServerIntentionUpstreamsDestination(deps ServerDataSourceDeps, defaultIntentionPolicy string) proxycfg.IntentionUpstreams {
|
||||||
return serverIntentionUpstreams{deps, structs.IntentionTargetDestination}
|
return serverIntentionUpstreams{deps, structs.IntentionTargetDestination, defaultIntentionPolicy}
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverIntentionUpstreams struct {
|
type serverIntentionUpstreams struct {
|
||||||
deps ServerDataSourceDeps
|
deps ServerDataSourceDeps
|
||||||
target structs.IntentionTargetType
|
target structs.IntentionTargetType
|
||||||
|
defaultIntentionPolicy string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s serverIntentionUpstreams) Notify(ctx context.Context, req *structs.ServiceSpecificRequest, correlationID string, ch chan<- proxycfg.UpdateEvent) error {
|
func (s serverIntentionUpstreams) Notify(ctx context.Context, req *structs.ServiceSpecificRequest, correlationID string, ch chan<- proxycfg.UpdateEvent) error {
|
||||||
|
@ -58,9 +60,10 @@ func (s serverIntentionUpstreams) Notify(ctx context.Context, req *structs.Servi
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
defaultDecision := authz.IntentionDefaultAllow(nil)
|
|
||||||
|
|
||||||
index, services, err := store.IntentionTopology(ws, target, false, defaultDecision, s.target)
|
defaultAllow := consul.DefaultIntentionAllow(authz, s.defaultIntentionPolicy)
|
||||||
|
|
||||||
|
index, services, err := store.IntentionTopology(ws, target, false, defaultAllow, s.target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ func TestServerIntentionUpstreams(t *testing.T) {
|
||||||
dataSource := ServerIntentionUpstreams(ServerDataSourceDeps{
|
dataSource := ServerIntentionUpstreams(ServerDataSourceDeps{
|
||||||
ACLResolver: newStaticResolver(authz),
|
ACLResolver: newStaticResolver(authz),
|
||||||
GetStore: func() Store { return store },
|
GetStore: func() Store { return store },
|
||||||
})
|
}, "")
|
||||||
|
|
||||||
ch := make(chan proxycfg.UpdateEvent)
|
ch := make(chan proxycfg.UpdateEvent)
|
||||||
err := dataSource.Notify(ctx, &structs.ServiceSpecificRequest{ServiceName: serviceName}, "", ch)
|
err := dataSource.Notify(ctx, &structs.ServiceSpecificRequest{ServiceName: serviceName}, "", ch)
|
||||||
|
@ -84,6 +84,47 @@ func TestServerIntentionUpstreams(t *testing.T) {
|
||||||
require.Equal(t, "db", result.Services[0].Name)
|
require.Equal(t, "db", result.Services[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Variant of TestServerIntentionUpstreams where a default allow intention policy
|
||||||
|
// returns "db" service as an IntentionUpstream even if there are no explicit
|
||||||
|
// intentions for "db".
|
||||||
|
func TestServerIntentionUpstreams_DefaultIntentionPolicy(t *testing.T) {
|
||||||
|
const serviceName = "web"
|
||||||
|
|
||||||
|
var index uint64
|
||||||
|
getIndex := func() uint64 {
|
||||||
|
index++
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
t.Cleanup(cancel)
|
||||||
|
|
||||||
|
store := state.NewStateStore(nil)
|
||||||
|
disableLegacyIntentions(t, store)
|
||||||
|
|
||||||
|
require.NoError(t, store.EnsureRegistration(getIndex(), &structs.RegisterRequest{
|
||||||
|
Node: "node-1",
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Service: "db",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Ensures that "db" service will not be filtered due to ACLs
|
||||||
|
authz := policyAuthorizer(t, `service "db" { policy = "read" }`)
|
||||||
|
|
||||||
|
dataSource := ServerIntentionUpstreams(ServerDataSourceDeps{
|
||||||
|
ACLResolver: newStaticResolver(authz),
|
||||||
|
GetStore: func() Store { return store },
|
||||||
|
}, "allow")
|
||||||
|
|
||||||
|
ch := make(chan proxycfg.UpdateEvent)
|
||||||
|
require.NoError(t, dataSource.Notify(ctx, &structs.ServiceSpecificRequest{ServiceName: serviceName}, "", ch))
|
||||||
|
|
||||||
|
result := getEventResult[*structs.IndexedServiceList](t, ch)
|
||||||
|
require.Len(t, result.Services, 1)
|
||||||
|
require.Equal(t, "db", result.Services[0].Name)
|
||||||
|
}
|
||||||
|
|
||||||
func disableLegacyIntentions(t *testing.T, store *state.Store) {
|
func disableLegacyIntentions(t *testing.T, store *state.Store) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,9 @@ const (
|
||||||
// fix up all the places where this was used with the proper namespace
|
// fix up all the places where this was used with the proper namespace
|
||||||
// value.
|
// value.
|
||||||
IntentionDefaultNamespace = "default"
|
IntentionDefaultNamespace = "default"
|
||||||
|
|
||||||
|
IntentionDefaultPolicyAllow = "allow"
|
||||||
|
IntentionDefaultPolicyDeny = "deny"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Intention defines an intention for the Connect Service Graph. This defines
|
// Intention defines an intention for the Connect Service Graph. This defines
|
||||||
|
@ -727,7 +730,7 @@ type IntentionQueryCheckResponse struct {
|
||||||
// - Whether the matching intention has L7 permissions attached
|
// - Whether the matching intention has L7 permissions attached
|
||||||
// - Whether the intention is managed by an external source like k8s
|
// - Whether the intention is managed by an external source like k8s
|
||||||
// - Whether there is an exact, or wildcard, intention referencing the two services
|
// - Whether there is an exact, or wildcard, intention referencing the two services
|
||||||
// - Whether ACLs are in DefaultAllow mode
|
// - Whether intentions are in DefaultAllow mode
|
||||||
type IntentionDecisionSummary struct {
|
type IntentionDecisionSummary struct {
|
||||||
Allowed bool
|
Allowed bool
|
||||||
HasPermissions bool
|
HasPermissions bool
|
||||||
|
|
Loading…
Reference in New Issue