diff --git a/api/operator.go b/api/operator.go index 9df13d5d4352..502f3704e15c 100644 --- a/api/operator.go +++ b/api/operator.go @@ -43,8 +43,8 @@ type RaftConfiguration struct { Index uint64 } -// KeyringOpts is used for performing Keyring operations -type KeyringOpts struct { +// keyringRequest is used for performing Keyring operations +type keyringRequest struct { Key string `json:",omitempty"` } @@ -101,9 +101,10 @@ func (op *Operator) RaftRemovePeerByAddress(address string, q *WriteOptions) err } // KeyringInstall is used to install a new gossip encryption key into the cluster -func (op *Operator) KeyringInstall(key string) error { - r := op.c.newRequest("PUT", "/v1/operator/keyring/install") - r.obj = KeyringOpts{ +func (op *Operator) KeyringInstall(key string, q *WriteOptions) error { + r := op.c.newRequest("POST", "/v1/operator/keyring") + r.setWriteOptions(q) + r.obj = keyringRequest{ Key: key, } _, resp, err := requireOK(op.c.doRequest(r)) @@ -115,8 +116,9 @@ func (op *Operator) KeyringInstall(key string) error { } // KeyringList is used to list the gossip keys installed in the cluster -func (op *Operator) KeyringList() ([]*KeyringResponse, error) { - r := op.c.newRequest("GET", "/v1/operator/keyring/list") +func (op *Operator) KeyringList(q *QueryOptions) ([]*KeyringResponse, error) { + r := op.c.newRequest("GET", "/v1/operator/keyring") + r.setQueryOptions(q) _, resp, err := requireOK(op.c.doRequest(r)) if err != nil { return nil, err @@ -131,9 +133,10 @@ func (op *Operator) KeyringList() ([]*KeyringResponse, error) { } // KeyringRemove is used to remove a gossip encryption key from the cluster -func (op *Operator) KeyringRemove(key string) error { - r := op.c.newRequest("DELETE", "/v1/operator/keyring/remove") - r.obj = KeyringOpts{ +func (op *Operator) KeyringRemove(key string, q *WriteOptions) error { + r := op.c.newRequest("DELETE", "/v1/operator/keyring") + r.setWriteOptions(q) + r.obj = keyringRequest{ Key: key, } _, resp, err := requireOK(op.c.doRequest(r)) @@ -145,9 +148,10 @@ func (op *Operator) KeyringRemove(key string) error { } // KeyringUse is used to change the active gossip encryption key -func (op *Operator) KeyringUse(key string) error { - r := op.c.newRequest("PUT", "/v1/operator/keyring/use") - r.obj = KeyringOpts{ +func (op *Operator) KeyringUse(key string, q *WriteOptions) error { + r := op.c.newRequest("PUT", "/v1/operator/keyring") + r.setWriteOptions(q) + r.obj = keyringRequest{ Key: key, } _, resp, err := requireOK(op.c.doRequest(r)) diff --git a/api/operator_test.go b/api/operator_test.go index 9fd1b5d5c901..e55495e2e95e 100644 --- a/api/operator_test.go +++ b/api/operator_test.go @@ -49,11 +49,11 @@ func TestOperator_KeyringInstallListPutRemove(t *testing.T) { defer s.Stop() operator := c.Operator() - if err := operator.KeyringInstall(newKey); err != nil { + if err := operator.KeyringInstall(newKey, nil); err != nil { t.Fatalf("err: %v", err) } - listResponses, err := operator.KeyringList() + listResponses, err := operator.KeyringList(nil) if err != nil { t.Fatalf("err %v", err) } @@ -75,15 +75,15 @@ func TestOperator_KeyringInstallListPutRemove(t *testing.T) { } // Switch the primary to the new key - if err := operator.KeyringUse(newKey); err != nil { + if err := operator.KeyringUse(newKey, nil); err != nil { t.Fatalf("err: %v", err) } - if err := operator.KeyringRemove(oldKey); err != nil { + if err := operator.KeyringRemove(oldKey, nil); err != nil { t.Fatalf("err: %v", err) } - listResponses, err = operator.KeyringList() + listResponses, err = operator.KeyringList(nil) if err != nil { t.Fatalf("err %v", err) } diff --git a/command/agent/http.go b/command/agent/http.go index 6de095d5fee0..9d210fc2abd6 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -291,10 +291,7 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) { s.handleFuncMetrics("/v1/kv/", s.wrap(s.KVSEndpoint)) s.handleFuncMetrics("/v1/operator/raft/configuration", s.wrap(s.OperatorRaftConfiguration)) s.handleFuncMetrics("/v1/operator/raft/peer", s.wrap(s.OperatorRaftPeer)) - s.handleFuncMetrics("/v1/operator/keyring/install", s.wrap(s.OperatorKeyringInstall)) - s.handleFuncMetrics("/v1/operator/keyring/list", s.wrap(s.OperatorKeyringList)) - s.handleFuncMetrics("/v1/operator/keyring/remove", s.wrap(s.OperatorKeyringRemove)) - s.handleFuncMetrics("/v1/operator/keyring/use", s.wrap(s.OperatorKeyringUse)) + s.handleFuncMetrics("/v1/operator/keyring", s.wrap(s.OperatorKeyringEndpoint)) s.handleFuncMetrics("/v1/query", s.wrap(s.PreparedQueryGeneral)) s.handleFuncMetrics("/v1/query/", s.wrap(s.PreparedQuerySpecific)) s.handleFuncMetrics("/v1/session/create", s.wrap(s.SessionCreate)) diff --git a/command/agent/operator_endpoint.go b/command/agent/operator_endpoint.go index 93d1d197288d..ac5377a614dd 100644 --- a/command/agent/operator_endpoint.go +++ b/command/agent/operator_endpoint.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/hashicorp/consul/consul/structs" + multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/raft" ) @@ -57,109 +58,85 @@ func (s *HTTPServer) OperatorRaftPeer(resp http.ResponseWriter, req *http.Reques return nil, nil } -// OperatorKeyringInstall is used to install a new gossip encryption key into the cluster -func (s *HTTPServer) OperatorKeyringInstall(resp http.ResponseWriter, req *http.Request) (interface{}, error) { - if req.Method != "PUT" { - resp.WriteHeader(http.StatusMethodNotAllowed) - return nil, nil +type keyringArgs struct { + Key string + Token string +} + +// OperatorKeyringEndpoint handles keyring operations (install, list, use, remove) +func (s *HTTPServer) OperatorKeyringEndpoint(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + var args keyringArgs + if req.Method == "POST" || req.Method == "PUT" || req.Method == "DELETE" { + if err := decodeBody(req, &args, nil); err != nil { + resp.WriteHeader(400) + resp.Write([]byte(fmt.Sprintf("Request decode failed: %v", err))) + return nil, nil + } } + s.parseToken(req, &args.Token) - var args structs.KeyringRequest - if err := decodeBody(req, &args, nil); err != nil { - resp.WriteHeader(400) - resp.Write([]byte(fmt.Sprintf("Request decode failed: %v", err))) + // Switch on the method + switch req.Method { + case "GET": + return s.KeyringList(resp, req, &args) + case "POST": + return s.KeyringInstall(resp, req, &args) + case "PUT": + return s.KeyringUse(resp, req, &args) + case "DELETE": + return s.KeyringRemove(resp, req, &args) + default: + resp.WriteHeader(405) return nil, nil } - s.parseToken(req, &args.Token) +} +// KeyringInstall is used to install a new gossip encryption key into the cluster +func (s *HTTPServer) KeyringInstall(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) { responses, err := s.agent.InstallKey(args.Key, args.Token) if err != nil { return nil, err } - for _, response := range responses.Responses { - if response.Error != "" { - return nil, fmt.Errorf(response.Error) - } - } - return nil, nil + return nil, keyringErrorsOrNil(responses.Responses) } -// OperatorKeyringList is used to list the keys installed in the cluster -func (s *HTTPServer) OperatorKeyringList(resp http.ResponseWriter, req *http.Request) (interface{}, error) { - if req.Method != "GET" { - resp.WriteHeader(http.StatusMethodNotAllowed) - return nil, nil - } - - var token string - s.parseToken(req, &token) - - responses, err := s.agent.ListKeys(token) +// KeyringList is used to list the keys installed in the cluster +func (s *HTTPServer) KeyringList(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) { + responses, err := s.agent.ListKeys(args.Token) if err != nil { return nil, err } - for _, response := range responses.Responses { - if response.Error != "" { - return nil, fmt.Errorf(response.Error) - } - } - return responses.Responses, nil + return responses.Responses, keyringErrorsOrNil(responses.Responses) } -// OperatorKeyringRemove is used to list the keys installed in the cluster -func (s *HTTPServer) OperatorKeyringRemove(resp http.ResponseWriter, req *http.Request) (interface{}, error) { - if req.Method != "DELETE" { - resp.WriteHeader(http.StatusMethodNotAllowed) - return nil, nil - } - - var args structs.KeyringRequest - if err := decodeBody(req, &args, nil); err != nil { - resp.WriteHeader(400) - resp.Write([]byte(fmt.Sprintf("Request decode failed: %v", err))) - return nil, nil - } - s.parseToken(req, &args.Token) - +// KeyringRemove is used to list the keys installed in the cluster +func (s *HTTPServer) KeyringRemove(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) { responses, err := s.agent.RemoveKey(args.Key, args.Token) if err != nil { return nil, err } - for _, response := range responses.Responses { - if response.Error != "" { - return nil, fmt.Errorf(response.Error) - } - } - return nil, nil + return nil, keyringErrorsOrNil(responses.Responses) } -// OperatorKeyringUse is used to change the primary gossip encryption key -func (s *HTTPServer) OperatorKeyringUse(resp http.ResponseWriter, req *http.Request) (interface{}, error) { - if req.Method != "PUT" { - resp.WriteHeader(http.StatusMethodNotAllowed) - return nil, nil - } - - var args structs.KeyringRequest - if err := decodeBody(req, &args, nil); err != nil { - resp.WriteHeader(400) - resp.Write([]byte(fmt.Sprintf("Request decode failed: %v", err))) - return nil, nil - } - s.parseToken(req, &args.Token) - +// KeyringUse is used to change the primary gossip encryption key +func (s *HTTPServer) KeyringUse(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) { responses, err := s.agent.UseKey(args.Key, args.Token) if err != nil { return nil, err } - for _, response := range responses.Responses { + + return nil, keyringErrorsOrNil(responses.Responses) +} + +func keyringErrorsOrNil(responses []*structs.KeyringResponse) error { + var errs error + for _, response := range responses { if response.Error != "" { - return nil, fmt.Errorf(response.Error) + errs = multierror.Append(errs, fmt.Errorf(response.Error)) } } - - return nil, nil + return errs } diff --git a/command/agent/operator_endpoint_test.go b/command/agent/operator_endpoint_test.go index 8bac5858c8ad..9aff08a4b2b3 100644 --- a/command/agent/operator_endpoint_test.go +++ b/command/agent/operator_endpoint_test.go @@ -66,13 +66,13 @@ func TestOperator_KeyringInstall(t *testing.T) { } httpTestWithConfig(t, func(srv *HTTPServer) { body := bytes.NewBufferString(fmt.Sprintf("{\"Key\":\"%s\"}", newKey)) - req, err := http.NewRequest("PUT", "/v1/operator/keyring/install", body) + req, err := http.NewRequest("POST", "/v1/operator/keyring", body) if err != nil { t.Fatalf("err: %v", err) } resp := httptest.NewRecorder() - _, err = srv.OperatorKeyringInstall(resp, req) + _, err = srv.OperatorKeyringEndpoint(resp, req) if err != nil { t.Fatalf("err: %s", err) } @@ -81,6 +81,9 @@ func TestOperator_KeyringInstall(t *testing.T) { if err != nil { t.Fatalf("err: %s", err) } + if len(listResponse.Responses) != 2 { + t.Fatalf("bad: %d", len(listResponse.Responses)) + } for _, response := range listResponse.Responses { count, ok := response.Keys[newKey] @@ -100,13 +103,13 @@ func TestOperator_KeyringList(t *testing.T) { c.EncryptKey = key } httpTestWithConfig(t, func(srv *HTTPServer) { - req, err := http.NewRequest("GET", "/v1/operator/keyring/list", nil) + req, err := http.NewRequest("GET", "/v1/operator/keyring", nil) if err != nil { t.Fatalf("err: %v", err) } resp := httptest.NewRecorder() - r, err := srv.OperatorKeyringList(resp, req) + r, err := srv.OperatorKeyringEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } @@ -120,13 +123,27 @@ func TestOperator_KeyringList(t *testing.T) { if len(responses) != 2 { t.Fatalf("bad: %d", len(responses)) } - for _, response := range responses { - if len(response.Keys) != 1 { - t.Fatalf("bad: %d", len(response.Keys)) - } - if _, ok := response.Keys[key]; !ok { - t.Fatalf("bad: %v", ok) - } + + // WAN + if len(responses[0].Keys) != 1 { + t.Fatalf("bad: %d", len(responses[0].Keys)) + } + if !responses[0].WAN { + t.Fatalf("bad: %v", responses[0].WAN) + } + if _, ok := responses[0].Keys[key]; !ok { + t.Fatalf("bad: %v", ok) + } + + // LAN + if len(responses[1].Keys) != 1 { + t.Fatalf("bad: %d", len(responses[1].Keys)) + } + if responses[1].WAN { + t.Fatalf("bad: %v", responses[1].WAN) + } + if _, ok := responses[1].Keys[key]; !ok { + t.Fatalf("bad: %v", ok) } }, configFunc) } @@ -162,13 +179,13 @@ func TestOperator_KeyringRemove(t *testing.T) { } body := bytes.NewBufferString(fmt.Sprintf("{\"Key\":\"%s\"}", tempKey)) - req, err := http.NewRequest("DELETE", "/v1/operator/keyring/remove", body) + req, err := http.NewRequest("DELETE", "/v1/operator/keyring", body) if err != nil { t.Fatalf("err: %v", err) } resp := httptest.NewRecorder() - _, err = srv.OperatorKeyringRemove(resp, req) + _, err = srv.OperatorKeyringEndpoint(resp, req) if err != nil { t.Fatalf("err: %s", err) } @@ -205,13 +222,13 @@ func TestOperator_KeyringUse(t *testing.T) { } body := bytes.NewBufferString(fmt.Sprintf("{\"Key\":\"%s\"}", newKey)) - req, err := http.NewRequest("PUT", "/v1/operator/keyring/use", body) + req, err := http.NewRequest("PUT", "/v1/operator/keyring", body) if err != nil { t.Fatalf("err: %v", err) } resp := httptest.NewRecorder() - _, err = srv.OperatorKeyringUse(resp, req) + _, err = srv.OperatorKeyringEndpoint(resp, req) if err != nil { t.Fatalf("err: %s", err) } diff --git a/website/source/docs/agent/http/operator.html.markdown b/website/source/docs/agent/http/operator.html.markdown index 8b1ac1712102..31d7d03b2b26 100644 --- a/website/source/docs/agent/http/operator.html.markdown +++ b/website/source/docs/agent/http/operator.html.markdown @@ -27,10 +27,7 @@ The following endpoints are supported: * [`/v1/operator/raft/configuration`](#raft-configuration): Inspects the Raft configuration * [`/v1/operator/raft/peer`](#raft-peer): Operates on Raft peers -* [`/v1/operator/keyring/install`](#keyring-install): Installs a new key into the keyring -* [`/v1/operator/keyring/list`](#keyring-list): Lists the installed gossip encryption keys -* [`/v1/operator/keyring/remove`](#keyring-remove): Removes a gossip key from the cluster -* [`/v1/operator/keyring/use`](#keyring-use): Changes the active encryption key +* [`/v1/operator/keyring`](#keyring): Operates on gossip keyring Not all endpoints support blocking queries and all consistency modes, see details in the sections below. @@ -134,38 +131,13 @@ If ACLs are enabled, the client will need to supply an ACL Token with The return code will indicate success or failure. -### /v1/operator/keyring/install +### /v1/operator/keyring -Available in Consul 0.7.2 and later, the keyring install endpoint supports the -`PUT` method. +Available in Consul 0.7.2 and later, the keyring endpoint supports the +`GET`, `POST`, `PUT` and `DELETE` methods. -#### PUT Method - -Using the `PUT` method, this endpoint will install a new gossip encryption key -into the cluster. There is more information on gossip encryption available -[here](/docs/agent/encryption.html#gossip-encryption). - -The register endpoint expects a JSON request body to be PUT. The request -body must look like: - -```javascript -{ - "Key": "3lg9DxVfKNzI8O+IQ5Ek+Q==" -} -``` - -The `Key` field is mandatory and provides the encryption key to install into the -cluster. - -If ACLs are enabled, the client will need to supply an ACL Token with -[`keyring`](/docs/internals/acl.html#keyring) write privileges. - -The return code will indicate success or failure. - -### /v1/operator/keyring/list - -Available in Consul 0.7.2 and later, the keyring install endpoint supports the -`GET` method. +This endpoint supports the use of ACL tokens using either the `X-CONSUL-TOKEN` +header or the "?token=" query parameter. #### GET Method @@ -214,16 +186,10 @@ A JSON body is returned that looks like this: `NumNodes` is the total number of nodes in the datacenter. -### /v1/operator/keyring/remove - -Available in Consul 0.7.2 and later, the keyring remove endpoint supports the -`PUT` method. - -#### PUT Method +#### POST Method -Using the `PUT` method, this endpoint will remove a gossip encryption key from -the cluster. This operation may only be performed on keys which are not currently -the primary key. There is more information on gossip encryption available +Using the `POST` method, this endpoint will install a new gossip encryption key +into the cluster. There is more information on gossip encryption available [here](/docs/agent/encryption.html#gossip-encryption). The register endpoint expects a JSON request body to be PUT. The request @@ -231,11 +197,11 @@ body must look like: ```javascript { - "Key": "3lg9DxVfKNzI8O+IQ5Ek+Q==" + "Key": "3lg9DxVfKNzI8O+IQ5Ek+Q==" } ``` -The `Key` field is mandatory and provides the encryption key to remove from the +The `Key` field is mandatory and provides the encryption key to install into the cluster. If ACLs are enabled, the client will need to supply an ACL Token with @@ -243,11 +209,6 @@ If ACLs are enabled, the client will need to supply an ACL Token with The return code will indicate success or failure. -### /v1/operator/keyring/use - -Available in Consul 0.7.2 and later, the keyring use endpoint supports the `PUT` -method. - #### PUT Method Using the `PUT` method, this endpoint will change the primary gossip encryption @@ -271,3 +232,27 @@ If ACLs are enabled, the client will need to supply an ACL Token with [`keyring`](/docs/internals/acl.html#keyring) write privileges. The return code will indicate success or failure. + +#### DELETE Method + +Using the `DELETE` method, this endpoint will remove a gossip encryption key from +the cluster. This operation may only be performed on keys which are not currently +the primary key. There is more information on gossip encryption available +[here](/docs/agent/encryption.html#gossip-encryption). + +The register endpoint expects a JSON request body to be PUT. The request +body must look like: + +```javascript +{ + "Key": "3lg9DxVfKNzI8O+IQ5Ek+Q==" +} +``` + +The `Key` field is mandatory and provides the encryption key to remove from the +cluster. + +If ACLs are enabled, the client will need to supply an ACL Token with +[`keyring`](/docs/internals/acl.html#keyring) write privileges. + +The return code will indicate success or failure.