Add keyring http endpoints
This commit is contained in:
parent
2319624f69
commit
2d37a07476
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:"-"`
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue