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.