From 63d674d07d368d5b5bda0a51e9de6c60171977e2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 21 Mar 2018 12:42:42 -0700 Subject: [PATCH] agent: /v1/connect/ca/configuration PUT for setting configuration --- agent/connect_ca_endpoint.go | 28 ++++++++++ agent/consul/connect_ca_endpoint.go | 87 +++++++++++++++++++++++++++++ agent/http_oss.go | 1 + agent/structs/connect_ca.go | 11 ++++ 4 files changed, 127 insertions(+) diff --git a/agent/connect_ca_endpoint.go b/agent/connect_ca_endpoint.go index 1c78710150..7832ba36f1 100644 --- a/agent/connect_ca_endpoint.go +++ b/agent/connect_ca_endpoint.go @@ -1,6 +1,7 @@ package agent import ( + "fmt" "net/http" "github.com/hashicorp/consul/agent/structs" @@ -26,3 +27,30 @@ func (s *HTTPServer) ConnectCARoots(resp http.ResponseWriter, req *http.Request) return reply, nil } + +// /v1/connect/ca/configuration +func (s *HTTPServer) ConnectCAConfiguration(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + switch req.Method { + case "PUT": + return s.ConnectCAConfigurationSet(resp, req) + + default: + return nil, MethodNotAllowedError{req.Method, []string{"GET", "POST"}} + } +} + +// PUT /v1/connect/ca/configuration +func (s *HTTPServer) ConnectCAConfigurationSet(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + // Method is tested in ConnectCAConfiguration + + var args structs.CAConfiguration + if err := decodeBody(req, &args, nil); err != nil { + resp.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(resp, "Request decode failed: %v", err) + return nil, nil + } + + var reply interface{} + err := s.agent.RPC("ConnectCA.ConfigurationSet", &args, &reply) + return nil, err +} diff --git a/agent/consul/connect_ca_endpoint.go b/agent/consul/connect_ca_endpoint.go index 60e631cefc..a4cb569d8b 100644 --- a/agent/consul/connect_ca_endpoint.go +++ b/agent/consul/connect_ca_endpoint.go @@ -14,6 +14,9 @@ import ( "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/go-memdb" + "github.com/hashicorp/go-uuid" + "github.com/mitchellh/go-testing-interface" + "github.com/mitchellh/mapstructure" ) // ConnectCA manages the Connect CA. @@ -22,6 +25,90 @@ type ConnectCA struct { srv *Server } +// ConfigurationSet updates the configuration for the CA. +// +// NOTE(mitchellh): This whole implementation is temporary until the real +// CA plugin work comes in. For now, this is only used to configure a single +// static CA root. +func (s *ConnectCA) ConfigurationSet( + args *structs.CAConfiguration, + reply *interface{}) error { + // NOTE(mitchellh): This is the temporary hardcoding of a static CA + // provider. This will allow us to test agent implementations and so on + // with an incomplete CA for now. + if args.Provider != "static" { + return fmt.Errorf("The CA provider can only be 'static' for now") + } + + // Config is the configuration allowed for our static provider + var config struct { + Name string + CertPEM string + PrivateKeyPEM string + Generate bool + } + if err := mapstructure.Decode(args.Config, &config); err != nil { + return fmt.Errorf("error decoding config: %s", err) + } + + // Basic validation so demos aren't super jank + if config.Name == "" { + return fmt.Errorf("Name must be set") + } + if config.CertPEM == "" || config.PrivateKeyPEM == "" { + if !config.Generate { + return fmt.Errorf( + "CertPEM and PrivateKeyPEM must be set, or Generate must be true") + } + } + + // Convenience to auto-generate the cert + if config.Generate { + ca := connect.TestCA(&testing.RuntimeT{}, nil) + config.CertPEM = ca.RootCert + config.PrivateKeyPEM = ca.SigningKey + } + + // TODO(mitchellh): verify that the private key is valid for the cert + + // Generate an ID for this + id, err := uuid.GenerateUUID() + if err != nil { + return err + } + + // Get the highest index + state := s.srv.fsm.State() + idx, _, err := state.CARoots(nil) + if err != nil { + return err + } + + // Commit + resp, err := s.srv.raftApply(structs.ConnectCARequestType, &structs.CARequest{ + Op: structs.CAOpSet, + Index: idx, + Roots: []*structs.CARoot{ + &structs.CARoot{ + ID: id, + Name: config.Name, + RootCert: config.CertPEM, + SigningKey: config.PrivateKeyPEM, + Active: true, + }, + }, + }) + if err != nil { + s.srv.logger.Printf("[ERR] consul.test: Apply failed %v", err) + return err + } + if respErr, ok := resp.(error); ok { + return respErr + } + + return nil +} + // Roots returns the currently trusted root certificates. func (s *ConnectCA) Roots( args *structs.DCSpecificRequest, diff --git a/agent/http_oss.go b/agent/http_oss.go index d2e86622f8..6c2e697eaa 100644 --- a/agent/http_oss.go +++ b/agent/http_oss.go @@ -42,6 +42,7 @@ func init() { registerEndpoint("/v1/catalog/services", []string{"GET"}, (*HTTPServer).CatalogServices) registerEndpoint("/v1/catalog/service/", []string{"GET"}, (*HTTPServer).CatalogServiceNodes) registerEndpoint("/v1/catalog/node/", []string{"GET"}, (*HTTPServer).CatalogNodeServices) + registerEndpoint("/v1/connect/ca/configuration", []string{"PUT"}, (*HTTPServer).ConnectCAConfiguration) registerEndpoint("/v1/connect/ca/roots", []string{"GET"}, (*HTTPServer).ConnectCARoots) registerEndpoint("/v1/connect/intentions", []string{"GET", "POST"}, (*HTTPServer).IntentionEndpoint) registerEndpoint("/v1/connect/intentions/match", []string{"GET"}, (*HTTPServer).IntentionMatch) diff --git a/agent/structs/connect_ca.go b/agent/structs/connect_ca.go index 6dc2dbf30e..8576a1b418 100644 --- a/agent/structs/connect_ca.go +++ b/agent/structs/connect_ca.go @@ -113,3 +113,14 @@ type CARequest struct { // always be active. Roots []*CARoot } + +// CAConfiguration is the configuration for the current CA plugin. +type CAConfiguration struct { + // Provider is the CA provider implementation to use. + Provider string + + // Configuration is arbitrary configuration for the provider. This + // should only contain primitive values and containers (such as lists + // and maps). + Config map[string]interface{} +}