Combine keyring endpoints into one

pull/2317/merge
Kyle Havlovitz 2016-11-22 18:24:40 -05:00
parent 31453c7dbd
commit 6bd65c668b
6 changed files with 141 additions and 161 deletions

View File

@ -43,8 +43,8 @@ type RaftConfiguration struct {
Index uint64 Index uint64
} }
// KeyringOpts is used for performing Keyring operations // keyringRequest is used for performing Keyring operations
type KeyringOpts struct { type keyringRequest struct {
Key string `json:",omitempty"` 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 // KeyringInstall is used to install a new gossip encryption key into the cluster
func (op *Operator) KeyringInstall(key string) error { func (op *Operator) KeyringInstall(key string, q *WriteOptions) error {
r := op.c.newRequest("PUT", "/v1/operator/keyring/install") r := op.c.newRequest("POST", "/v1/operator/keyring")
r.obj = KeyringOpts{ r.setWriteOptions(q)
r.obj = keyringRequest{
Key: key, Key: key,
} }
_, resp, err := requireOK(op.c.doRequest(r)) _, 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 // KeyringList is used to list the gossip keys installed in the cluster
func (op *Operator) KeyringList() ([]*KeyringResponse, error) { func (op *Operator) KeyringList(q *QueryOptions) ([]*KeyringResponse, error) {
r := op.c.newRequest("GET", "/v1/operator/keyring/list") r := op.c.newRequest("GET", "/v1/operator/keyring")
r.setQueryOptions(q)
_, resp, err := requireOK(op.c.doRequest(r)) _, resp, err := requireOK(op.c.doRequest(r))
if err != nil { if err != nil {
return nil, err 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 // KeyringRemove is used to remove a gossip encryption key from the cluster
func (op *Operator) KeyringRemove(key string) error { func (op *Operator) KeyringRemove(key string, q *WriteOptions) error {
r := op.c.newRequest("DELETE", "/v1/operator/keyring/remove") r := op.c.newRequest("DELETE", "/v1/operator/keyring")
r.obj = KeyringOpts{ r.setWriteOptions(q)
r.obj = keyringRequest{
Key: key, Key: key,
} }
_, resp, err := requireOK(op.c.doRequest(r)) _, 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 // KeyringUse is used to change the active gossip encryption key
func (op *Operator) KeyringUse(key string) error { func (op *Operator) KeyringUse(key string, q *WriteOptions) error {
r := op.c.newRequest("PUT", "/v1/operator/keyring/use") r := op.c.newRequest("PUT", "/v1/operator/keyring")
r.obj = KeyringOpts{ r.setWriteOptions(q)
r.obj = keyringRequest{
Key: key, Key: key,
} }
_, resp, err := requireOK(op.c.doRequest(r)) _, resp, err := requireOK(op.c.doRequest(r))

View File

@ -49,11 +49,11 @@ func TestOperator_KeyringInstallListPutRemove(t *testing.T) {
defer s.Stop() defer s.Stop()
operator := c.Operator() operator := c.Operator()
if err := operator.KeyringInstall(newKey); err != nil { if err := operator.KeyringInstall(newKey, nil); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
listResponses, err := operator.KeyringList() listResponses, err := operator.KeyringList(nil)
if err != nil { if err != nil {
t.Fatalf("err %v", err) t.Fatalf("err %v", err)
} }
@ -75,15 +75,15 @@ func TestOperator_KeyringInstallListPutRemove(t *testing.T) {
} }
// Switch the primary to the new key // 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) 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) t.Fatalf("err: %v", err)
} }
listResponses, err = operator.KeyringList() listResponses, err = operator.KeyringList(nil)
if err != nil { if err != nil {
t.Fatalf("err %v", err) t.Fatalf("err %v", err)
} }

View File

@ -291,10 +291,7 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) {
s.handleFuncMetrics("/v1/kv/", s.wrap(s.KVSEndpoint)) s.handleFuncMetrics("/v1/kv/", s.wrap(s.KVSEndpoint))
s.handleFuncMetrics("/v1/operator/raft/configuration", s.wrap(s.OperatorRaftConfiguration)) s.handleFuncMetrics("/v1/operator/raft/configuration", s.wrap(s.OperatorRaftConfiguration))
s.handleFuncMetrics("/v1/operator/raft/peer", s.wrap(s.OperatorRaftPeer)) 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", s.wrap(s.OperatorKeyringEndpoint))
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/query", s.wrap(s.PreparedQueryGeneral)) s.handleFuncMetrics("/v1/query", s.wrap(s.PreparedQueryGeneral))
s.handleFuncMetrics("/v1/query/", s.wrap(s.PreparedQuerySpecific)) s.handleFuncMetrics("/v1/query/", s.wrap(s.PreparedQuerySpecific))
s.handleFuncMetrics("/v1/session/create", s.wrap(s.SessionCreate)) s.handleFuncMetrics("/v1/session/create", s.wrap(s.SessionCreate))

View File

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/consul/structs"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/raft" "github.com/hashicorp/raft"
) )
@ -57,109 +58,85 @@ func (s *HTTPServer) OperatorRaftPeer(resp http.ResponseWriter, req *http.Reques
return nil, nil return nil, nil
} }
// OperatorKeyringInstall is used to install a new gossip encryption key into the cluster type keyringArgs struct {
func (s *HTTPServer) OperatorKeyringInstall(resp http.ResponseWriter, req *http.Request) (interface{}, error) { Key string
if req.Method != "PUT" { Token string
resp.WriteHeader(http.StatusMethodNotAllowed)
return nil, nil
} }
var args structs.KeyringRequest // 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 { if err := decodeBody(req, &args, nil); err != nil {
resp.WriteHeader(400) resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf("Request decode failed: %v", err))) resp.Write([]byte(fmt.Sprintf("Request decode failed: %v", err)))
return nil, nil return nil, nil
} }
}
s.parseToken(req, &args.Token) s.parseToken(req, &args.Token)
// 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
}
}
// 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) responses, err := s.agent.InstallKey(args.Key, args.Token)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, response := range responses.Responses {
if response.Error != "" { return nil, keyringErrorsOrNil(responses.Responses)
return nil, fmt.Errorf(response.Error)
}
} }
return nil, nil // 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)
// 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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, response := range responses.Responses {
if response.Error != "" { return responses.Responses, keyringErrorsOrNil(responses.Responses)
return nil, fmt.Errorf(response.Error)
}
} }
return responses.Responses, nil // 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) {
// 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)
responses, err := s.agent.RemoveKey(args.Key, args.Token) responses, err := s.agent.RemoveKey(args.Key, args.Token)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, response := range responses.Responses {
if response.Error != "" { return nil, keyringErrorsOrNil(responses.Responses)
return nil, fmt.Errorf(response.Error)
}
} }
return nil, nil // KeyringUse is used to change the primary gossip encryption key
} func (s *HTTPServer) KeyringUse(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) {
// 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)
responses, err := s.agent.UseKey(args.Key, args.Token) responses, err := s.agent.UseKey(args.Key, args.Token)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, response := range responses.Responses {
if response.Error != "" { return nil, keyringErrorsOrNil(responses.Responses)
return nil, fmt.Errorf(response.Error)
}
} }
return nil, nil func keyringErrorsOrNil(responses []*structs.KeyringResponse) error {
var errs error
for _, response := range responses {
if response.Error != "" {
errs = multierror.Append(errs, fmt.Errorf(response.Error))
}
}
return errs
} }

