diff --git a/agent/agent.go b/agent/agent.go index 57f8cc78dc..97e14d73b6 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -69,6 +69,13 @@ const ( "service, but no reason was provided. This is a default message." ) +type configSource int + +const ( + ConfigSourceLocal configSource = iota + ConfigSourceRemote +) + // delegate defines the interface shared by both // consul.Client and consul.Server. type delegate interface { @@ -1858,7 +1865,7 @@ func (a *Agent) purgeCheck(checkID types.CheckID) error { // AddService is used to add a service entry. // This entry is persistent and the agent will make a best effort to // ensure it is registered -func (a *Agent) AddService(service *structs.NodeService, chkTypes []*structs.CheckType, persist bool, token string) error { +func (a *Agent) AddService(service *structs.NodeService, chkTypes []*structs.CheckType, persist bool, token string, source configSource) error { if service.Service == "" { return fmt.Errorf("Service name missing") } @@ -1940,7 +1947,7 @@ func (a *Agent) AddService(service *structs.NodeService, chkTypes []*structs.Che if chkType.Status != "" { check.Status = chkType.Status } - if err := a.AddCheck(check, chkType, persist, token); err != nil { + if err := a.AddCheck(check, chkType, persist, token, source); err != nil { return err } } @@ -2010,7 +2017,7 @@ func (a *Agent) RemoveService(serviceID string, persist bool) error { // This entry is persistent and the agent will make a best effort to // ensure it is registered. The Check may include a CheckType which // is used to automatically update the check status -func (a *Agent) AddCheck(check *structs.HealthCheck, chkType *structs.CheckType, persist bool, token string) error { +func (a *Agent) AddCheck(check *structs.HealthCheck, chkType *structs.CheckType, persist bool, token string, source configSource) error { if check.CheckID == "" { return fmt.Errorf("CheckID missing") } @@ -2020,8 +2027,14 @@ func (a *Agent) AddCheck(check *structs.HealthCheck, chkType *structs.CheckType, return fmt.Errorf("Check is not valid: %v", err) } - if chkType.IsScript() && !a.config.EnableScriptChecks { - return fmt.Errorf("Scripts are disabled on this agent; to enable, configure 'enable_script_checks' to true") + if chkType.IsScript() { + if source == ConfigSourceLocal && !a.config.EnableLocalScriptChecks { + return fmt.Errorf("Scripts are disabled on this agent; to enable, configure 'enable_script_checks' or 'enable_local_script_checks' to true") + } + + if source == ConfigSourceRemote && !a.config.EnableRemoteScriptChecks { + return fmt.Errorf("Scripts are disabled on this agent from remote calls; to enable, configure 'enable_script_checks' to true") + } } } @@ -2330,7 +2343,7 @@ func (a *Agent) RemoveCheck(checkID types.CheckID, persist bool) error { // assigned. We need to restore from disk to enable to continue authenticating // running proxies that already had that credential injected. func (a *Agent) addProxyLocked(proxy *structs.ConnectManagedProxy, persist, FromFile bool, - restoredProxyToken string) error { + restoredProxyToken string, source configSource) error { // Lookup the target service token in state if there is one. token := a.State.ServiceToken(proxy.TargetServiceID) @@ -2372,7 +2385,7 @@ func (a *Agent) addProxyLocked(proxy *structs.ConnectManagedProxy, persist, From } } - err = a.AddService(proxyService, chkTypes, persist, token) + err = a.AddService(proxyService, chkTypes, persist, token, source) if err != nil { // Remove the state too a.State.RemoveProxy(proxyService.ID) @@ -2402,10 +2415,10 @@ func (a *Agent) addProxyLocked(proxy *structs.ConnectManagedProxy, persist, From // assigned. We need to restore from disk to enable to continue authenticating // running proxies that already had that credential injected. func (a *Agent) AddProxy(proxy *structs.ConnectManagedProxy, persist, FromFile bool, - restoredProxyToken string) error { + restoredProxyToken string, source configSource) error { a.proxyLock.Lock() defer a.proxyLock.Unlock() - return a.addProxyLocked(proxy, persist, FromFile, restoredProxyToken) + return a.addProxyLocked(proxy, persist, FromFile, restoredProxyToken, source) } // resolveProxyCheckAddress returns the best address to use for a TCP check of @@ -2893,13 +2906,13 @@ func (a *Agent) loadServices(conf *config.RuntimeConfig) error { // syntax sugar and shouldn't be persisted in local or server state. ns.Connect.SidecarService = nil - if err := a.AddService(ns, chkTypes, false, service.Token); err != nil { + if err := a.AddService(ns, chkTypes, false, service.Token, ConfigSourceLocal); err != nil { return fmt.Errorf("Failed to register service %q: %v", service.Name, err) } // If there is a sidecar service, register that too. if sidecar != nil { - if err := a.AddService(sidecar, sidecarChecks, false, sidecarToken); err != nil { + if err := a.AddService(sidecar, sidecarChecks, false, sidecarToken, ConfigSourceLocal); err != nil { return fmt.Errorf("Failed to register sidecar for service %q: %v", service.Name, err) } } @@ -2962,7 +2975,7 @@ func (a *Agent) loadServices(conf *config.RuntimeConfig) error { } else { a.logger.Printf("[DEBUG] agent: restored service definition %q from %q", serviceID, file) - if err := a.AddService(p.Service, nil, false, p.Token); err != nil { + if err := a.AddService(p.Service, nil, false, p.Token, ConfigSourceLocal); err != nil { return fmt.Errorf("failed adding service %q: %s", serviceID, err) } } @@ -2988,7 +3001,7 @@ func (a *Agent) loadChecks(conf *config.RuntimeConfig) error { for _, check := range conf.Checks { health := check.HealthCheck(conf.NodeName) chkType := check.CheckType() - if err := a.AddCheck(health, chkType, false, check.Token); err != nil { + if err := a.AddCheck(health, chkType, false, check.Token, ConfigSourceLocal); err != nil { return fmt.Errorf("Failed to register check '%s': %v %v", check.Name, err, check) } } @@ -3043,7 +3056,7 @@ func (a *Agent) loadChecks(conf *config.RuntimeConfig) error { // services into the active pool p.Check.Status = api.HealthCritical - if err := a.AddCheck(p.Check, p.ChkType, false, p.Token); err != nil { + if err := a.AddCheck(p.Check, p.ChkType, false, p.Token, ConfigSourceLocal); err != nil { // Purge the check if it is unable to be restored. a.logger.Printf("[WARN] agent: Failed to restore check %q: %s", checkID, err) @@ -3144,7 +3157,7 @@ func (a *Agent) loadProxies(conf *config.RuntimeConfig) error { restoredToken = persisted.ProxyToken } - if err := a.addProxyLocked(proxy, true, true, restoredToken); err != nil { + if err := a.addProxyLocked(proxy, true, true, restoredToken, ConfigSourceLocal); err != nil { return fmt.Errorf("failed adding proxy: %s", err) } } @@ -3161,7 +3174,7 @@ func (a *Agent) loadProxies(conf *config.RuntimeConfig) error { } else if !persisted.FromFile { if a.State.Proxy(proxyID) == nil { a.logger.Printf("[DEBUG] agent: restored proxy definition %q", proxyID) - if err := a.addProxyLocked(persisted.Proxy, false, false, persisted.ProxyToken); err != nil { + if err := a.addProxyLocked(persisted.Proxy, false, false, persisted.ProxyToken, ConfigSourceLocal); err != nil { return fmt.Errorf("failed adding proxy %q: %v", proxyID, err) } } else { @@ -3251,7 +3264,7 @@ func (a *Agent) EnableServiceMaintenance(serviceID, reason, token string) error ServiceName: service.Service, Status: api.HealthCritical, } - a.AddCheck(check, nil, true, token) + a.AddCheck(check, nil, true, token, ConfigSourceLocal) a.logger.Printf("[INFO] agent: Service %q entered maintenance mode", serviceID) return nil @@ -3297,7 +3310,7 @@ func (a *Agent) EnableNodeMaintenance(reason, token string) { Notes: reason, Status: api.HealthCritical, } - a.AddCheck(check, nil, true, token) + a.AddCheck(check, nil, true, token, ConfigSourceLocal) a.logger.Printf("[INFO] agent: Node entered maintenance mode") } diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go index 67e5b8106a..853b4dcdde 100644 --- a/agent/agent_endpoint.go +++ b/agent/agent_endpoint.go @@ -570,7 +570,7 @@ func (s *HTTPServer) AgentRegisterCheck(resp http.ResponseWriter, req *http.Requ } // Add the check. - if err := s.agent.AddCheck(health, chkType, true, token); err != nil { + if err := s.agent.AddCheck(health, chkType, true, token, ConfigSourceRemote); err != nil { return nil, err } s.syncChanges() @@ -889,18 +889,18 @@ func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Re } // Add the service. - if err := s.agent.AddService(ns, chkTypes, true, token); err != nil { + if err := s.agent.AddService(ns, chkTypes, true, token, ConfigSourceRemote); err != nil { return nil, err } // Add proxy (which will add proxy service so do it before we trigger sync) if proxy != nil { - if err := s.agent.AddProxy(proxy, true, false, ""); err != nil { + if err := s.agent.AddProxy(proxy, true, false, "", ConfigSourceRemote); err != nil { return nil, err } } // Add sidecar. if sidecar != nil { - if err := s.agent.AddService(sidecar, sidecarChecks, true, sidecarToken); err != nil { + if err := s.agent.AddService(sidecar, sidecarChecks, true, sidecarToken, ConfigSourceRemote); err != nil { return nil, err } } diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index dea70d4795..3a13135f57 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -1329,6 +1329,60 @@ func TestAgent_RegisterCheck_Scripts(t *testing.T) { } } +func TestAgent_RegisterCheckScriptsExecDisable(t *testing.T) { + t.Parallel() + a := NewTestAgent(t.Name(), "") + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + args := &structs.CheckDefinition{ + Name: "test", + ScriptArgs: []string{"true"}, + Interval: time.Second, + } + req, _ := http.NewRequest("PUT", "/v1/agent/check/register?token=abc123", jsonReader(args)) + res := httptest.NewRecorder() + _, err := a.srv.AgentRegisterCheck(res, req) + if err == nil { + t.Fatalf("expected error but got nil") + } + if !strings.Contains(err.Error(), "Scripts are disabled on this agent") { + t.Fatalf("expected script disabled error, got: %s", err) + } + checkID := types.CheckID("test") + if _, ok := a.State.Checks()[checkID]; ok { + t.Fatalf("check registered with exec disable") + } +} + +func TestAgent_RegisterCheckScriptsExecRemoteDisable(t *testing.T) { + t.Parallel() + a := NewTestAgent(t.Name(), ` + enable_local_script_checks = true + `) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + args := &structs.CheckDefinition{ + Name: "test", + ScriptArgs: []string{"true"}, + Interval: time.Second, + } + req, _ := http.NewRequest("PUT", "/v1/agent/check/register?token=abc123", jsonReader(args)) + res := httptest.NewRecorder() + _, err := a.srv.AgentRegisterCheck(res, req) + if err == nil { + t.Fatalf("expected error but got nil") + } + if !strings.Contains(err.Error(), "Scripts are disabled on this agent") { + t.Fatalf("expected script disabled error, got: %s", err) + } + checkID := types.CheckID("test") + if _, ok := a.State.Checks()[checkID]; ok { + t.Fatalf("check registered with exec disable") + } +} + func TestAgent_RegisterCheck_Passing(t *testing.T) { t.Parallel() a := NewTestAgent(t.Name(), "") @@ -1419,7 +1473,7 @@ func TestAgent_DeregisterCheck(t *testing.T) { testrpc.WaitForTestAgent(t, a.RPC, "dc1") chk := &structs.HealthCheck{Name: "test", CheckID: "test"} - if err := a.AddCheck(chk, nil, false, ""); err != nil { + if err := a.AddCheck(chk, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1445,7 +1499,7 @@ func TestAgent_DeregisterCheckACLDeny(t *testing.T) { testrpc.WaitForLeader(t, a.RPC, "dc1") chk := &structs.HealthCheck{Name: "test", CheckID: "test"} - if err := a.AddCheck(chk, nil, false, ""); err != nil { + if err := a.AddCheck(chk, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1472,7 +1526,7 @@ func TestAgent_PassCheck(t *testing.T) { chk := &structs.HealthCheck{Name: "test", CheckID: "test"} chkType := &structs.CheckType{TTL: 15 * time.Second} - if err := a.AddCheck(chk, chkType, false, ""); err != nil { + if err := a.AddCheck(chk, chkType, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1500,7 +1554,7 @@ func TestAgent_PassCheck_ACLDeny(t *testing.T) { chk := &structs.HealthCheck{Name: "test", CheckID: "test"} chkType := &structs.CheckType{TTL: 15 * time.Second} - if err := a.AddCheck(chk, chkType, false, ""); err != nil { + if err := a.AddCheck(chk, chkType, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1527,7 +1581,7 @@ func TestAgent_WarnCheck(t *testing.T) { chk := &structs.HealthCheck{Name: "test", CheckID: "test"} chkType := &structs.CheckType{TTL: 15 * time.Second} - if err := a.AddCheck(chk, chkType, false, ""); err != nil { + if err := a.AddCheck(chk, chkType, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1555,7 +1609,7 @@ func TestAgent_WarnCheck_ACLDeny(t *testing.T) { chk := &structs.HealthCheck{Name: "test", CheckID: "test"} chkType := &structs.CheckType{TTL: 15 * time.Second} - if err := a.AddCheck(chk, chkType, false, ""); err != nil { + if err := a.AddCheck(chk, chkType, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1582,7 +1636,7 @@ func TestAgent_FailCheck(t *testing.T) { chk := &structs.HealthCheck{Name: "test", CheckID: "test"} chkType := &structs.CheckType{TTL: 15 * time.Second} - if err := a.AddCheck(chk, chkType, false, ""); err != nil { + if err := a.AddCheck(chk, chkType, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1610,7 +1664,7 @@ func TestAgent_FailCheck_ACLDeny(t *testing.T) { chk := &structs.HealthCheck{Name: "test", CheckID: "test"} chkType := &structs.CheckType{TTL: 15 * time.Second} - if err := a.AddCheck(chk, chkType, false, ""); err != nil { + if err := a.AddCheck(chk, chkType, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1637,7 +1691,7 @@ func TestAgent_UpdateCheck(t *testing.T) { chk := &structs.HealthCheck{Name: "test", CheckID: "test"} chkType := &structs.CheckType{TTL: 15 * time.Second} - if err := a.AddCheck(chk, chkType, false, ""); err != nil { + if err := a.AddCheck(chk, chkType, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1721,7 +1775,7 @@ func TestAgent_UpdateCheck_ACLDeny(t *testing.T) { chk := &structs.HealthCheck{Name: "test", CheckID: "test"} chkType := &structs.CheckType{TTL: 15 * time.Second} - if err := a.AddCheck(chk, chkType, false, ""); err != nil { + if err := a.AddCheck(chk, chkType, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -2751,10 +2805,10 @@ func TestAgent_RegisterServiceDeregisterService_Sidecar(t *testing.T) { testrpc.WaitForLeader(t, a.RPC, "dc1") if tt.preRegister != nil { - require.NoError(a.AddService(tt.preRegister, nil, false, "")) + require.NoError(a.AddService(tt.preRegister, nil, false, "", ConfigSourceLocal)) } if tt.preRegister2 != nil { - require.NoError(a.AddService(tt.preRegister2, nil, false, "")) + require.NoError(a.AddService(tt.preRegister2, nil, false, "", ConfigSourceLocal)) } // Create an ACL token with require policy @@ -2910,6 +2964,80 @@ func TestAgent_RegisterService_ConnectNative(t *testing.T) { assert.True(svc.Connect.Native) } +func TestAgent_RegisterService_ScriptCheck_ExecDisable(t *testing.T) { + t.Parallel() + a := NewTestAgent(t.Name(), "") + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + args := &structs.ServiceDefinition{ + Name: "test", + Meta: map[string]string{"hello": "world"}, + Tags: []string{"master"}, + Port: 8000, + Check: structs.CheckType{ + Name: "test-check", + Interval: time.Second, + ScriptArgs: []string{"true"}, + }, + Weights: &structs.Weights{ + Passing: 100, + Warning: 3, + }, + } + req, _ := http.NewRequest("PUT", "/v1/agent/service/register?token=abc123", jsonReader(args)) + + _, err := a.srv.AgentRegisterService(nil, req) + if err == nil { + t.Fatalf("expected error but got nil") + } + if !strings.Contains(err.Error(), "Scripts are disabled on this agent") { + t.Fatalf("expected script disabled error, got: %s", err) + } + checkID := types.CheckID("test-check") + if _, ok := a.State.Checks()[checkID]; ok { + t.Fatalf("check registered with exec disable") + } +} + +func TestAgent_RegisterService_ScriptCheck_ExecRemoteDisable(t *testing.T) { + t.Parallel() + a := NewTestAgent(t.Name(), ` + enable_local_script_checks = true + `) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + args := &structs.ServiceDefinition{ + Name: "test", + Meta: map[string]string{"hello": "world"}, + Tags: []string{"master"}, + Port: 8000, + Check: structs.CheckType{ + Name: "test-check", + Interval: time.Second, + ScriptArgs: []string{"true"}, + }, + Weights: &structs.Weights{ + Passing: 100, + Warning: 3, + }, + } + req, _ := http.NewRequest("PUT", "/v1/agent/service/register?token=abc123", jsonReader(args)) + + _, err := a.srv.AgentRegisterService(nil, req) + if err == nil { + t.Fatalf("expected error but got nil") + } + if !strings.Contains(err.Error(), "Scripts are disabled on this agent") { + t.Fatalf("expected script disabled error, got: %s", err) + } + checkID := types.CheckID("test-check") + if _, ok := a.State.Checks()[checkID]; ok { + t.Fatalf("check registered with exec disable") + } +} + func TestAgent_DeregisterService(t *testing.T) { t.Parallel() a := NewTestAgent(t.Name(), "") @@ -2920,7 +3048,7 @@ func TestAgent_DeregisterService(t *testing.T) { ID: "test", Service: "test", } - if err := a.AddService(service, nil, false, ""); err != nil { + if err := a.AddService(service, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -2953,7 +3081,7 @@ func TestAgent_DeregisterService_ACLDeny(t *testing.T) { ID: "test", Service: "test", } - if err := a.AddService(service, nil, false, ""); err != nil { + if err := a.AddService(service, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -3127,7 +3255,7 @@ func TestAgent_ServiceMaintenance_Enable(t *testing.T) { ID: "test", Service: "test", } - if err := a.AddService(service, nil, false, ""); err != nil { + if err := a.AddService(service, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -3170,7 +3298,7 @@ func TestAgent_ServiceMaintenance_Disable(t *testing.T) { ID: "test", Service: "test", } - if err := a.AddService(service, nil, false, ""); err != nil { + if err := a.AddService(service, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -3207,7 +3335,7 @@ func TestAgent_ServiceMaintenance_ACLDeny(t *testing.T) { ID: "test", Service: "test", } - if err := a.AddService(service, nil, false, ""); err != nil { + if err := a.AddService(service, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } diff --git a/agent/agent_test.go b/agent/agent_test.go index 6456fa846a..7551a59770 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -438,7 +438,7 @@ func TestAgent_AddService(t *testing.T) { t.Run(tt.desc, func(t *testing.T) { // check the service registration t.Run(tt.srv.ID, func(t *testing.T) { - err := a.AddService(tt.srv, tt.chkTypes, false, "") + err := a.AddService(tt.srv, tt.chkTypes, false, "", ConfigSourceLocal) if err != nil { t.Fatalf("err: %v", err) } @@ -474,6 +474,62 @@ func TestAgent_AddService(t *testing.T) { } } +func TestAgent_AddServiceNoExec(t *testing.T) { + t.Parallel() + a := NewTestAgent(t.Name(), ` + node_name = "node1" + `) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + srv := &structs.NodeService{ + ID: "svcid1", + Service: "svcname1", + Tags: []string{"tag1"}, + Port: 8100, + } + chk := &structs.CheckType{ + ScriptArgs: []string{"exit", "0"}, + Interval: 15 * time.Second, + } + + err := a.AddService(srv, []*structs.CheckType{chk}, false, "", ConfigSourceLocal) + if err == nil || !strings.Contains(err.Error(), "Scripts are disabled on this agent") { + t.Fatalf("err: %v", err) + } + + err = a.AddService(srv, []*structs.CheckType{chk}, false, "", ConfigSourceRemote) + if err == nil || !strings.Contains(err.Error(), "Scripts are disabled on this agent") { + t.Fatalf("err: %v", err) + } +} + +func TestAgent_AddServiceNoRemoteExec(t *testing.T) { + t.Parallel() + a := NewTestAgent(t.Name(), ` + node_name = "node1" + enable_local_script_checks = true + `) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + srv := &structs.NodeService{ + ID: "svcid1", + Service: "svcname1", + Tags: []string{"tag1"}, + Port: 8100, + } + chk := &structs.CheckType{ + ScriptArgs: []string{"exit", "0"}, + Interval: 15 * time.Second, + } + + err := a.AddService(srv, []*structs.CheckType{chk}, false, "", ConfigSourceRemote) + if err == nil || !strings.Contains(err.Error(), "Scripts are disabled on this agent") { + t.Fatalf("err: %v", err) + } +} + func TestAgent_RemoveService(t *testing.T) { t.Parallel() a := NewTestAgent(t.Name(), "") @@ -498,7 +554,7 @@ func TestAgent_RemoveService(t *testing.T) { } chkTypes := []*structs.CheckType{&structs.CheckType{TTL: time.Minute}} - if err := a.AddService(srv, chkTypes, false, ""); err != nil { + if err := a.AddService(srv, chkTypes, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -510,7 +566,7 @@ func TestAgent_RemoveService(t *testing.T) { TTL: time.Minute, } hc := check.HealthCheck("node1") - if err := a.AddCheck(hc, check.CheckType(), false, ""); err != nil { + if err := a.AddCheck(hc, check.CheckType(), false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %s", err) } @@ -536,7 +592,7 @@ func TestAgent_RemoveService(t *testing.T) { &structs.CheckType{TTL: time.Minute}, &structs.CheckType{TTL: 30 * time.Second}, } - if err := a.AddService(srv, chkTypes, false, ""); err != nil { + if err := a.AddService(srv, chkTypes, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -582,7 +638,7 @@ func TestAgent_RemoveServiceRemovesAllChecks(t *testing.T) { hchk2 := &structs.HealthCheck{Node: "node1", CheckID: "chk2", Name: "chk2", Status: "critical", ServiceID: "redis", ServiceName: "redis"} // register service with chk1 - if err := a.AddService(svc, []*structs.CheckType{chk1}, false, ""); err != nil { + if err := a.AddService(svc, []*structs.CheckType{chk1}, false, "", ConfigSourceLocal); err != nil { t.Fatal("Failed to register service", err) } @@ -592,7 +648,7 @@ func TestAgent_RemoveServiceRemovesAllChecks(t *testing.T) { } // update the service with chk2 - if err := a.AddService(svc, []*structs.CheckType{chk2}, false, ""); err != nil { + if err := a.AddService(svc, []*structs.CheckType{chk2}, false, "", ConfigSourceLocal); err != nil { t.Fatal("Failed to update service", err) } @@ -655,7 +711,7 @@ func verifyIndexChurn(t *testing.T, tags []string) { Tags: tags, Weights: weights, } - if err := a.AddService(svc, nil, true, ""); err != nil { + if err := a.AddService(svc, nil, true, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -668,7 +724,7 @@ func verifyIndexChurn(t *testing.T, tags []string) { chkt := &structs.CheckType{ TTL: time.Hour, } - if err := a.AddCheck(chk, chkt, true, ""); err != nil { + if err := a.AddCheck(chk, chkt, true, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -680,7 +736,7 @@ func verifyIndexChurn(t *testing.T, tags []string) { chkt = &structs.CheckType{ TTL: time.Hour, } - if err := a.AddCheck(chk, chkt, true, ""); err != nil { + if err := a.AddCheck(chk, chkt, true, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -745,7 +801,7 @@ func TestAgent_AddCheck(t *testing.T) { ScriptArgs: []string{"exit", "0"}, Interval: 15 * time.Second, } - err := a.AddCheck(health, chk, false, "") + err := a.AddCheck(health, chk, false, "", ConfigSourceLocal) if err != nil { t.Fatalf("err: %v", err) } @@ -784,7 +840,7 @@ func TestAgent_AddCheck_StartPassing(t *testing.T) { ScriptArgs: []string{"exit", "0"}, Interval: 15 * time.Second, } - err := a.AddCheck(health, chk, false, "") + err := a.AddCheck(health, chk, false, "", ConfigSourceLocal) if err != nil { t.Fatalf("err: %v", err) } @@ -823,7 +879,7 @@ func TestAgent_AddCheck_MinInterval(t *testing.T) { ScriptArgs: []string{"exit", "0"}, Interval: time.Microsecond, } - err := a.AddCheck(health, chk, false, "") + err := a.AddCheck(health, chk, false, "", ConfigSourceLocal) if err != nil { t.Fatalf("err: %v", err) } @@ -858,7 +914,7 @@ func TestAgent_AddCheck_MissingService(t *testing.T) { ScriptArgs: []string{"exit", "0"}, Interval: time.Microsecond, } - err := a.AddCheck(health, chk, false, "") + err := a.AddCheck(health, chk, false, "", ConfigSourceLocal) if err == nil || err.Error() != `ServiceID "baz" does not exist` { t.Fatalf("expected service id error, got: %v", err) } @@ -888,7 +944,7 @@ func TestAgent_AddCheck_RestoreState(t *testing.T) { chk := &structs.CheckType{ TTL: time.Minute, } - err = a.AddCheck(health, chk, false, "") + err = a.AddCheck(health, chk, false, "", ConfigSourceLocal) if err != nil { t.Fatalf("err: %s", err) } @@ -923,7 +979,7 @@ func TestAgent_AddCheck_ExecDisable(t *testing.T) { ScriptArgs: []string{"exit", "0"}, Interval: 15 * time.Second, } - err := a.AddCheck(health, chk, false, "") + err := a.AddCheck(health, chk, false, "", ConfigSourceLocal) if err == nil || !strings.Contains(err.Error(), "Scripts are disabled on this agent") { t.Fatalf("err: %v", err) } @@ -932,6 +988,46 @@ func TestAgent_AddCheck_ExecDisable(t *testing.T) { if memChk := a.State.Checks()["mem"]; memChk != nil { t.Fatalf("should be missing mem check") } + + err = a.AddCheck(health, chk, false, "", ConfigSourceRemote) + if err == nil || !strings.Contains(err.Error(), "Scripts are disabled on this agent") { + t.Fatalf("err: %v", err) + } + + // Ensure we don't have a check mapping + if memChk := a.State.Checks()["mem"]; memChk != nil { + t.Fatalf("should be missing mem check") + } +} + +func TestAgent_AddCheck_ExecRemoteDisable(t *testing.T) { + t.Parallel() + + a := NewTestAgent(t.Name(), ` + enable_local_script_checks = true + `) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + health := &structs.HealthCheck{ + Node: "foo", + CheckID: "mem", + Name: "memory util", + Status: api.HealthCritical, + } + chk := &structs.CheckType{ + ScriptArgs: []string{"exit", "0"}, + Interval: 15 * time.Second, + } + err := a.AddCheck(health, chk, false, "", ConfigSourceRemote) + if err == nil || !strings.Contains(err.Error(), "Scripts are disabled on this agent from remote calls") { + t.Fatalf("err: %v", err) + } + + // Ensure we don't have a check mapping + if memChk := a.State.Checks()["mem"]; memChk != nil { + t.Fatalf("should be missing mem check") + } } func TestAgent_AddCheck_GRPC(t *testing.T) { @@ -949,7 +1045,7 @@ func TestAgent_AddCheck_GRPC(t *testing.T) { GRPC: "localhost:12345/package.Service", Interval: 15 * time.Second, } - err := a.AddCheck(health, chk, false, "") + err := a.AddCheck(health, chk, false, "", ConfigSourceLocal) if err != nil { t.Fatalf("err: %v", err) } @@ -987,7 +1083,7 @@ func TestAgent_AddCheck_Alias(t *testing.T) { chk := &structs.CheckType{ AliasService: "foo", } - err := a.AddCheck(health, chk, false, "") + err := a.AddCheck(health, chk, false, "", ConfigSourceLocal) require.NoError(err) // Ensure we have a check mapping @@ -1021,7 +1117,7 @@ func TestAgent_AddCheck_Alias_setToken(t *testing.T) { chk := &structs.CheckType{ AliasService: "foo", } - err := a.AddCheck(health, chk, false, "foo") + err := a.AddCheck(health, chk, false, "foo", ConfigSourceLocal) require.NoError(err) cs := a.State.CheckState("aliashealth") @@ -1051,7 +1147,7 @@ acl_token = "hello" chk := &structs.CheckType{ AliasService: "foo", } - err := a.AddCheck(health, chk, false, "") + err := a.AddCheck(health, chk, false, "", ConfigSourceLocal) require.NoError(err) cs := a.State.CheckState("aliashealth") @@ -1081,7 +1177,7 @@ acl_token = "hello" chk := &structs.CheckType{ AliasService: "foo", } - err := a.AddCheck(health, chk, false, "goodbye") + err := a.AddCheck(health, chk, false, "goodbye", ConfigSourceLocal) require.NoError(err) cs := a.State.CheckState("aliashealth") @@ -1120,7 +1216,7 @@ func TestAgent_RemoveCheck(t *testing.T) { ScriptArgs: []string{"exit", "0"}, Interval: 15 * time.Second, } - err := a.AddCheck(health, chk, false, "") + err := a.AddCheck(health, chk, false, "", ConfigSourceLocal) if err != nil { t.Fatalf("err: %v", err) } @@ -1165,7 +1261,7 @@ func TestAgent_HTTPCheck_TLSSkipVerify(t *testing.T) { TLSSkipVerify: true, } - err := a.AddCheck(health, chk, false, "") + err := a.AddCheck(health, chk, false, "", ConfigSourceLocal) if err != nil { t.Fatalf("err: %v", err) } @@ -1214,7 +1310,7 @@ func TestAgent_HTTPCheck_EnableAgentTLSForChecks(t *testing.T) { Interval: 20 * time.Millisecond, } - err := a.AddCheck(health, chk, false, "") + err := a.AddCheck(health, chk, false, "", ConfigSourceLocal) if err != nil { t.Fatalf("err: %v", err) } @@ -1263,7 +1359,7 @@ func TestAgent_updateTTLCheck(t *testing.T) { } // Add check and update it. - err := a.AddCheck(health, chk, false, "") + err := a.AddCheck(health, chk, false, "", ConfigSourceLocal) if err != nil { t.Fatalf("err: %v", err) } @@ -1304,7 +1400,7 @@ func TestAgent_PersistService(t *testing.T) { file := filepath.Join(a.Config.DataDir, servicesDir, stringHash(svc.ID)) // Check is not persisted unless requested - if err := a.AddService(svc, nil, false, ""); err != nil { + if err := a.AddService(svc, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } if _, err := os.Stat(file); err == nil { @@ -1312,7 +1408,7 @@ func TestAgent_PersistService(t *testing.T) { } // Persists to file if requested - if err := a.AddService(svc, nil, true, "mytoken"); err != nil { + if err := a.AddService(svc, nil, true, "mytoken", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } if _, err := os.Stat(file); err != nil { @@ -1335,7 +1431,7 @@ func TestAgent_PersistService(t *testing.T) { // Updates service definition on disk svc.Port = 8001 - if err := a.AddService(svc, nil, true, "mytoken"); err != nil { + if err := a.AddService(svc, nil, true, "mytoken", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } expected, err = json.Marshal(persistedService{ @@ -1429,7 +1525,7 @@ func TestAgent_PurgeService(t *testing.T) { } file := filepath.Join(a.Config.DataDir, servicesDir, stringHash(svc.ID)) - if err := a.AddService(svc, nil, true, ""); err != nil { + if err := a.AddService(svc, nil, true, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1442,7 +1538,7 @@ func TestAgent_PurgeService(t *testing.T) { } // Re-add the service - if err := a.AddService(svc, nil, true, ""); err != nil { + if err := a.AddService(svc, nil, true, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1476,7 +1572,7 @@ func TestAgent_PurgeServiceOnDuplicate(t *testing.T) { } // First persist the service - if err := a.AddService(svc1, nil, true, ""); err != nil { + if err := a.AddService(svc1, nil, true, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } a.Shutdown() @@ -1530,7 +1626,7 @@ func TestAgent_PersistProxy(t *testing.T) { Tags: []string{"foo"}, Port: 8000, } - require.NoError(a.AddService(svc1, nil, true, "")) + require.NoError(a.AddService(svc1, nil, true, "", ConfigSourceLocal)) // Add a proxy for it proxy := &structs.ConnectManagedProxy{ @@ -1541,12 +1637,12 @@ func TestAgent_PersistProxy(t *testing.T) { file := filepath.Join(a.Config.DataDir, proxyDir, stringHash("redis-proxy")) // Proxy is not persisted unless requested - require.NoError(a.AddProxy(proxy, false, false, "")) + require.NoError(a.AddProxy(proxy, false, false, "", ConfigSourceLocal)) _, err := os.Stat(file) require.Error(err, "proxy should not be persisted") // Proxy is persisted if requested - require.NoError(a.AddProxy(proxy, true, false, "")) + require.NoError(a.AddProxy(proxy, true, false, "", ConfigSourceLocal)) _, err = os.Stat(file) require.NoError(err, "proxy should be persisted") @@ -1562,7 +1658,7 @@ func TestAgent_PersistProxy(t *testing.T) { proxy.Config = map[string]interface{}{ "foo": "bar", } - require.NoError(a.AddProxy(proxy, true, false, "")) + require.NoError(a.AddProxy(proxy, true, false, "", ConfigSourceLocal)) content, err = ioutil.ReadFile(file) require.NoError(err) @@ -1601,7 +1697,7 @@ func TestAgent_PurgeProxy(t *testing.T) { Tags: []string{"foo"}, Port: 8000, } - require.NoError(a.AddService(svc1, nil, true, "")) + require.NoError(a.AddService(svc1, nil, true, "", ConfigSourceLocal)) // Add a proxy for it proxy := &structs.ConnectManagedProxy{ @@ -1609,7 +1705,7 @@ func TestAgent_PurgeProxy(t *testing.T) { Command: []string{"/bin/sleep", "3600"}, } proxyID := "redis-proxy" - require.NoError(a.AddProxy(proxy, true, false, "")) + require.NoError(a.AddProxy(proxy, true, false, "", ConfigSourceLocal)) file := filepath.Join(a.Config.DataDir, proxyDir, stringHash("redis-proxy")) @@ -1619,7 +1715,7 @@ func TestAgent_PurgeProxy(t *testing.T) { require.NoError(err, "should not be removed") // Re-add the proxy - require.NoError(a.AddProxy(proxy, true, false, "")) + require.NoError(a.AddProxy(proxy, true, false, "", ConfigSourceLocal)) // Removed require.NoError(a.RemoveProxy(proxyID, true)) @@ -1649,7 +1745,7 @@ func TestAgent_PurgeProxyOnDuplicate(t *testing.T) { Tags: []string{"foo"}, Port: 8000, } - require.NoError(a.AddService(svc1, nil, true, "")) + require.NoError(a.AddService(svc1, nil, true, "", ConfigSourceLocal)) // Add a proxy for it proxy := &structs.ConnectManagedProxy{ @@ -1657,7 +1753,7 @@ func TestAgent_PurgeProxyOnDuplicate(t *testing.T) { Command: []string{"/bin/sleep", "3600"}, } proxyID := "redis-proxy" - require.NoError(a.AddProxy(proxy, true, false, "")) + require.NoError(a.AddProxy(proxy, true, false, "", ConfigSourceLocal)) a.Shutdown() @@ -1716,7 +1812,7 @@ func TestAgent_PersistCheck(t *testing.T) { file := filepath.Join(a.Config.DataDir, checksDir, checkIDHash(check.CheckID)) // Not persisted if not requested - if err := a.AddCheck(check, chkType, false, ""); err != nil { + if err := a.AddCheck(check, chkType, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } if _, err := os.Stat(file); err == nil { @@ -1724,7 +1820,7 @@ func TestAgent_PersistCheck(t *testing.T) { } // Should persist if requested - if err := a.AddCheck(check, chkType, true, "mytoken"); err != nil { + if err := a.AddCheck(check, chkType, true, "mytoken", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } if _, err := os.Stat(file); err != nil { @@ -1748,7 +1844,7 @@ func TestAgent_PersistCheck(t *testing.T) { // Updates the check definition on disk check.Name = "mem1" - if err := a.AddCheck(check, chkType, true, "mytoken"); err != nil { + if err := a.AddCheck(check, chkType, true, "mytoken", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } expected, err = json.Marshal(persistedCheck{ @@ -1806,7 +1902,7 @@ func TestAgent_PurgeCheck(t *testing.T) { } file := filepath.Join(a.Config.DataDir, checksDir, checkIDHash(check.CheckID)) - if err := a.AddCheck(check, nil, true, ""); err != nil { + if err := a.AddCheck(check, nil, true, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1850,7 +1946,7 @@ func TestAgent_PurgeCheckOnDuplicate(t *testing.T) { } // First persist the check - if err := a.AddCheck(check1, nil, true, ""); err != nil { + if err := a.AddCheck(check1, nil, true, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } a.Shutdown() @@ -1926,7 +2022,7 @@ func TestAgent_unloadChecks(t *testing.T) { Tags: []string{"foo"}, Port: 8000, } - if err := a.AddService(svc, nil, false, ""); err != nil { + if err := a.AddService(svc, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -1939,7 +2035,7 @@ func TestAgent_unloadChecks(t *testing.T) { ServiceID: "redis", ServiceName: "redis", } - if err := a.AddCheck(check1, nil, false, ""); err != nil { + if err := a.AddCheck(check1, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %s", err) } found := false @@ -2066,7 +2162,7 @@ func TestAgent_unloadServices(t *testing.T) { } // Register the service - if err := a.AddService(svc, nil, false, ""); err != nil { + if err := a.AddService(svc, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } found := false @@ -2192,7 +2288,7 @@ func TestAgent_Service_MaintenanceMode(t *testing.T) { } // Register the service - if err := a.AddService(svc, nil, false, ""); err != nil { + if err := a.AddService(svc, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -2267,7 +2363,7 @@ func TestAgent_Service_Reap(t *testing.T) { } // Register the service. - if err := a.AddService(svc, chkTypes, false, ""); err != nil { + if err := a.AddService(svc, chkTypes, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -2340,7 +2436,7 @@ func TestAgent_Service_NoReap(t *testing.T) { } // Register the service. - if err := a.AddService(svc, chkTypes, false, ""); err != nil { + if err := a.AddService(svc, chkTypes, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -2383,7 +2479,7 @@ func TestAgent_addCheck_restoresSnapshot(t *testing.T) { Tags: []string{"foo"}, Port: 8000, } - if err := a.AddService(svc, nil, false, ""); err != nil { + if err := a.AddService(svc, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -2396,13 +2492,13 @@ func TestAgent_addCheck_restoresSnapshot(t *testing.T) { ServiceID: "redis", ServiceName: "redis", } - if err := a.AddCheck(check1, nil, false, ""); err != nil { + if err := a.AddCheck(check1, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %s", err) } // Re-registering the service preserves the state of the check chkTypes := []*structs.CheckType{&structs.CheckType{TTL: 30 * time.Second}} - if err := a.AddService(svc, chkTypes, false, ""); err != nil { + if err := a.AddService(svc, chkTypes, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %s", err) } check, ok := a.State.Checks()["service:redis"] @@ -2471,7 +2567,7 @@ func TestAgent_checkStateSnapshot(t *testing.T) { Tags: []string{"foo"}, Port: 8000, } - if err := a.AddService(svc, nil, false, ""); err != nil { + if err := a.AddService(svc, nil, false, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -2484,7 +2580,7 @@ func TestAgent_checkStateSnapshot(t *testing.T) { ServiceID: "redis", ServiceName: "redis", } - if err := a.AddCheck(check1, nil, true, ""); err != nil { + if err := a.AddCheck(check1, nil, true, "", ConfigSourceLocal); err != nil { t.Fatalf("err: %s", err) } @@ -2976,9 +3072,9 @@ func TestAgent_AddProxy(t *testing.T) { Service: "web", Port: 8080, } - require.NoError(a.AddService(reg, nil, false, "")) + require.NoError(a.AddService(reg, nil, false, "", ConfigSourceLocal)) - err := a.AddProxy(tt.proxy, false, false, "") + err := a.AddProxy(tt.proxy, false, false, "", ConfigSourceLocal) if tt.wantErr { require.Error(err) return @@ -3030,7 +3126,7 @@ func TestAgent_RemoveProxy(t *testing.T) { Service: "web", Port: 8080, } - require.NoError(a.AddService(reg, nil, false, "")) + require.NoError(a.AddService(reg, nil, false, "", ConfigSourceLocal)) // Add a proxy for web pReg := &structs.ConnectManagedProxy{ @@ -3038,7 +3134,7 @@ func TestAgent_RemoveProxy(t *testing.T) { ExecMode: structs.ProxyExecModeDaemon, Command: []string{"foo"}, } - require.NoError(a.AddProxy(pReg, false, false, "")) + require.NoError(a.AddProxy(pReg, false, false, "", ConfigSourceLocal)) // Test the ID was created as we expect. gotProxy := a.State.Proxy("web-proxy") @@ -3069,7 +3165,7 @@ func TestAgent_ReLoadProxiesFromConfig(t *testing.T) { Service: "web", Port: 8080, } - require.NoError(a.AddService(reg, nil, false, "")) + require.NoError(a.AddService(reg, nil, false, "", ConfigSourceLocal)) proxies := a.State.Proxies() require.Len(proxies, 0) diff --git a/agent/config/builder.go b/agent/config/builder.go index c37d8bdaac..5afe86c312 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -588,6 +588,9 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) { proxyDefaultScriptCommand := c.Connect.ProxyDefaults.ScriptCommand proxyDefaultConfig := c.Connect.ProxyDefaults.Config + enableRemoteScriptChecks := b.boolVal(c.EnableScriptChecks) + enableLocalScriptChecks := b.boolValWithDefault(c.EnableLocalScriptChecks, enableRemoteScriptChecks) + // ---------------------------------------------------------------- // build runtime config // @@ -743,7 +746,8 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) { DiscoveryMaxStale: b.durationVal("discovery_max_stale", c.DiscoveryMaxStale), EnableAgentTLSForChecks: b.boolVal(c.EnableAgentTLSForChecks), EnableDebug: b.boolVal(c.EnableDebug), - EnableScriptChecks: b.boolVal(c.EnableScriptChecks), + EnableRemoteScriptChecks: enableRemoteScriptChecks, + EnableLocalScriptChecks: enableLocalScriptChecks, EnableSyslog: b.boolVal(c.EnableSyslog), EnableUI: b.boolVal(c.UI), EncryptKey: b.stringVal(c.EncryptKey), diff --git a/agent/config/config.go b/agent/config/config.go index 2a873da829..22b1b20031 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -191,6 +191,7 @@ type Config struct { EnableAgentTLSForChecks *bool `json:"enable_agent_tls_for_checks,omitempty" hcl:"enable_agent_tls_for_checks" mapstructure:"enable_agent_tls_for_checks"` EnableDebug *bool `json:"enable_debug,omitempty" hcl:"enable_debug" mapstructure:"enable_debug"` EnableScriptChecks *bool `json:"enable_script_checks,omitempty" hcl:"enable_script_checks" mapstructure:"enable_script_checks"` + EnableLocalScriptChecks *bool `json:"enable_local_script_checks,omitempty" hcl:"enable_local_script_checks" mapstructure:"enable_local_script_checks"` EnableSyslog *bool `json:"enable_syslog,omitempty" hcl:"enable_syslog" mapstructure:"enable_syslog"` EncryptKey *string `json:"encrypt,omitempty" hcl:"encrypt" mapstructure:"encrypt"` EncryptVerifyIncoming *bool `json:"encrypt_verify_incoming,omitempty" hcl:"encrypt_verify_incoming" mapstructure:"encrypt_verify_incoming"` diff --git a/agent/config/flags.go b/agent/config/flags.go index 31d6e2da6c..53b255b859 100644 --- a/agent/config/flags.go +++ b/agent/config/flags.go @@ -71,6 +71,7 @@ func AddFlags(fs *flag.FlagSet, f *Flags) { add(&f.Config.Ports.DNS, "dns-port", "DNS port to use.") add(&f.Config.DNSDomain, "domain", "Domain to use for DNS interface.") add(&f.Config.EnableScriptChecks, "enable-script-checks", "Enables health check scripts.") + add(&f.Config.EnableLocalScriptChecks, "enable-local-script-checks", "Enables health check scripts from configuration file.") add(&f.Config.EncryptKey, "encrypt", "Provides the gossip encryption key.") add(&f.Config.Ports.GRPC, "grpc-port", "Sets the gRPC API port to listen on (currently needed for Envoy xDS only).") add(&f.Config.Ports.HTTP, "http-port", "Sets the HTTP API port to listen on.") diff --git a/agent/config/runtime.go b/agent/config/runtime.go index fb6e034cd4..883606b9e2 100644 --- a/agent/config/runtime.go +++ b/agent/config/runtime.go @@ -638,13 +638,21 @@ type RuntimeConfig struct { // hcl: enable_debug = (true|false) EnableDebug bool - // EnableScriptChecks controls whether health checks which execute - // scripts are enabled. This includes regular script checks and Docker + // EnableLocalScriptChecks controls whether health checks declared from the local + // config file which execute scripts are enabled. This includes regular script + // checks and Docker checks. + // + // hcl: (enable_script_checks|enable_local_script_checks) = (true|false) + // flag: -enable-script-checks, -enable-local-script-checks + EnableLocalScriptChecks bool + + // EnableRemoeScriptChecks controls whether health checks declared from the http API + // which execute scripts are enabled. This includes regular script checks and Docker // checks. // // hcl: enable_script_checks = (true|false) // flag: -enable-script-checks - EnableScriptChecks bool + EnableRemoteScriptChecks bool // EnableSyslog is used to also tee all the logs over to syslog. Only supported // on linux and OSX. Other platforms will generate an error. diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index 1c199ab9be..c415942f42 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -351,7 +351,8 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { `-data-dir=` + dataDir, }, patch: func(rt *RuntimeConfig) { - rt.EnableScriptChecks = true + rt.EnableLocalScriptChecks = true + rt.EnableRemoteScriptChecks = true rt.DataDir = dataDir }, }, @@ -2973,6 +2974,7 @@ func TestFullConfig(t *testing.T) { "enable_agent_tls_for_checks": true, "enable_debug": true, "enable_script_checks": true, + "enable_local_script_checks": true, "enable_syslog": true, "encrypt": "A4wELWqH", "encrypt_verify_incoming": true, @@ -3504,6 +3506,7 @@ func TestFullConfig(t *testing.T) { enable_agent_tls_for_checks = true enable_debug = true enable_script_checks = true + enable_local_script_checks = true enable_syslog = true encrypt = "A4wELWqH" encrypt_verify_incoming = true @@ -4129,7 +4132,8 @@ func TestFullConfig(t *testing.T) { EnableACLReplication: true, EnableAgentTLSForChecks: true, EnableDebug: true, - EnableScriptChecks: true, + EnableRemoteScriptChecks: true, + EnableLocalScriptChecks: true, EnableSyslog: true, EnableUI: true, EncryptKey: "A4wELWqH", @@ -4918,7 +4922,8 @@ func TestSanitize(t *testing.T) { "EnableACLReplication": false, "EnableAgentTLSForChecks": false, "EnableDebug": false, - "EnableScriptChecks": false, + "EnableLocalScriptChecks": false, + "EnableRemoteScriptChecks": false, "EnableSyslog": false, "EnableUI": false, "EncryptKey": "hidden", diff --git a/agent/connect/ca/provider_consul.go b/agent/connect/ca/provider_consul.go index 8971d5cd99..c4141ed48b 100644 --- a/agent/connect/ca/provider_consul.go +++ b/agent/connect/ca/provider_consul.go @@ -623,9 +623,9 @@ func (c *ConsulProvider) generateCA(privateKey string, sn uint64) (string, error serialNum := &big.Int{} serialNum.SetUint64(sn) template := x509.Certificate{ - SerialNumber: serialNum, - Subject: pkix.Name{CommonName: name}, - URIs: []*url.URL{id.URI()}, + SerialNumber: serialNum, + Subject: pkix.Name{CommonName: name}, + URIs: []*url.URL{id.URI()}, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | diff --git a/agent/consul/fsm/snapshot_oss.go b/agent/consul/fsm/snapshot_oss.go index 8646711ff2..ad4af9c5bb 100644 --- a/agent/consul/fsm/snapshot_oss.go +++ b/agent/consul/fsm/snapshot_oss.go @@ -79,7 +79,7 @@ func (s *snapshot) persistNodes(sink raft.SnapshotSink, Node: n.Node, Address: n.Address, TaggedAddresses: n.TaggedAddresses, - NodeMeta: n.Meta, + NodeMeta: n.Meta, } // Register the node itself diff --git a/agent/sidecar_service_test.go b/agent/sidecar_service_test.go index 96cb53d35d..0364fc5eb2 100644 --- a/agent/sidecar_service_test.go +++ b/agent/sidecar_service_test.go @@ -283,7 +283,7 @@ func TestAgent_sidecarServiceFromNodeService(t *testing.T) { a := NewTestAgent("jones", hcl) if tt.preRegister != nil { - err := a.AddService(tt.preRegister.NodeService(), nil, false, "") + err := a.AddService(tt.preRegister.NodeService(), nil, false, "", ConfigSourceLocal) require.NoError(err) } diff --git a/command/maint/maint_test.go b/command/maint/maint_test.go index 7fa89c6f8b..e211228eaa 100644 --- a/command/maint/maint_test.go +++ b/command/maint/maint_test.go @@ -49,7 +49,7 @@ func TestMaintCommand_NoArgs(t *testing.T) { ID: "test", Service: "test", } - if err := a.AddService(service, nil, false, ""); err != nil { + if err := a.AddService(service, nil, false, "", agent.ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } if err := a.EnableServiceMaintenance("test", "broken 1", ""); err != nil { @@ -145,7 +145,7 @@ func TestMaintCommand_EnableServiceMaintenance(t *testing.T) { ID: "test", Service: "test", } - if err := a.AddService(service, nil, false, ""); err != nil { + if err := a.AddService(service, nil, false, "", agent.ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } @@ -179,7 +179,7 @@ func TestMaintCommand_DisableServiceMaintenance(t *testing.T) { ID: "test", Service: "test", } - if err := a.AddService(service, nil, false, ""); err != nil { + if err := a.AddService(service, nil, false, "", agent.ConfigSourceLocal); err != nil { t.Fatalf("err: %v", err) } diff --git a/website/source/docs/agent/checks.html.md b/website/source/docs/agent/checks.html.md index 747bb3bb5c..f8a2b95227 100644 --- a/website/source/docs/agent/checks.html.md +++ b/website/source/docs/agent/checks.html.md @@ -28,8 +28,13 @@ There are several different kinds of checks: Consul will wait for any child processes spawned by the script to finish. For any other system, Consul will attempt to force-kill the script and any child processes it has spawned once the timeout has passed. - In Consul 0.9.0 and later, the agent must be configured with [`enable_script_checks`] - (/docs/agent/options.html#_enable_script_checks) set to `true` in order to enable script checks. + In Consul 0.9.0 and later, script checks are not enabled by default. To use them you + can either use : + * [`enable_local_script_checks`](/docs/agent/options.html#_enable_local_script_checks): + enable script checks defile in local config files. Script checks defined via the HTTP + API will not be allowed. + * [`enable_script_checks`](/docs/agent/options.html#_enable_script_checks): enable + script checks regardless of how they are defined. * HTTP + Interval - These checks make an HTTP `GET` request every Interval (e.g. every 30 seconds) to the specified URL. The status of the service depends on diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md index 532e79d197..b8b501d650 100644 --- a/website/source/docs/agent/options.html.md +++ b/website/source/docs/agent/options.html.md @@ -200,9 +200,13 @@ will exit with an error at startup. * `-enable-script-checks` This controls whether [health checks that execute scripts](/docs/agent/checks.html) are enabled on - this agent, and defaults to `false` so operators must opt-in to allowing these. If enabled, - it is recommended to [enable ACLs](/docs/guides/acl.html) as well to control which users are - allowed to register new checks to execute scripts. This was added in Consul 0.9.0. + this agent, and defaults to `false` so operators must opt-in to allowing these. If enabled, it is recommended + to [enable ACLs](/docs/guides/acl.html) as well to control which users are allowed to register new checks to + execute scripts. This was added in Consul 0.9.0. + +* `-enable-local-script-checks` + Like [`enable_script_checks`](#_enable_script_checks), but only enable them when they are defined in the local + config files. Script checks defined in HTTP API registratrions will still not be allowed. * `-encrypt` - Specifies the secret key to use for encryption of Consul @@ -747,7 +751,7 @@ default will automatically work with some tooling. #### Common CA Config Options

