From 571300629758bc7a90f10e66ce461d2251db54d9 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Wed, 4 Feb 2015 17:19:01 -0800 Subject: [PATCH 01/23] agent: Adding new Atlas configs --- command/agent/config.go | 22 ++++++++++++++++++++++ command/agent/config_test.go | 20 ++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/command/agent/config.go b/command/agent/config.go index 6f457aa05..d1cc57655 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -318,6 +318,19 @@ type Config struct { // HTTPAPIResponseHeaders are used to add HTTP header response fields to the HTTP API responses. HTTPAPIResponseHeaders map[string]string `mapstructure:"http_api_response_headers"` + // AtlasCluster is the name of the cluster we belong to. e.g. hashicorp/stage + AtlasCluster string `mapstructure:"atlas_cluster"` + + // AtlasToken is our authentication token from Atlas + AtlasToken string `mapstructure:"atlas_token"` + + // AtlasACLToken is applied to inbound requests if no other token + // is provided. This takes higher precedence than the ACLToken. + // Without this, the ACLToken is used. If that is not specified either, + // then the 'anonymous' token is used. This can be set to 'anonymous' + // to reduce the Atlas privileges to below that of the ACLToken. + AtlasACLToken string `mapstructure:"atlas_acl_token"` + // AEInterval controls the anti-entropy interval. This is how often // the agent attempts to reconcile it's local state with the server' // representation of our state. Defaults to every 60s. @@ -941,6 +954,15 @@ func MergeConfig(a, b *Config) *Config { if b.UnixSockets.Perms != "" { result.UnixSockets.Perms = b.UnixSockets.Perms } + if b.AtlasCluster != "" { + result.AtlasCluster = b.AtlasCluster + } + if b.AtlasToken != "" { + result.AtlasToken = b.AtlasToken + } + if b.AtlasACLToken != "" { + result.AtlasACLToken = b.AtlasACLToken + } if len(b.HTTPAPIResponseHeaders) != 0 { if result.HTTPAPIResponseHeaders == nil { diff --git a/command/agent/config_test.go b/command/agent/config_test.go index 699f8de88..3aaef2189 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -633,6 +633,23 @@ func TestDecodeConfig(t *testing.T) { if config.HTTPAPIResponseHeaders["X-XSS-Protection"] != "1; mode=block" { t.Fatalf("bad: %#v", config) } + + // Atlas configs + input = `{"atlas_cluster": "hashicorp/prod", "atlas_token": "abcdefg", "atlas_acl_token": "123456789"}` + config, err = DecodeConfig(bytes.NewReader([]byte(input))) + if err != nil { + t.Fatalf("err: %s", err) + } + + if config.AtlasCluster != "hashicorp/prod" { + t.Fatalf("bad: %#v", config) + } + if config.AtlasToken != "abcdefg" { + t.Fatalf("bad: %#v", config) + } + if config.AtlasACLToken != "123456789" { + t.Fatalf("bad: %#v", config) + } } func TestDecodeConfig_invalidKeys(t *testing.T) { @@ -1096,6 +1113,9 @@ func TestMergeConfig(t *testing.T) { Perms: "0700", }, }, + AtlasCluster: "hashicorp/prod", + AtlasToken: "123456789", + AtlasACLToken: "abcdefgh", } c := MergeConfig(a, b) From 49d11e37f7e297761a6c5fe4a1be4000608b4f39 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Wed, 4 Feb 2015 17:23:42 -0800 Subject: [PATCH 02/23] agent: Adding Atlas CLI flags --- command/agent/command.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/command/agent/command.go b/command/agent/command.go index e0ebfbfc7..d1f820585 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -76,6 +76,9 @@ func (c *Command) readConfig() *Config { cmdFlags.StringVar(&cmdConfig.BindAddr, "bind", "", "address to bind server listeners to") cmdFlags.StringVar(&cmdConfig.AdvertiseAddr, "advertise", "", "address to advertise instead of bind addr") + cmdFlags.StringVar(&cmdConfig.AtlasCluster, "atlas-cluster", "", "cluster name in Atlas") + cmdFlags.StringVar(&cmdConfig.AtlasToken, "atlas-token", "", "authentication token for Atlas") + cmdFlags.IntVar(&cmdConfig.Protocol, "protocol", -1, "protocol version") cmdFlags.BoolVar(&cmdConfig.EnableSyslog, "syslog", false, @@ -815,6 +818,8 @@ Usage: consul agent [options] Options: -advertise=addr Sets the advertise address to use + -atlas-cluster=org/name Sets the Atlas cluster name, enables SCADA. + -atlas-token=token Provides the Atlas API token -bootstrap Sets server to bootstrap mode -bind=0.0.0.0 Sets the bind address for cluster communication -bootstrap-expect=0 Sets server to expect bootstrap mode. From 08895ce325cf5c2c66e38a90ac773270f03ae2bf Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Wed, 4 Feb 2015 18:06:36 -0800 Subject: [PATCH 03/23] agent: Starting SCADA integration --- command/agent/command.go | 24 ++++++++++++++- command/agent/scada.go | 66 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 command/agent/scada.go diff --git a/command/agent/command.go b/command/agent/command.go index d1f820585..4b75d7896 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -19,6 +19,7 @@ import ( "github.com/hashicorp/go-checkpoint" "github.com/hashicorp/go-syslog" "github.com/hashicorp/logutils" + scada "github.com/hashicorp/scada-client" "github.com/mitchellh/cli" ) @@ -45,6 +46,7 @@ type Command struct { rpcServer *AgentRPC httpServers []*HTTPServer dnsServer *DNSServer + scadaProvider *scada.Provider } // readConfig is responsible for setup of our configuration using @@ -382,6 +384,17 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log }() } + // Enable the SCADA integration + if config.AtlasCluster != "" { + provider, err := NewProvider(config, logOutput) + if err != nil { + agent.Shutdown() + c.Ui.Error(fmt.Sprintf("Error starting SCADA connection: %s", err)) + return err + } + c.scadaProvider = provider + } + return nil } @@ -589,10 +602,12 @@ func (c *Command) Run(args []string) int { if c.dnsServer != nil { defer c.dnsServer.Shutdown() } - for _, server := range c.httpServers { defer server.Shutdown() } + if c.scadaProvider != nil { + defer c.scadaProvider.Shutdown() + } // Join startup nodes if specified if err := c.startupJoin(config); err != nil { @@ -631,6 +646,12 @@ func (c *Command) Run(args []string) int { gossipEncrypted = c.agent.client.Encrypted() } + // Determine the Atlas cluster + cluster := config.AtlasCluster + if cluster == "" { + cluster = "" + } + // Let the agent know we've finished registration c.agent.StartSync() @@ -644,6 +665,7 @@ func (c *Command) Run(args []string) int { config.Ports.SerfLan, config.Ports.SerfWan)) c.Ui.Info(fmt.Sprintf("Gossip encrypt: %v, RPC-TLS: %v, TLS-Incoming: %v", gossipEncrypted, config.VerifyOutgoing, config.VerifyIncoming)) + c.Ui.Info(fmt.Sprintf(" Atlas Cluster: %v", cluster)) // Enable log streaming c.Ui.Info("") diff --git a/command/agent/scada.go b/command/agent/scada.go new file mode 100644 index 000000000..75ebfdd1b --- /dev/null +++ b/command/agent/scada.go @@ -0,0 +1,66 @@ +package agent + +import ( + "crypto/tls" + "fmt" + "io" + "log" + + "github.com/hashicorp/scada-client" +) + +const ( + // providerService is the service name we use + providerService = "consul" + + // resourceType is the type of resource we represent + // when connecting to SCADA + resourceType = "infrastructures" +) + +// ProviderService returns the service information for the provider +func ProviderService(c *Config) *client.ProviderService { + return &client.ProviderService{ + Service: providerService, + ServiceVersion: fmt.Sprintf("%s%s", c.Version, c.VersionPrerelease), + Capabilities: map[string]int{ + "http": 1, + }, + Meta: map[string]string{ + "type": "", + "datacenter": "", + }, + ResourceType: resourceType, + } +} + +// ProviderConfig returns the configuration for the SCADA provider +func ProviderConfig(c *Config) *client.ProviderConfig { + return &client.ProviderConfig{ + Service: ProviderService(c), + Handlers: map[string]client.CapabilityProvider{ + "http": nil, + }, + ResourceGroup: c.AtlasCluster, + Token: c.AtlasToken, + } +} + +// NewProvider creates a new SCADA provider using the +// given configuration. Requests are routed to the +func NewProvider(c *Config, logOutput io.Writer) (*client.Provider, error) { + // Get the configuration of the provider + config := ProviderConfig(c) + config.Logger = log.New(logOutput, "", log.LstdFlags) + + // TODO: REMOVE + config.TLSConfig = &tls.Config{ + InsecureSkipVerify: true, + } + + // TODO: Setup the handlers + config.Handlers["http"] = nil + + // Create the provider + return client.NewProvider(config) +} From 948f9a0d00e6cca4908002b88212833b81ee06b6 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Wed, 4 Feb 2015 18:17:45 -0800 Subject: [PATCH 04/23] agent: SCADA HTTP listener --- command/agent/command.go | 29 +++++++++++++++-------------- command/agent/http.go | 22 +++++++++++++++++++++- command/agent/scada.go | 14 ++++++++++---- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/command/agent/command.go b/command/agent/command.go index 4b75d7896..76c7a0944 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -332,8 +332,21 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log c.Ui.Output("Starting Consul agent RPC...") c.rpcServer = NewAgentRPC(agent, rpcListener, logOutput, logWriter) - if config.Ports.HTTP > 0 || config.Ports.HTTPS > 0 { - servers, err := NewHTTPServers(agent, config, logOutput) + // Enable the SCADA integration + var scadaList net.Listener + if config.AtlasCluster != "" { + provider, list, err := NewProvider(config, logOutput) + if err != nil { + agent.Shutdown() + c.Ui.Error(fmt.Sprintf("Error starting SCADA connection: %s", err)) + return err + } + c.scadaProvider = provider + scadaList = list + } + + if config.Ports.HTTP > 0 || config.Ports.HTTPS > 0 || scadaList != nil { + servers, err := NewHTTPServers(agent, config, scadaList, logOutput) if err != nil { agent.Shutdown() c.Ui.Error(fmt.Sprintf("Error starting http servers: %s", err)) @@ -383,18 +396,6 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log c.checkpointResults(checkpoint.Check(updateParams)) }() } - - // Enable the SCADA integration - if config.AtlasCluster != "" { - provider, err := NewProvider(config, logOutput) - if err != nil { - agent.Shutdown() - c.Ui.Error(fmt.Sprintf("Error starting SCADA connection: %s", err)) - return err - } - c.scadaProvider = provider - } - return nil } diff --git a/command/agent/http.go b/command/agent/http.go index 91ae3a1d0..236fd451f 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -32,7 +32,7 @@ type HTTPServer struct { // NewHTTPServers starts new HTTP servers to provide an interface to // the agent. -func NewHTTPServers(agent *Agent, config *Config, logOutput io.Writer) ([]*HTTPServer, error) { +func NewHTTPServers(agent *Agent, config *Config, scada net.Listener, logOutput io.Writer) ([]*HTTPServer, error) { var tlsConfig *tls.Config var list net.Listener var httpAddr net.Addr @@ -136,6 +136,26 @@ func NewHTTPServers(agent *Agent, config *Config, logOutput io.Writer) ([]*HTTPS servers = append(servers, srv) } + if scada != nil { + // Create the mux + mux := http.NewServeMux() + + // Create the server + srv := &HTTPServer{ + agent: agent, + mux: mux, + listener: scada, + logger: log.New(logOutput, "", log.LstdFlags), + uiDir: config.UiDir, + addr: "SCADA", + } + srv.registerHandlers(false) // Never allow debug for SCADA + + // Start the server + go http.Serve(list, mux) + servers = append(servers, srv) + } + return servers, nil } diff --git a/command/agent/scada.go b/command/agent/scada.go index 75ebfdd1b..05416c311 100644 --- a/command/agent/scada.go +++ b/command/agent/scada.go @@ -5,6 +5,8 @@ import ( "fmt" "io" "log" + "net" + "strconv" "github.com/hashicorp/scada-client" ) @@ -27,8 +29,8 @@ func ProviderService(c *Config) *client.ProviderService { "http": 1, }, Meta: map[string]string{ - "type": "", - "datacenter": "", + "server": strconv.FormatBool(c.Server), + "datacenter": c.Datacenter, }, ResourceType: resourceType, } @@ -48,7 +50,7 @@ func ProviderConfig(c *Config) *client.ProviderConfig { // NewProvider creates a new SCADA provider using the // given configuration. Requests are routed to the -func NewProvider(c *Config, logOutput io.Writer) (*client.Provider, error) { +func NewProvider(c *Config, logOutput io.Writer) (*client.Provider, net.Listener, error) { // Get the configuration of the provider config := ProviderConfig(c) config.Logger = log.New(logOutput, "", log.LstdFlags) @@ -62,5 +64,9 @@ func NewProvider(c *Config, logOutput io.Writer) (*client.Provider, error) { config.Handlers["http"] = nil // Create the provider - return client.NewProvider(config) + provider, err := client.NewProvider(config) + if err != nil { + return nil, nil, err + } + return provider, nil, nil } From b714144685caed7ceb5ac6cd436c5246eb9a4e8b Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Wed, 4 Feb 2015 18:36:35 -0800 Subject: [PATCH 05/23] agent: SCADA HTTP integration --- command/agent/scada.go | 122 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 3 deletions(-) diff --git a/command/agent/scada.go b/command/agent/scada.go index 05416c311..74df8a427 100644 --- a/command/agent/scada.go +++ b/command/agent/scada.go @@ -2,11 +2,14 @@ package agent import ( "crypto/tls" + "errors" "fmt" "io" "log" "net" "strconv" + "sync" + "time" "github.com/hashicorp/scada-client" ) @@ -60,13 +63,126 @@ func NewProvider(c *Config, logOutput io.Writer) (*client.Provider, net.Listener InsecureSkipVerify: true, } - // TODO: Setup the handlers - config.Handlers["http"] = nil + // TODO: AtlasACLToken + + // Create an HTTP listener and handler + list := newScadaListener(c.AtlasCluster) + config.Handlers["http"] = func(capability string, meta map[string]string, + conn io.ReadWriteCloser) error { + return list.PushRWC(conn) + } // Create the provider provider, err := client.NewProvider(config) if err != nil { + list.Close() return nil, nil, err } - return provider, nil, nil + return provider, list, nil +} + +// scadaListener is used to return a net.Listener for +// incoming SCADA connections +type scadaListener struct { + addr *scadaAddr + pending chan net.Conn + + closed bool + closedCh chan struct{} + l sync.Mutex +} + +// newScadaListener returns a new listener +func newScadaListener(cluster string) *scadaListener { + l := &scadaListener{ + addr: &scadaAddr{cluster}, + pending: make(chan net.Conn), + closedCh: make(chan struct{}), + } + return l +} + +// PushRWC is used to push a io.ReadWriteCloser as a net.Conn +func (s *scadaListener) PushRWC(conn io.ReadWriteCloser) error { + // Check if this already implements net.Conn + if nc, ok := conn.(net.Conn); ok { + return s.Push(nc) + } + + // Wrap to implement the interface + wrapped := &scadaRWC{conn, s.addr} + return s.Push(wrapped) +} + +// Push is used to add a connection to the queu +func (s *scadaListener) Push(conn net.Conn) error { + select { + case s.pending <- conn: + return nil + case <-s.closedCh: + return fmt.Errorf("scada listener closed") + } +} + +func (s *scadaListener) Accept() (net.Conn, error) { + select { + case conn := <-s.pending: + return conn, nil + case <-s.closedCh: + return nil, fmt.Errorf("scada listener closed") + } +} + +func (s *scadaListener) Close() error { + s.l.Lock() + defer s.l.Unlock() + if s.closed { + return nil + } + s.closed = true + close(s.pending) + close(s.closedCh) + return nil +} + +func (s *scadaListener) Addr() net.Addr { + return s.addr +} + +// scadaAddr is used to return a net.Addr for SCADA +type scadaAddr struct { + cluster string +} + +func (s *scadaAddr) Network() string { + return "SCADA" +} + +func (s *scadaAddr) String() string { + return fmt.Sprintf("SCADA::Atlas::%s", s.cluster) +} + +type scadaRWC struct { + io.ReadWriteCloser + addr *scadaAddr +} + +func (s *scadaRWC) LocalAddr() net.Addr { + return s.addr +} + +func (s *scadaRWC) RemoteAddr() net.Addr { + return s.addr +} + +func (s *scadaRWC) SetDeadline(t time.Time) error { + return errors.New("SCADA.Conn does not support deadlines") +} + +func (s *scadaRWC) SetReadDeadline(t time.Time) error { + return errors.New("SCADA.Conn does not support deadlines") +} + +func (s *scadaRWC) SetWriteDeadline(t time.Time) error { + return errors.New("SCADA.Conn does not support deadlines") } From 524bfccc5ac4181f222c46b6643e294f6d516052 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Wed, 4 Feb 2015 18:45:08 -0800 Subject: [PATCH 06/23] agent: Adding atlas_join configuration --- command/agent/command.go | 2 ++ command/agent/config.go | 7 +++++++ command/agent/config_test.go | 6 +++++- command/agent/http_test.go | 4 ++-- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/command/agent/command.go b/command/agent/command.go index 76c7a0944..2f6478206 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -80,6 +80,7 @@ func (c *Command) readConfig() *Config { cmdFlags.StringVar(&cmdConfig.AtlasCluster, "atlas-cluster", "", "cluster name in Atlas") cmdFlags.StringVar(&cmdConfig.AtlasToken, "atlas-token", "", "authentication token for Atlas") + cmdFlags.BoolVar(&cmdConfig.AtlasJoin, "atlas-join", false, "auto-join with Atlas") cmdFlags.IntVar(&cmdConfig.Protocol, "protocol", -1, "protocol version") @@ -842,6 +843,7 @@ Options: -advertise=addr Sets the advertise address to use -atlas-cluster=org/name Sets the Atlas cluster name, enables SCADA. + -atlas-join Enables auto-joining the Atlas cluster -atlas-token=token Provides the Atlas API token -bootstrap Sets server to bootstrap mode -bind=0.0.0.0 Sets the bind address for cluster communication diff --git a/command/agent/config.go b/command/agent/config.go index d1cc57655..f3c237dcb 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -331,6 +331,10 @@ type Config struct { // to reduce the Atlas privileges to below that of the ACLToken. AtlasACLToken string `mapstructure:"atlas_acl_token"` + // AtlasJoin controls if Atlas will attempt to auto-join the node + // to it's cluster. Requires Atlas integration. + AtlasJoin bool `mapstructure:"atlas_join"` + // AEInterval controls the anti-entropy interval. This is how often // the agent attempts to reconcile it's local state with the server' // representation of our state. Defaults to every 60s. @@ -963,6 +967,9 @@ func MergeConfig(a, b *Config) *Config { if b.AtlasACLToken != "" { result.AtlasACLToken = b.AtlasACLToken } + if b.AtlasJoin { + result.AtlasJoin = true + } if len(b.HTTPAPIResponseHeaders) != 0 { if result.HTTPAPIResponseHeaders == nil { diff --git a/command/agent/config_test.go b/command/agent/config_test.go index 3aaef2189..5e7f254ed 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -635,7 +635,7 @@ func TestDecodeConfig(t *testing.T) { } // Atlas configs - input = `{"atlas_cluster": "hashicorp/prod", "atlas_token": "abcdefg", "atlas_acl_token": "123456789"}` + input = `{"atlas_cluster": "hashicorp/prod", "atlas_token": "abcdefg", "atlas_acl_token": "123456789", "atlas_join": true}` config, err = DecodeConfig(bytes.NewReader([]byte(input))) if err != nil { t.Fatalf("err: %s", err) @@ -650,6 +650,9 @@ func TestDecodeConfig(t *testing.T) { if config.AtlasACLToken != "123456789" { t.Fatalf("bad: %#v", config) } + if !config.AtlasJoin { + t.Fatalf("bad: %#v", config) + } } func TestDecodeConfig_invalidKeys(t *testing.T) { @@ -1116,6 +1119,7 @@ func TestMergeConfig(t *testing.T) { AtlasCluster: "hashicorp/prod", AtlasToken: "123456789", AtlasACLToken: "abcdefgh", + AtlasJoin: true, } c := MergeConfig(a, b) diff --git a/command/agent/http_test.go b/command/agent/http_test.go index 19e8f95af..ae0ef64de 100644 --- a/command/agent/http_test.go +++ b/command/agent/http_test.go @@ -36,7 +36,7 @@ func makeHTTPServerWithConfig(t *testing.T, cb func(c *Config)) (string, *HTTPSe t.Fatalf("err: %v", err) } conf.UiDir = uiDir - servers, err := NewHTTPServers(agent, conf, agent.logOutput) + servers, err := NewHTTPServers(agent, conf, nil, agent.logOutput) if err != nil { t.Fatalf("err: %v", err) } @@ -146,7 +146,7 @@ func TestHTTPServer_UnixSocket_FileExists(t *testing.T) { defer os.RemoveAll(dir) // Try to start the server with the same path anyways. - if _, err := NewHTTPServers(agent, conf, agent.logOutput); err != nil { + if _, err := NewHTTPServers(agent, conf, nil, agent.logOutput); err != nil { t.Fatalf("err: %s", err) } From 75849d643e176096888cff140590f67d3a8345b3 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Wed, 4 Feb 2015 18:46:24 -0800 Subject: [PATCH 07/23] agent: Provide auto-join metadata to SCADA --- command/agent/scada.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/command/agent/scada.go b/command/agent/scada.go index 74df8a427..f94bfe714 100644 --- a/command/agent/scada.go +++ b/command/agent/scada.go @@ -32,8 +32,9 @@ func ProviderService(c *Config) *client.ProviderService { "http": 1, }, Meta: map[string]string{ - "server": strconv.FormatBool(c.Server), + "auto-join": strconv.FormatBool(c.AtlasJoin), "datacenter": c.Datacenter, + "server": strconv.FormatBool(c.Server), }, ResourceType: resourceType, } From d9f95e29838dc72d67b7cff9d5571d4209051f23 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 5 Feb 2015 17:29:55 -0800 Subject: [PATCH 08/23] agent: Fixing panic on shutdown --- command/agent/http.go | 2 +- command/agent/scada.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/command/agent/http.go b/command/agent/http.go index 236fd451f..e80b459a6 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -179,7 +179,7 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { // Shutdown is used to shutdown the HTTP server func (s *HTTPServer) Shutdown() { if s != nil { - s.logger.Printf("[DEBUG] http: Shutting down http server(%v)", s.addr) + s.logger.Printf("[DEBUG] http: Shutting down http server (%v)", s.addr) s.listener.Close() } } diff --git a/command/agent/scada.go b/command/agent/scada.go index f94bfe714..b47b435ca 100644 --- a/command/agent/scada.go +++ b/command/agent/scada.go @@ -141,7 +141,6 @@ func (s *scadaListener) Close() error { return nil } s.closed = true - close(s.pending) close(s.closedCh) return nil } From 269153705ba783f3de5aecd53d1c04fb1b28ac6a Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 5 Feb 2015 17:54:22 -0800 Subject: [PATCH 09/23] agent: Fixing setup of SCADA HTTP listener --- command/agent/http.go | 15 ++++++--------- command/agent/scada.go | 2 ++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/command/agent/http.go b/command/agent/http.go index e80b459a6..bb46787b2 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -33,14 +33,10 @@ type HTTPServer struct { // NewHTTPServers starts new HTTP servers to provide an interface to // the agent. func NewHTTPServers(agent *Agent, config *Config, scada net.Listener, logOutput io.Writer) ([]*HTTPServer, error) { - var tlsConfig *tls.Config - var list net.Listener - var httpAddr net.Addr - var err error var servers []*HTTPServer if config.Ports.HTTPS > 0 { - httpAddr, err = config.ClientListener(config.Addresses.HTTPS, config.Ports.HTTPS) + httpAddr, err := config.ClientListener(config.Addresses.HTTPS, config.Ports.HTTPS) if err != nil { return nil, err } @@ -54,7 +50,7 @@ func NewHTTPServers(agent *Agent, config *Config, scada net.Listener, logOutput NodeName: config.NodeName, ServerName: config.ServerName} - tlsConfig, err = tlsConf.IncomingTLSConfig() + tlsConfig, err := tlsConf.IncomingTLSConfig() if err != nil { return nil, err } @@ -64,7 +60,7 @@ func NewHTTPServers(agent *Agent, config *Config, scada net.Listener, logOutput return nil, fmt.Errorf("Failed to get Listen on %s: %v", httpAddr.String(), err) } - list = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, tlsConfig) + list := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, tlsConfig) // Create the mux mux := http.NewServeMux() @@ -86,7 +82,7 @@ func NewHTTPServers(agent *Agent, config *Config, scada net.Listener, logOutput } if config.Ports.HTTP > 0 { - httpAddr, err = config.ClientListener(config.Addresses.HTTP, config.Ports.HTTP) + httpAddr, err := config.ClientListener(config.Addresses.HTTP, config.Ports.HTTP) if err != nil { return nil, fmt.Errorf("Failed to get ClientListener address:port: %v", err) } @@ -107,6 +103,7 @@ func NewHTTPServers(agent *Agent, config *Config, scada net.Listener, logOutput return nil, fmt.Errorf("Failed to get Listen on %s: %v", httpAddr.String(), err) } + var list net.Listener if isSocket { // Set up ownership/permission bits on the socket file if err := setFilePermissions(socketPath, config.UnixSockets); err != nil { @@ -152,7 +149,7 @@ func NewHTTPServers(agent *Agent, config *Config, scada net.Listener, logOutput srv.registerHandlers(false) // Never allow debug for SCADA // Start the server - go http.Serve(list, mux) + go http.Serve(scada, mux) servers = append(servers, srv) } diff --git a/command/agent/scada.go b/command/agent/scada.go index b47b435ca..7fb8c42f9 100644 --- a/command/agent/scada.go +++ b/command/agent/scada.go @@ -120,6 +120,8 @@ func (s *scadaListener) Push(conn net.Conn) error { select { case s.pending <- conn: return nil + case <-time.After(time.Second): + return fmt.Errorf("accept timed out") case <-s.closedCh: return fmt.Errorf("scada listener closed") } From 7ddf87eb54bf078c22893ded02bc2a9ae6310730 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Fri, 6 Feb 2015 12:44:55 -0800 Subject: [PATCH 10/23] agent: Rename AtlasCluster to AtlasInfrastructure --- command/agent/command.go | 14 +++++++------- command/agent/config.go | 8 ++++---- command/agent/config_test.go | 12 ++++++------ command/agent/scada.go | 4 ++-- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/command/agent/command.go b/command/agent/command.go index 2f6478206..b1da6751e 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -78,7 +78,7 @@ func (c *Command) readConfig() *Config { cmdFlags.StringVar(&cmdConfig.BindAddr, "bind", "", "address to bind server listeners to") cmdFlags.StringVar(&cmdConfig.AdvertiseAddr, "advertise", "", "address to advertise instead of bind addr") - cmdFlags.StringVar(&cmdConfig.AtlasCluster, "atlas-cluster", "", "cluster name in Atlas") + cmdFlags.StringVar(&cmdConfig.AtlasInfrastructure, "atlas", "", "infrastructure name in Atlas") cmdFlags.StringVar(&cmdConfig.AtlasToken, "atlas-token", "", "authentication token for Atlas") cmdFlags.BoolVar(&cmdConfig.AtlasJoin, "atlas-join", false, "auto-join with Atlas") @@ -335,7 +335,7 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log // Enable the SCADA integration var scadaList net.Listener - if config.AtlasCluster != "" { + if config.AtlasInfrastructure != "" { provider, list, err := NewProvider(config, logOutput) if err != nil { agent.Shutdown() @@ -649,9 +649,9 @@ func (c *Command) Run(args []string) int { } // Determine the Atlas cluster - cluster := config.AtlasCluster - if cluster == "" { - cluster = "" + atlas := "" + if config.AtlasInfrastructure != "" { + atlas = fmt.Sprintf("(Infrastructure: '%s' Join: %v)", config.AtlasInfrastructure, config.AtlasJoin) } // Let the agent know we've finished registration @@ -667,7 +667,7 @@ func (c *Command) Run(args []string) int { config.Ports.SerfLan, config.Ports.SerfWan)) c.Ui.Info(fmt.Sprintf("Gossip encrypt: %v, RPC-TLS: %v, TLS-Incoming: %v", gossipEncrypted, config.VerifyOutgoing, config.VerifyIncoming)) - c.Ui.Info(fmt.Sprintf(" Atlas Cluster: %v", cluster)) + c.Ui.Info(fmt.Sprintf(" Atlas: %s", atlas)) // Enable log streaming c.Ui.Info("") @@ -842,7 +842,7 @@ Usage: consul agent [options] Options: -advertise=addr Sets the advertise address to use - -atlas-cluster=org/name Sets the Atlas cluster name, enables SCADA. + -atlas=org/name Sets the Atlas infrastructure name, enables SCADA. -atlas-join Enables auto-joining the Atlas cluster -atlas-token=token Provides the Atlas API token -bootstrap Sets server to bootstrap mode diff --git a/command/agent/config.go b/command/agent/config.go index f3c237dcb..38490a919 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -318,8 +318,8 @@ type Config struct { // HTTPAPIResponseHeaders are used to add HTTP header response fields to the HTTP API responses. HTTPAPIResponseHeaders map[string]string `mapstructure:"http_api_response_headers"` - // AtlasCluster is the name of the cluster we belong to. e.g. hashicorp/stage - AtlasCluster string `mapstructure:"atlas_cluster"` + // AtlasInfrastructure is the name of the infrastructure we belong to. e.g. hashicorp/stage + AtlasInfrastructure string `mapstructure:"atlas_infrastructure"` // AtlasToken is our authentication token from Atlas AtlasToken string `mapstructure:"atlas_token"` @@ -958,8 +958,8 @@ func MergeConfig(a, b *Config) *Config { if b.UnixSockets.Perms != "" { result.UnixSockets.Perms = b.UnixSockets.Perms } - if b.AtlasCluster != "" { - result.AtlasCluster = b.AtlasCluster + if b.AtlasInfrastructure != "" { + result.AtlasInfrastructure = b.AtlasInfrastructure } if b.AtlasToken != "" { result.AtlasToken = b.AtlasToken diff --git a/command/agent/config_test.go b/command/agent/config_test.go index 5e7f254ed..b6cdac205 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -635,13 +635,13 @@ func TestDecodeConfig(t *testing.T) { } // Atlas configs - input = `{"atlas_cluster": "hashicorp/prod", "atlas_token": "abcdefg", "atlas_acl_token": "123456789", "atlas_join": true}` + input = `{"atlas_infrastructure": "hashicorp/prod", "atlas_token": "abcdefg", "atlas_acl_token": "123456789", "atlas_join": true}` config, err = DecodeConfig(bytes.NewReader([]byte(input))) if err != nil { t.Fatalf("err: %s", err) } - if config.AtlasCluster != "hashicorp/prod" { + if config.AtlasInfrastructure != "hashicorp/prod" { t.Fatalf("bad: %#v", config) } if config.AtlasToken != "abcdefg" { @@ -1116,10 +1116,10 @@ func TestMergeConfig(t *testing.T) { Perms: "0700", }, }, - AtlasCluster: "hashicorp/prod", - AtlasToken: "123456789", - AtlasACLToken: "abcdefgh", - AtlasJoin: true, + AtlasInfrastructure: "hashicorp/prod", + AtlasToken: "123456789", + AtlasACLToken: "abcdefgh", + AtlasJoin: true, } c := MergeConfig(a, b) diff --git a/command/agent/scada.go b/command/agent/scada.go index 7fb8c42f9..1805611a2 100644 --- a/command/agent/scada.go +++ b/command/agent/scada.go @@ -47,7 +47,7 @@ func ProviderConfig(c *Config) *client.ProviderConfig { Handlers: map[string]client.CapabilityProvider{ "http": nil, }, - ResourceGroup: c.AtlasCluster, + ResourceGroup: c.AtlasInfrastructure, Token: c.AtlasToken, } } @@ -67,7 +67,7 @@ func NewProvider(c *Config, logOutput io.Writer) (*client.Provider, net.Listener // TODO: AtlasACLToken // Create an HTTP listener and handler - list := newScadaListener(c.AtlasCluster) + list := newScadaListener(c.AtlasInfrastructure) config.Handlers["http"] = func(capability string, meta map[string]string, conn io.ReadWriteCloser) error { return list.PushRWC(conn) From 6ee6f676956e5311ea22a96200563f5798c69cdc Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Fri, 6 Feb 2015 14:10:01 -0800 Subject: [PATCH 11/23] agent: Use AtlasACLToken --- command/agent/http.go | 22 +++++++++++++++++++--- command/agent/scada.go | 12 +++++++----- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/command/agent/http.go b/command/agent/http.go index bb46787b2..85ea3c6c9 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -19,6 +19,14 @@ import ( "github.com/mitchellh/mapstructure" ) +var ( + // scadaHTTPAddr is the address associated with the + // HTTPServer. When populating an ACL token for a request, + // this is checked to switch between the ACLToken and + // AtlasACLToken + scadaHTTPAddr = "SCADA" +) + // HTTPServer is used to wrap an Agent and expose various API's // in a RESTful manner type HTTPServer struct { @@ -144,7 +152,7 @@ func NewHTTPServers(agent *Agent, config *Config, scada net.Listener, logOutput listener: scada, logger: log.New(logOutput, "", log.LstdFlags), uiDir: config.UiDir, - addr: "SCADA", + addr: scadaHTTPAddr, } srv.registerHandlers(false) // Never allow debug for SCADA @@ -439,9 +447,17 @@ func (s *HTTPServer) parseDC(req *http.Request, dc *string) { func (s *HTTPServer) parseToken(req *http.Request, token *string) { if other := req.URL.Query().Get("token"); other != "" { *token = other - } else if *token == "" { - *token = s.agent.config.ACLToken + return } + + // Set the AtlasACLToken if SCADA + if s.addr == scadaHTTPAddr && s.agent.config.AtlasACLToken != "" { + *token = s.agent.config.AtlasACLToken + return + } + + // Set the default ACLToken + *token = s.agent.config.ACLToken } // parse is a convenience method for endpoints that need diff --git a/command/agent/scada.go b/command/agent/scada.go index 1805611a2..a7929a552 100644 --- a/command/agent/scada.go +++ b/command/agent/scada.go @@ -7,6 +7,7 @@ import ( "io" "log" "net" + "os" "strconv" "sync" "time" @@ -59,13 +60,14 @@ func NewProvider(c *Config, logOutput io.Writer) (*client.Provider, net.Listener config := ProviderConfig(c) config.Logger = log.New(logOutput, "", log.LstdFlags) - // TODO: REMOVE - config.TLSConfig = &tls.Config{ - InsecureSkipVerify: true, + // SCADA_INSECURE env variable is used for testing to disable + // TLS certificate verification. + if os.Getenv("SCADA_INSECURE") != "" { + config.TLSConfig = &tls.Config{ + InsecureSkipVerify: true, + } } - // TODO: AtlasACLToken - // Create an HTTP listener and handler list := newScadaListener(c.AtlasInfrastructure) config.Handlers["http"] = func(capability string, meta map[string]string, From 8fb86d14abd58bff988173980b16c1e5ba34c972 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Fri, 6 Feb 2015 14:27:11 -0800 Subject: [PATCH 12/23] agent: Adding SCADA tests --- command/agent/scada.go | 8 +-- command/agent/scada_test.go | 104 ++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 command/agent/scada_test.go diff --git a/command/agent/scada.go b/command/agent/scada.go index a7929a552..25da9e78c 100644 --- a/command/agent/scada.go +++ b/command/agent/scada.go @@ -96,9 +96,9 @@ type scadaListener struct { } // newScadaListener returns a new listener -func newScadaListener(cluster string) *scadaListener { +func newScadaListener(infra string) *scadaListener { l := &scadaListener{ - addr: &scadaAddr{cluster}, + addr: &scadaAddr{infra}, pending: make(chan net.Conn), closedCh: make(chan struct{}), } @@ -155,7 +155,7 @@ func (s *scadaListener) Addr() net.Addr { // scadaAddr is used to return a net.Addr for SCADA type scadaAddr struct { - cluster string + infra string } func (s *scadaAddr) Network() string { @@ -163,7 +163,7 @@ func (s *scadaAddr) Network() string { } func (s *scadaAddr) String() string { - return fmt.Sprintf("SCADA::Atlas::%s", s.cluster) + return fmt.Sprintf("SCADA::Atlas::%s", s.infra) } type scadaRWC struct { diff --git a/command/agent/scada_test.go b/command/agent/scada_test.go new file mode 100644 index 000000000..e142f54ae --- /dev/null +++ b/command/agent/scada_test.go @@ -0,0 +1,104 @@ +package agent + +import ( + "net" + "reflect" + "testing" + + "github.com/hashicorp/scada-client" +) + +func TestProviderService(t *testing.T) { + conf := DefaultConfig() + conf.Version = "0.5.0" + conf.VersionPrerelease = "rc1" + conf.AtlasJoin = true + conf.Server = true + ps := ProviderService(conf) + + expect := &client.ProviderService{ + Service: "consul", + ServiceVersion: "0.5.0rc1", + Capabilities: map[string]int{ + "http": 1, + }, + Meta: map[string]string{ + "auto-join": "true", + "datacenter": "dc1", + "server": "true", + }, + ResourceType: "infrastructures", + } + + if !reflect.DeepEqual(ps, expect) { + t.Fatalf("bad: %v", ps) + } +} + +func TestProviderConfig(t *testing.T) { + conf := DefaultConfig() + conf.Version = "0.5.0" + conf.VersionPrerelease = "rc1" + conf.AtlasJoin = true + conf.Server = true + conf.AtlasInfrastructure = "armon/test" + conf.AtlasToken = "foobarbaz" + pc := ProviderConfig(conf) + + expect := &client.ProviderConfig{ + Service: &client.ProviderService{ + Service: "consul", + ServiceVersion: "0.5.0rc1", + Capabilities: map[string]int{ + "http": 1, + }, + Meta: map[string]string{ + "auto-join": "true", + "datacenter": "dc1", + "server": "true", + }, + ResourceType: "infrastructures", + }, + Handlers: map[string]client.CapabilityProvider{ + "http": nil, + }, + ResourceGroup: "armon/test", + Token: "foobarbaz", + } + + if !reflect.DeepEqual(pc, expect) { + t.Fatalf("bad: %v", pc) + } +} + +func TestSCADAListener(t *testing.T) { + list := newScadaListener("armon/test") + defer list.Close() + + var raw interface{} = list + _, ok := raw.(net.Listener) + if !ok { + t.Fatalf("bad") + } + + a, b := net.Pipe() + defer a.Close() + defer b.Close() + + go list.Push(a) + out, err := list.Accept() + if err != nil { + t.Fatalf("err: %v", err) + } + if out != a { + t.Fatalf("bad") + } +} + +func TestSCADAAddr(t *testing.T) { + var addr interface{} = &scadaAddr{"armon/test"} + _, ok := addr.(net.Addr) + if !ok { + t.Fatalf("bad") + } +} From 0c2951c5f92af2ba8433a372190881ef77b1008b Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Fri, 6 Feb 2015 14:38:01 -0800 Subject: [PATCH 13/23] agent: Test ACL token resolution --- command/agent/http_test.go | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/command/agent/http_test.go b/command/agent/http_test.go index ae0ef64de..a6ec471d5 100644 --- a/command/agent/http_test.go +++ b/command/agent/http_test.go @@ -429,6 +429,67 @@ func TestParseConsistency_Invalid(t *testing.T) { } } +// Test ACL token is resolved in correct order +func TestACLResolution(t *testing.T) { + var token string + // Request without token + req, err := http.NewRequest("GET", + "/v1/catalog/nodes", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Request with explicit token + reqToken, err := http.NewRequest("GET", + "/v1/catalog/nodes?token=foo", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + httpTest(t, func(srv *HTTPServer) { + // Check when no token is set + srv.agent.config.ACLToken = "" + srv.parseToken(req, &token) + if token != "" { + t.Fatalf("bad: %s", token) + } + + // Check when ACLToken set + srv.agent.config.ACLToken = "agent" + srv.parseToken(req, &token) + if token != "agent" { + t.Fatalf("bad: %s", token) + } + + // Check when AtlasACLToken set, wrong server + srv.agent.config.AtlasACLToken = "atlas" + srv.parseToken(req, &token) + if token != "agent" { + t.Fatalf("bad: %s", token) + } + + // Check when AtlasACLToken set, correct server + srv.addr = scadaHTTPAddr + srv.parseToken(req, &token) + if token != "atlas" { + t.Fatalf("bad: %s", token) + } + + // Check when AtlasACLToken not, correct server + srv.agent.config.AtlasACLToken = "" + srv.parseToken(req, &token) + if token != "agent" { + t.Fatalf("bad: %s", token) + } + + // Explicit token has highest precedence + srv.parseToken(reqToken, &token) + if token != "foo" { + t.Fatalf("bad: %s", token) + } + }) +} + // assertIndex tests that X-Consul-Index is set and non-zero func assertIndex(t *testing.T, resp *httptest.ResponseRecorder) { header := resp.Header().Get("X-Consul-Index") From 441b94028072de3ba9cbce51fb90d67bde6ef938 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Fri, 6 Feb 2015 14:52:45 -0800 Subject: [PATCH 14/23] website: Document new options --- .../source/docs/agent/options.html.markdown | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/website/source/docs/agent/options.html.markdown b/website/source/docs/agent/options.html.markdown index 43f9dc55b..0e0f132d8 100644 --- a/website/source/docs/agent/options.html.markdown +++ b/website/source/docs/agent/options.html.markdown @@ -40,6 +40,17 @@ The options below are all specified on the command-line. If this address is not routable, the node will be in a constant flapping state as other nodes will treat the non-routability as a failure. +* `-atlas` - This flag enables [Atlas](https://atlas.hashicorp.com) integration. + It is used to provide the Atlas infrastructure name and the SCADA connection. + This enables Atlas features such as the dashboard and node auto joining. + +* `-atlas-join` - When set, enables auto-join via Atlas. Atlas will track the most + recent members to join the infrastructure named by `-atlas` and automatically + join them on start. For servers, the LAN and WAN pool are both joined. + +* `-atlas-token` - Provides the Atlas API authentication token. This can also be provided + using the `ATLAS_TOKEN` environment variable. Required for use with Atlas. + * `-bootstrap` - This flag is used to control if a server is in "bootstrap" mode. It is important that no more than one server *per* data center be running in this mode. Technically, a server in bootstrap mode is allowed to self-elect as the Raft leader. It is important that only a single node is in this mode; @@ -260,6 +271,16 @@ definitions support being updated during a reload. * `advertise_addr` - Equivalent to the [`-advertise` command-line flag](#advertise). +* `atlas_acl_token` - When provided, any requests made by Atlas will use this ACL + token unless explicitly overriden. When not provided the `acl_token` is used. + This can be set to 'anonymous' to reduce permission below that of `acl_token`. + +* `atlas_infrastructure` - Equivalent to the [`-atlas` command-line flag](#atlas). + +* `atlas_join` - Equivalent to the [`-atlas-join` command-line flag](#atlas_join). + +* `atlas_token` - Equivalent to the [`-atlas-token` command-line flag](#atlas_token). + * `bootstrap` - Equivalent to the [`-bootstrap` command-line flag](#bootstrap_anchor). * `bootstrap_expect` - Equivalent to the [`-bootstrap-expect` command-line flag](#bootstrap_expect). From 52bdbdade9212149e94b9cde9ecb10233f2d92d0 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Fri, 6 Feb 2015 16:34:07 -0800 Subject: [PATCH 15/23] website: Adding Atlas docs --- .../source/docs/agent/basics.html.markdown | 6 ++ website/source/docs/faq.html.markdown | 12 ++++ .../source/docs/guides/atlas.html.markdown | 59 +++++++++++++++++++ .../source/docs/guides/index.html.markdown | 2 + website/source/layouts/docs.erb | 6 +- 5 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 website/source/docs/guides/atlas.html.markdown diff --git a/website/source/docs/agent/basics.html.markdown b/website/source/docs/agent/basics.html.markdown index 3aff65da9..d520b5533 100644 --- a/website/source/docs/agent/basics.html.markdown +++ b/website/source/docs/agent/basics.html.markdown @@ -39,6 +39,7 @@ $ consul agent -data-dir=/tmp/consul Server: false (bootstrap: false) Client Addr: 127.0.0.1 (HTTP: 8500, DNS: 8600, RPC: 8400) Cluster Addr: 192.168.1.43 (LAN: 8301, WAN: 8302) + Atlas: (Infrastructure: 'hashicorp/test' Join: true) ==> Log data will now stream in as it occurs: @@ -75,6 +76,11 @@ There are several important messages that `consul agent` outputs: Consul agents in a cluster. Not all Consul agents in a cluster have to use the same port, but this address **MUST** be reachable by all other nodes. +* **Atlas**: This shows the [Atlas infrastructure](https://atlas.hashicorp.com) + the node is registered with. It also indicates if auto join is enabled. + The Atlas infrastructure is set using `-atlas` and auto-join is enabled by + setting `-atlas-join`. + ## Stopping an Agent An agent can be stopped in two ways: gracefully or forcefully. To gracefully diff --git a/website/source/docs/faq.html.markdown b/website/source/docs/faq.html.markdown index 56fdd67f5..797ec064a 100644 --- a/website/source/docs/faq.html.markdown +++ b/website/source/docs/faq.html.markdown @@ -28,4 +28,16 @@ and can be disabled. See [`disable_anonymous_signature`](/docs/agent/options.html#disable_anonymous_signature) and [`disable_update_check`](/docs/agent/options.html#disable_update_check). +## Q: How does Atlas integration work? / Does Consul call home? + +Consul makes use of a HashiCorp service called [SCADA](http://scada.hashicorp.com) +which stands for Supervisory Control And Data Acquisition. The SCADA system allows +clients to maintain a long-running connection to Atlas which is used to make requests +to Consul agents for features like the dashboard and auto joining. Standard ACLs can +be applied to the SCADA connection, which has no enhanced or elevated privileges. +Using the SCADA service is optional and only enabled by opt-in. + +See [`atlas_infrastructure`](/docs/agent/options.html#_atlas) +and [`atlas_acl_token`](/docs/agent/options.html#atlas_acl_token). + diff --git a/website/source/docs/guides/atlas.html.markdown b/website/source/docs/guides/atlas.html.markdown new file mode 100644 index 000000000..86e76cac8 --- /dev/null +++ b/website/source/docs/guides/atlas.html.markdown @@ -0,0 +1,59 @@ +--- +layout: "docs" +page_title: "Atlas Integration" +sidebar_current: "docs-guides-atlas" +description: |- + This guide covers how to integrate Atlas with Consul to provide features like an infrastructure dashboard and automatic cluster joining. +--- + +# Atlas Integration + +[Atlas](https://atlas.hashicorp.com) is service provided by HashiCorp to deploy applications and manage infrastructure. +Starting with Consul 0.5, it is possible to integrate Consul with Atlas. This is done by registering a node as part +of an Atlas infrastructure (specified with the `-atlas` flag). Consul maintains a long running connection to the +[SCADA](http://scada.hashicorp.com) service which allows Atlas to retrieve data and control nodes. + +Data acquisition allows Atlas to display the state of the Consul cluster in its dashboard as well as enabling +alerts to be setup using health checks. Remote control enables Atlas to provide features like the auto joinining +nodes. + +## Enabling Atlas Integration + +To enable Atlas integration, you must specify the name of the Atlas infrastructure and the Atlas authentication +token. The Atlas infrastructure name can be set either with the `-atlas` CLI flag, or with the `atlas_infrastructure` +[configuration option](/docs/agent/options.html). The Atlas token is set with the `-atlas-token` CLI flag, `atlas_token` +configuration option, or `ATLAS_TOKEN` environment variable. + +To verify the integration, either run the agent with `debug` level logging or use `consul monitor -log-level=debug` +and look for a line like: + + [DEBUG] scada-client: assigned session '406ca55d-1801-f964-2942-45f5f9df3995' + +This shows that the Consul agent was successfully able to register with the SCADA service. + +## Using Auto-Join + +Once integrated with Atlas, the auto join feature can be used to have nodes automatically join other +peers in their datacenter. Server nodes will automatically join peer LAN nodes and other WAN nodes. +Client nodes will only join other LAN nodes in their datacenter. + +Auto join is enabled with the `-atlas-join` CLI flag or the `atlas_join` configuration option. + +## Securing Atlas + +The connection to Atlas does not have elevated privileges. API requests made by Atlas +are served in the same way any other HTTP request is made. If ACLs are enabled, it is possible to +force an Atlas ACL token to be used instead of the agent's default token. + +When ACLs are enabled, the `atlas_acl_token` configuration option can be specified. This changes +the ACL token resolution order to be: + +1. Request specific token provided by `?token=`. These tokens are set in the Atlas UI. +2. The `atlas_acl_token` if configured. +3. The `acl_token` if configured. +4. The `anonymous` token. + +Because the `acl_token` typically has elevated permissions compared to the `anonymous` token, +the `atlas_acl_token` can be set to `anonymous` to drop privileges that would otherwise be +inherited from the agent. + diff --git a/website/source/docs/guides/index.html.markdown b/website/source/docs/guides/index.html.markdown index 6e618b048..c1a8f946b 100644 --- a/website/source/docs/guides/index.html.markdown +++ b/website/source/docs/guides/index.html.markdown @@ -14,6 +14,8 @@ guidance to do them safely. The following guides are available: +* [Atlas Integration](/docs/guides/atlas.html) - This guide covers how to integrate [Atlas](https://atlas.hashicorp.com) with Consul. + * [Adding/Removing Servers](/docs/guides/servers.html) - This guide covers how to safely add and remove Consul servers from the cluster. This should be done carefully to avoid availability outages. * [Bootstrapping](/docs/guides/bootstrapping.html) - This guide covers bootstrapping a new datacenter. This covers safely adding the initial Consul servers. diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 7b99f15fa..1da41cd05 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -196,7 +196,11 @@ > Guides -