View File

@ -66,13 +66,13 @@ func TestOperator_KeyringInstall(t *testing.T) {
} }
httpTestWithConfig(t, func(srv *HTTPServer) { httpTestWithConfig(t, func(srv *HTTPServer) {
body := bytes.NewBufferString(fmt.Sprintf("{\"Key\":\"%s\"}", newKey)) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
_, err = srv.OperatorKeyringInstall(resp, req) _, err = srv.OperatorKeyringEndpoint(resp, req)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@ -81,6 +81,9 @@ func TestOperator_KeyringInstall(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if len(listResponse.Responses) != 2 {
t.Fatalf("bad: %d", len(listResponse.Responses))
}
for _, response := range listResponse.Responses { for _, response := range listResponse.Responses {
count, ok := response.Keys[newKey] count, ok := response.Keys[newKey]
@ -100,13 +103,13 @@ func TestOperator_KeyringList(t *testing.T) {
c.EncryptKey = key c.EncryptKey = key
} }
httpTestWithConfig(t, func(srv *HTTPServer) { 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
r, err := srv.OperatorKeyringList(resp, req) r, err := srv.OperatorKeyringEndpoint(resp, req)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -120,13 +123,27 @@ func TestOperator_KeyringList(t *testing.T) {
if len(responses) != 2 { if len(responses) != 2 {
t.Fatalf("bad: %d", len(responses)) t.Fatalf("bad: %d", len(responses))
} }
for _, response := range responses {
if len(response.Keys) != 1 { // WAN
t.Fatalf("bad: %d", len(response.Keys)) if len(responses[0].Keys) != 1 {
t.Fatalf("bad: %d", len(responses[0].Keys))
} }
if _, ok := response.Keys[key]; !ok { if !responses[0].WAN {
t.Fatalf("bad: %v", responses[0].WAN)
}
if _, ok := responses[0].Keys[key]; !ok {
t.Fatalf("bad: %v", 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) }, configFunc)
} }
@ -162,13 +179,13 @@ func TestOperator_KeyringRemove(t *testing.T) {
} }
body := bytes.NewBufferString(fmt.Sprintf("{\"Key\":\"%s\"}", tempKey)) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
_, err = srv.OperatorKeyringRemove(resp, req) _, err = srv.OperatorKeyringEndpoint(resp, req)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@ -205,13 +222,13 @@ func TestOperator_KeyringUse(t *testing.T) {
} }
body := bytes.NewBufferString(fmt.Sprintf("{\"Key\":\"%s\"}", newKey)) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
_, err = srv.OperatorKeyringUse(resp, req) _, err = srv.OperatorKeyringEndpoint(resp, req)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }

View File

@ -27,10 +27,7 @@ The following endpoints are supported:
* [`/v1/operator/raft/configuration`](#raft-configuration): Inspects the Raft configuration * [`/v1/operator/raft/configuration`](#raft-configuration): Inspects the Raft configuration
* [`/v1/operator/raft/peer`](#raft-peer): Operates on Raft peers * [`/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`](#keyring): Operates on gossip 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
Not all endpoints support blocking queries and all consistency modes, Not all endpoints support blocking queries and all consistency modes,
see details in the sections below. 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. The return code will indicate success or failure.
### <a name="keyring-install"></a> /v1/operator/keyring/install ### <a name="keyring"></a> /v1/operator/keyring
Available in Consul 0.7.2 and later, the keyring install endpoint supports the Available in Consul 0.7.2 and later, the keyring endpoint supports the
`PUT` method. `GET`, `POST`, `PUT` and `DELETE` methods.
#### PUT Method This endpoint supports the use of ACL tokens using either the `X-CONSUL-TOKEN`
header or the "?token=" query parameter.
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.
### <a name="keyring-list"></a> /v1/operator/keyring/list
Available in Consul 0.7.2 and later, the keyring install endpoint supports the
`GET` method.
#### GET Method #### 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. `NumNodes` is the total number of nodes in the datacenter.
### <a name="keyring-remove"></a> /v1/operator/keyring/remove #### POST Method
Available in Consul 0.7.2 and later, the keyring remove endpoint supports the Using the `POST` method, this endpoint will install a new gossip encryption key
`PUT` method. into the cluster. There is more information on gossip encryption available
#### PUT 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
[here](/docs/agent/encryption.html#gossip-encryption). [here](/docs/agent/encryption.html#gossip-encryption).
The register endpoint expects a JSON request body to be PUT. The request The register endpoint expects a JSON request body to be PUT. The request
@ -235,7 +201,7 @@ body must look like:
} }
``` ```
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. cluster.
If ACLs are enabled, the client will need to supply an ACL Token with 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. The return code will indicate success or failure.
### <a name="keyring-use"></a> /v1/operator/keyring/use
Available in Consul 0.7.2 and later, the keyring use endpoint supports the `PUT`
method.
#### PUT Method #### PUT Method
Using the `PUT` method, this endpoint will change the primary gossip encryption 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. [`keyring`](/docs/internals/acl.html#keyring) write privileges.
The return code will indicate success or failure. 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.