Combine keyring endpoints into one

This commit is contained in:
Kyle Havlovitz 2016-11-22 18:24:40 -05:00
parent b13c9dc5d3
commit 79f339b5dc
6 changed files with 141 additions and 161 deletions

View File

@ -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))

View File

@ -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)
}

View File

@ -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))

View File

@ -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
}
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
// 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)
// 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)
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
}

View File

@ -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)
}

View File

@ -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.
### <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
`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.
### <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.
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.
### <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
`PUT` method.
#### 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
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.
### <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
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.