agent: /v1/connect/ca/configuration PUT for setting configuration

This commit is contained in:
Mitchell Hashimoto 2018-03-21 12:42:42 -07:00
parent 1c3dbc83ff
commit 63d674d07d
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
4 changed files with 127 additions and 0 deletions

View File

@ -1,6 +1,7 @@
package agent package agent
import ( import (
"fmt"
"net/http" "net/http"
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
@ -26,3 +27,30 @@ func (s *HTTPServer) ConnectCARoots(resp http.ResponseWriter, req *http.Request)
return reply, nil 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
}

View File

@ -14,6 +14,9 @@ import (
"github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/consul/state"
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/go-memdb" "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. // ConnectCA manages the Connect CA.
@ -22,6 +25,90 @@ type ConnectCA struct {
srv *Server 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. // Roots returns the currently trusted root certificates.
func (s *ConnectCA) Roots( func (s *ConnectCA) Roots(
args *structs.DCSpecificRequest, args *structs.DCSpecificRequest,

View File

@ -42,6 +42,7 @@ func init() {
registerEndpoint("/v1/catalog/services", []string{"GET"}, (*HTTPServer).CatalogServices) registerEndpoint("/v1/catalog/services", []string{"GET"}, (*HTTPServer).CatalogServices)
registerEndpoint("/v1/catalog/service/", []string{"GET"}, (*HTTPServer).CatalogServiceNodes) registerEndpoint("/v1/catalog/service/", []string{"GET"}, (*HTTPServer).CatalogServiceNodes)
registerEndpoint("/v1/catalog/node/", []string{"GET"}, (*HTTPServer).CatalogNodeServices) 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/ca/roots", []string{"GET"}, (*HTTPServer).ConnectCARoots)
registerEndpoint("/v1/connect/intentions", []string{"GET", "POST"}, (*HTTPServer).IntentionEndpoint) registerEndpoint("/v1/connect/intentions", []string{"GET", "POST"}, (*HTTPServer).IntentionEndpoint)
registerEndpoint("/v1/connect/intentions/match", []string{"GET"}, (*HTTPServer).IntentionMatch) registerEndpoint("/v1/connect/intentions/match", []string{"GET"}, (*HTTPServer).IntentionMatch)

View File

@ -113,3 +113,14 @@ type CARequest struct {
// always be active. // always be active.
Roots []*CARoot 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{}
}