There are also a number of common configuration options supported by all providers:

- + * `leaf_cert_ttl` The upper bound on the lease duration of a leaf certificate issued for a service. In most cases a new leaf certificate will be requested by a proxy before this limit is reached. This is also the effective limit on how long a server @@ -959,72 +963,72 @@ default will automatically work with some tooling. [`-disable-keyring-file` command-line flag](#_disable_keyring_file). * `gossip_lan` - **(Advanced)** This object contains a number of sub-keys - which can be set to tune the LAN gossip communications. These are only provided for users running especially large + which can be set to tune the LAN gossip communications. These are only provided for users running especially large clusters that need fine tuning and are prepared to spend significant effort correctly tuning them for their environment and workload. **Tuning these improperly can cause Consul to fail in unexpected ways**. The default values are appropriate in almost all deployments. - + * `gossip_nodes` - The number of random nodes to send - gossip messages to per gossip_interval. Increasing this number causes the gossip messages to propagate + gossip messages to per gossip_interval. Increasing this number causes the gossip messages to propagate across the cluster more quickly at the expense of increased bandwidth. The default is 3. - + * `gossip_interval` - The interval between sending - messages that need to be gossiped that haven't been able to piggyback on probing messages. If this is set to + messages that need to be gossiped that haven't been able to piggyback on probing messages. If this is set to zero, non-piggyback gossip is disabled. By lowering this value (more frequent) gossip messages are propagated across the cluster more quickly at the expense of increased bandwidth. The default is 200ms. - + * `probe_interval` - The interval between random node - probes. Setting this lower (more frequent) will cause the cluster to detect failed nodes more quickly + probes. Setting this lower (more frequent) will cause the cluster to detect failed nodes more quickly at the expense of increased bandwidth usage. The default is 1s. - + * `probe_timeout` - The timeout to wait for an ack from a probed node before assuming it is unhealthy. This should be at least the 99-percentile of RTT (round-trip time) on your network. The default is 500ms and is a conservative value suitable for almost all realistic deployments. - - * `retransmit_mult` - The multiplier for the number + + * `retransmit_mult` - The multiplier for the number of retransmissions that are attempted for messages broadcasted over gossip. The number of retransmits is scaled using this multiplier and the cluster size. The higher the multiplier, the more likely a failed broadcast is to converge at the expense of increased bandwidth. The default is 4. - + * `suspicion_mult` - The multiplier for determining the time an inaccessible node is considered suspect before declaring it dead. The timeout is scaled with the cluster - size and the probe_interval. This allows the timeout to scale properly with expected propagation delay with a - larger cluster size. The higher the multiplier, the longer an inaccessible node is considered part of the + size and the probe_interval. This allows the timeout to scale properly with expected propagation delay with a + larger cluster size. The higher the multiplier, the longer an inaccessible node is considered part of the cluster before declaring it dead, giving that suspect node more time to refute if it is indeed still alive. The default is 4. - + * `gossip_wan` - **(Advanced)** This object contains a number of sub-keys - which can be set to tune the WAN gossip communications. These are only provided for users running especially large + which can be set to tune the WAN gossip communications. These are only provided for users running especially large clusters that need fine tuning and are prepared to spend significant effort correctly tuning them for their environment and workload. **Tuning these improperly can cause Consul to fail in unexpected ways**. The default values are appropriate in almost all deployments. - + * `gossip_nodes` - The number of random nodes to send - gossip messages to per gossip_interval. Increasing this number causes the gossip messages to propagate + gossip messages to per gossip_interval. Increasing this number causes the gossip messages to propagate across the cluster more quickly at the expense of increased bandwidth. The default is 3. - + * `gossip_interval` - The interval between sending - messages that need to be gossiped that haven't been able to piggyback on probing messages. If this is set to + messages that need to be gossiped that haven't been able to piggyback on probing messages. If this is set to zero, non-piggyback gossip is disabled. By lowering this value (more frequent) gossip messages are propagated across the cluster more quickly at the expense of increased bandwidth. The default is 200ms. - + * `probe_interval` - The interval between random node - probes. Setting this lower (more frequent) will cause the cluster to detect failed nodes more quickly + probes. Setting this lower (more frequent) will cause the cluster to detect failed nodes more quickly at the expense of increased bandwidth usage. The default is 1s. - + * `probe_timeout` - The timeout to wait for an ack from a probed node before assuming it is unhealthy. This should be at least the 99-percentile of RTT (round-trip time) on your network. The default is 500ms and is a conservative value suitable for almost all realistic deployments. - - * `retransmit_mult` - The multiplier for the number + + * `retransmit_mult` - The multiplier for the number of retransmissions that are attempted for messages broadcasted over gossip. The number of retransmits is scaled using this multiplier and the cluster size. The higher the multiplier, the more likely a failed broadcast is to converge at the expense of increased bandwidth. The default is 4. - + * `suspicion_mult` - The multiplier for determining the time an inaccessible node is considered suspect before declaring it dead. The timeout is scaled with the cluster - size and the probe_interval. This allows the timeout to scale properly with expected propagation delay with a - larger cluster size. The higher the multiplier, the longer an inaccessible node is considered part of the + size and the probe_interval. This allows the timeout to scale properly with expected propagation delay with a + larger cluster size. The higher the multiplier, the longer an inaccessible node is considered part of the cluster before declaring it dead, giving that suspect node more time to refute if it is indeed still alive. The default is 4. diff --git a/website/source/docs/guides/acl.html.md b/website/source/docs/guides/acl.html.md index d46b19bc2e..1106dd32c7 100644 --- a/website/source/docs/guides/acl.html.md +++ b/website/source/docs/guides/acl.html.md @@ -996,8 +996,9 @@ to use for registration events: access. In addition to ACLs, in Consul 0.9.0 and later, the agent must be configured with -[`enable_script_checks`](/docs/agent/options.html#_enable_script_checks) set to `true` in order to enable -script checks. +[`enable_script_checks`](/docs/agent/options.html#_enable_script_checks) or +[`enable_local_script_checks`](/docs/agent/options.html#_enable_local_script_checks) +set to `true` in order to enable script checks. #### Session Rules