server: intentions CRUD requires connect to be enabled (#9194)
Fixes #9123
This commit is contained in:
parent
53a7f852fe
commit
db1184c094
|
@ -444,6 +444,11 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
|
||||||
func (c *ConfigEntry) preflightCheck(kind string) error {
|
func (c *ConfigEntry) preflightCheck(kind string) error {
|
||||||
switch kind {
|
switch kind {
|
||||||
case structs.ServiceIntentions:
|
case structs.ServiceIntentions:
|
||||||
|
// Exit early if Connect hasn't been enabled.
|
||||||
|
if !c.srv.config.ConnectEnabled {
|
||||||
|
return ErrConnectNotEnabled
|
||||||
|
}
|
||||||
|
|
||||||
usingConfigEntries, err := c.srv.fsm.State().AreIntentionsInConfigEntries()
|
usingConfigEntries, err := c.srv.fsm.State().AreIntentionsInConfigEntries()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("system metadata lookup failed: %v", err)
|
return fmt.Errorf("system metadata lookup failed: %v", err)
|
||||||
|
|
|
@ -59,6 +59,11 @@ func (s *Intention) legacyUpgradeCheck() error {
|
||||||
|
|
||||||
// Apply creates or updates an intention in the data store.
|
// Apply creates or updates an intention in the data store.
|
||||||
func (s *Intention) Apply(args *structs.IntentionRequest, reply *string) error {
|
func (s *Intention) Apply(args *structs.IntentionRequest, reply *string) error {
|
||||||
|
// Exit early if Connect hasn't been enabled.
|
||||||
|
if !s.srv.config.ConnectEnabled {
|
||||||
|
return ErrConnectNotEnabled
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that all service-intentions config entry writes go to the primary
|
// Ensure that all service-intentions config entry writes go to the primary
|
||||||
// datacenter. These will then be replicated to all the other datacenters.
|
// datacenter. These will then be replicated to all the other datacenters.
|
||||||
args.Datacenter = s.srv.config.PrimaryDatacenter
|
args.Datacenter = s.srv.config.PrimaryDatacenter
|
||||||
|
@ -402,9 +407,12 @@ func (s *Intention) computeApplyChangesDelete(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a single intention by ID.
|
// Get returns a single intention by ID.
|
||||||
func (s *Intention) Get(
|
func (s *Intention) Get(args *structs.IntentionQueryRequest, reply *structs.IndexedIntentions) error {
|
||||||
args *structs.IntentionQueryRequest,
|
// Exit early if Connect hasn't been enabled.
|
||||||
reply *structs.IndexedIntentions) error {
|
if !s.srv.config.ConnectEnabled {
|
||||||
|
return ErrConnectNotEnabled
|
||||||
|
}
|
||||||
|
|
||||||
// Forward if necessary
|
// Forward if necessary
|
||||||
if done, err := s.srv.ForwardRPC("Intention.Get", args, args, reply); done {
|
if done, err := s.srv.ForwardRPC("Intention.Get", args, args, reply); done {
|
||||||
return err
|
return err
|
||||||
|
@ -477,9 +485,12 @@ func (s *Intention) Get(
|
||||||
}
|
}
|
||||||
|
|
||||||
// List returns all the intentions.
|
// List returns all the intentions.
|
||||||
func (s *Intention) List(
|
func (s *Intention) List(args *structs.IntentionListRequest, reply *structs.IndexedIntentions) error {
|
||||||
args *structs.IntentionListRequest,
|
// Exit early if Connect hasn't been enabled.
|
||||||
reply *structs.IndexedIntentions) error {
|
if !s.srv.config.ConnectEnabled {
|
||||||
|
return ErrConnectNotEnabled
|
||||||
|
}
|
||||||
|
|
||||||
// Forward if necessary
|
// Forward if necessary
|
||||||
if done, err := s.srv.ForwardRPC("Intention.List", args, args, reply); done {
|
if done, err := s.srv.ForwardRPC("Intention.List", args, args, reply); done {
|
||||||
return err
|
return err
|
||||||
|
@ -544,9 +555,12 @@ func (s *Intention) List(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns the set of intentions that match the given source/destination.
|
// Match returns the set of intentions that match the given source/destination.
|
||||||
func (s *Intention) Match(
|
func (s *Intention) Match(args *structs.IntentionQueryRequest, reply *structs.IndexedIntentionMatches) error {
|
||||||
args *structs.IntentionQueryRequest,
|
// Exit early if Connect hasn't been enabled.
|
||||||
reply *structs.IndexedIntentionMatches) error {
|
if !s.srv.config.ConnectEnabled {
|
||||||
|
return ErrConnectNotEnabled
|
||||||
|
}
|
||||||
|
|
||||||
// Forward if necessary
|
// Forward if necessary
|
||||||
if done, err := s.srv.ForwardRPC("Intention.Match", args, args, reply); done {
|
if done, err := s.srv.ForwardRPC("Intention.Match", args, args, reply); done {
|
||||||
return err
|
return err
|
||||||
|
@ -615,9 +629,12 @@ func (s *Intention) Match(
|
||||||
// Note: Whenever the logic for this method is changed, you should take
|
// Note: Whenever the logic for this method is changed, you should take
|
||||||
// a look at the agent authorize endpoint (agent/agent_endpoint.go) since
|
// a look at the agent authorize endpoint (agent/agent_endpoint.go) since
|
||||||
// the logic there is similar.
|
// the logic there is similar.
|
||||||
func (s *Intention) Check(
|
func (s *Intention) Check(args *structs.IntentionQueryRequest, reply *structs.IntentionQueryCheckResponse) error {
|
||||||
args *structs.IntentionQueryRequest,
|
// Exit early if Connect hasn't been enabled.
|
||||||
reply *structs.IntentionQueryCheckResponse) error {
|
if !s.srv.config.ConnectEnabled {
|
||||||
|
return ErrConnectNotEnabled
|
||||||
|
}
|
||||||
|
|
||||||
// Forward maybe
|
// Forward maybe
|
||||||
if done, err := s.srv.ForwardRPC("Intention.Check", args, args, reply); done {
|
if done, err := s.srv.ForwardRPC("Intention.Check", args, args, reply); done {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -1017,18 +1017,32 @@ func (s *Server) bootstrapConfigEntries(entries []structs.ConfigEntry) error {
|
||||||
|
|
||||||
state := s.fsm.State()
|
state := s.fsm.State()
|
||||||
|
|
||||||
// Do a quick preflight check to see if someone is trying to upgrade from
|
// Do some quick preflight checks to see if someone is doing something
|
||||||
// an older pre-1.9.0 version of consul with intentions AND are trying to
|
// that's not allowed at this time:
|
||||||
// bootstrap a service-intentions config entry at the same time.
|
//
|
||||||
|
// - Trying to upgrade from an older pre-1.9.0 version of consul with
|
||||||
|
// intentions AND are trying to bootstrap a service-intentions config entry
|
||||||
|
// at the same time.
|
||||||
|
//
|
||||||
|
// - Trying to insert service-intentions config entries when connect is
|
||||||
|
// disabled.
|
||||||
|
|
||||||
usingConfigEntries, err := s.fsm.State().AreIntentionsInConfigEntries()
|
usingConfigEntries, err := s.fsm.State().AreIntentionsInConfigEntries()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to determine if we are migrating intentions yet: %v", err)
|
return fmt.Errorf("Failed to determine if we are migrating intentions yet: %v", err)
|
||||||
}
|
}
|
||||||
if !usingConfigEntries {
|
|
||||||
|
if !usingConfigEntries || !s.config.ConnectEnabled {
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.GetKind() == structs.ServiceIntentions {
|
if entry.GetKind() == structs.ServiceIntentions {
|
||||||
return fmt.Errorf("Refusing to apply configuration entry %q / %q because intentions are still being migrated to config entries: %v",
|
if !s.config.ConnectEnabled {
|
||||||
entry.GetKind(), entry.GetName(), err)
|
return fmt.Errorf("Refusing to apply configuration entry %q / %q because Connect must be enabled to bootstrap intentions",
|
||||||
|
entry.GetKind(), entry.GetName())
|
||||||
|
}
|
||||||
|
if !usingConfigEntries {
|
||||||
|
return fmt.Errorf("Refusing to apply configuration entry %q / %q because intentions are still being migrated to config entries",
|
||||||
|
entry.GetKind(), entry.GetName())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1230,63 +1230,133 @@ func TestLeader_ConfigEntryBootstrap(t *testing.T) {
|
||||||
func TestLeader_ConfigEntryBootstrap_Fail(t *testing.T) {
|
func TestLeader_ConfigEntryBootstrap_Fail(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
pr, pw := io.Pipe()
|
type testcase struct {
|
||||||
defer pw.Close()
|
name string
|
||||||
|
entries []structs.ConfigEntry
|
||||||
|
serverCB func(c *Config)
|
||||||
|
expectMessage string
|
||||||
|
}
|
||||||
|
|
||||||
ch := make(chan string, 1)
|
cases := []testcase{
|
||||||
go func() {
|
{
|
||||||
defer pr.Close()
|
name: "service-splitter without L7 protocol",
|
||||||
scan := bufio.NewScanner(pr)
|
entries: []structs.ConfigEntry{
|
||||||
for scan.Scan() {
|
&structs.ServiceSplitterConfigEntry{
|
||||||
line := scan.Text()
|
Kind: structs.ServiceSplitter,
|
||||||
|
Name: "web",
|
||||||
if strings.Contains(line, "failed to establish leadership") {
|
Splits: []structs.ServiceSplit{
|
||||||
ch <- ""
|
{Weight: 100, Service: "web"},
|
||||||
return
|
},
|
||||||
}
|
},
|
||||||
if strings.Contains(line, "successfully established leadership") {
|
|
||||||
ch <- "leadership should not have gotten here if config entries properly failed"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if scan.Err() != nil {
|
|
||||||
ch <- fmt.Sprintf("ERROR: %v", scan.Err())
|
|
||||||
} else {
|
|
||||||
ch <- "should not get here"
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, config := testServerConfig(t)
|
|
||||||
config.Build = "1.6.0"
|
|
||||||
config.ConfigEntryBootstrap = []structs.ConfigEntry{
|
|
||||||
&structs.ServiceSplitterConfigEntry{
|
|
||||||
Kind: structs.ServiceSplitter,
|
|
||||||
Name: "web",
|
|
||||||
Splits: []structs.ServiceSplit{
|
|
||||||
{Weight: 100, Service: "web"},
|
|
||||||
},
|
},
|
||||||
|
expectMessage: `Failed to apply configuration entry "service-splitter" / "web": discovery chain "web" uses a protocol "tcp" that does not permit advanced routing or splitting behavior"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service-intentions without migration",
|
||||||
|
entries: []structs.ConfigEntry{
|
||||||
|
&structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: "web",
|
||||||
|
Sources: []*structs.SourceIntention{
|
||||||
|
{
|
||||||
|
Name: "debug",
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
serverCB: func(c *Config) {
|
||||||
|
c.OverrideInitialSerfTags = func(tags map[string]string) {
|
||||||
|
tags["ft_si"] = "0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectMessage: `Refusing to apply configuration entry "service-intentions" / "web" because intentions are still being migrated to config entries`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service-intentions without Connect",
|
||||||
|
entries: []structs.ConfigEntry{
|
||||||
|
&structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: "web",
|
||||||
|
Sources: []*structs.SourceIntention{
|
||||||
|
{
|
||||||
|
Name: "debug",
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
serverCB: func(c *Config) {
|
||||||
|
c.ConnectEnabled = false
|
||||||
|
},
|
||||||
|
expectMessage: `Refusing to apply configuration entry "service-intentions" / "web" because Connect must be enabled to bootstrap intentions"`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
|
for _, tc := range cases {
|
||||||
Name: config.NodeName,
|
tc := tc
|
||||||
Level: hclog.Debug,
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
Output: io.MultiWriter(pw, testutil.NewLogBuffer(t)),
|
pr, pw := io.Pipe()
|
||||||
})
|
defer pw.Close()
|
||||||
|
|
||||||
deps := newDefaultDeps(t, config)
|
var (
|
||||||
deps.Logger = logger
|
ch = make(chan string, 1)
|
||||||
|
applyErrorLine string
|
||||||
|
)
|
||||||
|
go func() {
|
||||||
|
defer pr.Close()
|
||||||
|
scan := bufio.NewScanner(pr)
|
||||||
|
for scan.Scan() {
|
||||||
|
line := scan.Text()
|
||||||
|
|
||||||
srv, err := NewServer(config, deps)
|
if strings.Contains(line, "failed to establish leadership") {
|
||||||
require.NoError(t, err)
|
applyErrorLine = line
|
||||||
defer srv.Shutdown()
|
ch <- ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.Contains(line, "successfully established leadership") {
|
||||||
|
ch <- "leadership should not have gotten here if config entries properly failed"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
if scan.Err() != nil {
|
||||||
case result := <-ch:
|
ch <- fmt.Sprintf("ERROR: %v", scan.Err())
|
||||||
require.Empty(t, result)
|
} else {
|
||||||
case <-time.After(time.Second):
|
ch <- "should not get here"
|
||||||
t.Fatal("timeout waiting for a result from tailing logs")
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, config := testServerConfig(t)
|
||||||
|
config.Build = "1.6.0"
|
||||||
|
config.ConfigEntryBootstrap = tc.entries
|
||||||
|
if tc.serverCB != nil {
|
||||||
|
tc.serverCB(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
|
||||||
|
Name: config.NodeName,
|
||||||
|
Level: hclog.Debug,
|
||||||
|
Output: io.MultiWriter(pw, testutil.NewLogBuffer(t)),
|
||||||
|
})
|
||||||
|
|
||||||
|
deps := newDefaultDeps(t, config)
|
||||||
|
deps.Logger = logger
|
||||||
|
|
||||||
|
srv, err := NewServer(config, deps)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case result := <-ch:
|
||||||
|
require.Empty(t, result)
|
||||||
|
if tc.expectMessage != "" {
|
||||||
|
require.Contains(t, applyErrorLine, tc.expectMessage)
|
||||||
|
}
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("timeout waiting for a result from tailing logs")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue