Add keyring http endpoints

This commit is contained in:
Kyle Havlovitz 2016-11-14 21:54:37 -05:00
parent 2319624f69
commit 2d37a07476
8 changed files with 446 additions and 3 deletions

View File

@ -43,6 +43,26 @@ type RaftConfiguration struct {
Index uint64
}
// KeyringOpts is used for performing Keyring operations
type KeyringOpts struct {
Key string `json:",omitempty"`
}
// KeyringResponse is returned when listing the gossip encryption keys
type KeyringResponse struct {
// Whether this response is for a WAN ring
WAN bool
// The datacenter name this request corresponds to
Datacenter string
// A map of the encryption keys to the number of nodes they're installed on
Keys map[string]int
// The total number of nodes in this ring
NumNodes int
}
// RaftGetConfiguration is used to query the current Raft peer set.
func (op *Operator) RaftGetConfiguration(q *QueryOptions) (*RaftConfiguration, error) {
r := op.c.newRequest("GET", "/v1/operator/raft/configuration")
@ -79,3 +99,61 @@ func (op *Operator) RaftRemovePeerByAddress(address string, q *WriteOptions) err
resp.Body.Close()
return nil
}
// 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{
Key: key,
}
_, resp, err := requireOK(op.c.doRequest(r))
if err != nil {
return err
}
resp.Body.Close()
return nil
}
// 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")
_, resp, err := requireOK(op.c.doRequest(r))
if err != nil {
return nil, err
}
defer resp.Body.Close()
var out []*KeyringResponse
if err := decodeBody(resp, &out); err != nil {
return nil, err
}
return out, nil
}
// 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{
Key: key,
}
_, resp, err := requireOK(op.c.doRequest(r))
if err != nil {
return err
}
resp.Body.Close()
return nil
}
// 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{
Key: key,
}
_, resp, err := requireOK(op.c.doRequest(r))
if err != nil {
return err
}
resp.Body.Close()
return nil
}

View File

@ -3,6 +3,8 @@ package api
import (
"strings"
"testing"
"github.com/hashicorp/consul/testutil"
)
func TestOperator_RaftGetConfiguration(t *testing.T) {
@ -36,3 +38,69 @@ func TestOperator_RaftRemovePeerByAddress(t *testing.T) {
t.Fatalf("err: %v", err)
}
}
func TestOperator_KeyringInstallListPutRemove(t *testing.T) {
oldKey := "d8wu8CSUrqgtjVsvcBPmhQ=="
newKey := "qxycTi/SsePj/TZzCBmNXw=="
t.Parallel()
c, s := makeClientWithConfig(t, nil, func(c *testutil.TestServerConfig) {
c.Encrypt = oldKey
})
defer s.Stop()
operator := c.Operator()
if err := operator.KeyringInstall(newKey); err != nil {
t.Fatalf("err: %v", err)
}
listResponses, err := operator.KeyringList()
if err != nil {
t.Fatalf("err %v", err)
}
// Make sure the new key is installed
if len(listResponses) != 2 {
t.Fatalf("bad: %v", len(listResponses))
}
for _, response := range listResponses {
if len(response.Keys) != 2 {
t.Fatalf("bad: %v", len(response.Keys))
}
if _, ok := response.Keys[oldKey]; !ok {
t.Fatalf("bad: %v", ok)
}
if _, ok := response.Keys[newKey]; !ok {
t.Fatalf("bad: %v", ok)
}
}
// Switch the primary to the new key
if err := operator.KeyringUse(newKey); err != nil {
t.Fatalf("err: %v", err)
}
if err := operator.KeyringRemove(oldKey); err != nil {
t.Fatalf("err: %v", err)
}
listResponses, err = operator.KeyringList()
if err != nil {
t.Fatalf("err %v", err)
}
// Make sure the old key is removed
if len(listResponses) != 2 {
t.Fatalf("bad: %v", len(listResponses))
}
for _, response := range listResponses {
if len(response.Keys) != 1 {
t.Fatalf("bad: %v", len(response.Keys))
}
if _, ok := response.Keys[oldKey]; ok {
t.Fatalf("bad: %v", ok)
}
if _, ok := response.Keys[newKey]; !ok {
t.Fatalf("bad: %v", ok)
}
}
}

View File

@ -291,6 +291,10 @@ 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/query", s.wrap(s.PreparedQueryGeneral))
s.handleFuncMetrics("/v1/query/", s.wrap(s.PreparedQuerySpecific))
s.handleFuncMetrics("/v1/session/create", s.wrap(s.SessionCreate))

View File

@ -1,6 +1,7 @@
package agent
import (
"fmt"
"net/http"
"github.com/hashicorp/consul/consul/structs"
@ -55,3 +56,110 @@ 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
}
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.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
}
// 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 {
return nil, err
}
for _, response := range responses.Responses {
if response.Error != "" {
return nil, fmt.Errorf(response.Error)
}
}
return responses.Responses, nil
}
// 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)
if err != nil {
return nil, err
}
for _, response := range responses.Responses {
if response.Error != "" {
return nil, fmt.Errorf(response.Error)
}
}
return nil, nil
}
// 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)
if err != nil {
return nil, err
}
for _, response := range responses.Responses {
if response.Error != "" {
return nil, fmt.Errorf(response.Error)
}
}
return nil, nil
}

View File

@ -2,6 +2,7 @@ package agent
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"strings"
@ -56,3 +57,185 @@ func TestOperator_OperatorRaftPeer(t *testing.T) {
}
})
}
func TestOperator_KeyringInstall(t *testing.T) {
oldKey := "H3/9gBxcKKRf45CaI2DlRg=="
newKey := "z90lFx3sZZLtTOkutXcwYg=="
configFunc := func(c *Config) {
c.EncryptKey = oldKey
}
httpTestWithConfig(t, func(srv *HTTPServer) {
body := bytes.NewBufferString(fmt.Sprintf("{\"Key\":\"%s\"}", newKey))
req, err := http.NewRequest("PUT", "/v1/operator/keyring/install", body)
if err != nil {
t.Fatalf("err: %v", err)
}
resp := httptest.NewRecorder()
_, err = srv.OperatorKeyringInstall(resp, req)
if err != nil {
t.Fatalf("err: %s", err)
}
listResponse, err := srv.agent.ListKeys("")
if err != nil {
t.Fatalf("err: %s", err)
}
for _, response := range listResponse.Responses {
count, ok := response.Keys[newKey]
if !ok {
t.Fatalf("bad: %v", response.Keys)
}
if count != response.NumNodes {
t.Fatalf("bad: %d, %d", count, response.NumNodes)
}
}
}, configFunc)
}
func TestOperator_KeyringList(t *testing.T) {
key := "H3/9gBxcKKRf45CaI2DlRg=="
configFunc := func(c *Config) {
c.EncryptKey = key
}
httpTestWithConfig(t, func(srv *HTTPServer) {
req, err := http.NewRequest("GET", "/v1/operator/keyring/list", nil)
if err != nil {
t.Fatalf("err: %v", err)
}
resp := httptest.NewRecorder()
r, err := srv.OperatorKeyringList(resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
responses, ok := r.([]*structs.KeyringResponse)
if !ok {
t.Fatalf("err: %v", !ok)
}
// Check that we get both a LAN and WAN response, and that they both only
// contain the original key
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)
}
}
}, configFunc)
}
func TestOperator_KeyringRemove(t *testing.T) {
key := "H3/9gBxcKKRf45CaI2DlRg=="
tempKey := "z90lFx3sZZLtTOkutXcwYg=="
configFunc := func(c *Config) {
c.EncryptKey = key
}
httpTestWithConfig(t, func(srv *HTTPServer) {
_, err := srv.agent.InstallKey(tempKey, "")
if err != nil {
t.Fatalf("err: %v", err)
}
// Make sure the temp key is installed
list, err := srv.agent.ListKeys("")
if err != nil {
t.Fatalf("err: %v", err)
}
responses := list.Responses
if len(responses) != 2 {
t.Fatalf("bad: %d", len(responses))
}
for _, response := range responses {
if len(response.Keys) != 2 {
t.Fatalf("bad: %d", len(response.Keys))
}
if _, ok := response.Keys[tempKey]; !ok {
t.Fatalf("bad: %v", ok)
}
}
body := bytes.NewBufferString(fmt.Sprintf("{\"Key\":\"%s\"}", tempKey))
req, err := http.NewRequest("DELETE", "/v1/operator/keyring/remove", body)
if err != nil {
t.Fatalf("err: %v", err)
}
resp := httptest.NewRecorder()
_, err = srv.OperatorKeyringRemove(resp, req)
if err != nil {
t.Fatalf("err: %s", err)
}
// Make sure the temp key has been removed
list, err = srv.agent.ListKeys("")
if err != nil {
t.Fatalf("err: %v", err)
}
responses = list.Responses
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[tempKey]; ok {
t.Fatalf("bad: %v", ok)
}
}
}, configFunc)
}
func TestOperator_KeyringUse(t *testing.T) {
oldKey := "H3/9gBxcKKRf45CaI2DlRg=="
newKey := "z90lFx3sZZLtTOkutXcwYg=="
configFunc := func(c *Config) {
c.EncryptKey = oldKey
}
httpTestWithConfig(t, func(srv *HTTPServer) {
if _, err := srv.agent.InstallKey(newKey, ""); err != nil {
t.Fatalf("err: %v", err)
}
body := bytes.NewBufferString(fmt.Sprintf("{\"Key\":\"%s\"}", newKey))
req, err := http.NewRequest("PUT", "/v1/operator/keyring/use", body)
if err != nil {
t.Fatalf("err: %v", err)
}
resp := httptest.NewRecorder()
_, err = srv.OperatorKeyringUse(resp, req)
if err != nil {
t.Fatalf("err: %s", err)
}
if _, err := srv.agent.RemoveKey(oldKey, ""); err != nil {
t.Fatalf("err: %v", err)
}
// Make sure only the new key remains
list, err := srv.agent.ListKeys("")
if err != nil {
t.Fatalf("err: %v", err)
}
responses := list.Responses
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[newKey]; !ok {
t.Fatalf("bad: %v", ok)
}
}
}, configFunc)
}

View File

@ -4,8 +4,9 @@ import (
"crypto/rand"
"encoding/base64"
"fmt"
"github.com/mitchellh/cli"
"strings"
"github.com/mitchellh/cli"
)
// KeygenCommand is a Command implementation that generates an encryption

View File

@ -906,10 +906,10 @@ func (r *KeyringRequest) RequestDatacenter() string {
type KeyringResponse struct {
WAN bool
Datacenter string
Messages map[string]string
Messages map[string]string `json:",omitempty"`
Keys map[string]int
NumNodes int
Error string
Error string `json:",omitempty"`
}
// KeyringResponses holds multiple responses to keyring queries. Each

View File

@ -70,6 +70,7 @@ type TestServerConfig struct {
ACLMasterToken string `json:"acl_master_token,omitempty"`
ACLDatacenter string `json:"acl_datacenter,omitempty"`
ACLDefaultPolicy string `json:"acl_default_policy,omitempty"`
Encrypt string `json:"encrypt,omitempty"`
Stdout, Stderr io.Writer `json:"-"`
}