Combine keyring endpoints into one
This commit is contained in:
parent
b13c9dc5d3
commit
79f339b5dc
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
// 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 {
|
||||
if response.Error != "" {
|
||||
return nil, fmt.Errorf(response.Error)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
return nil, keyringErrorsOrNil(responses.Responses)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
||||
// WAN
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
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.
|
||||
|
|
Loading…
Reference in New Issue