nomad: adding ACL bootstrap endpoints
This commit is contained in:
parent
1ace912341
commit
e24a4abf2c
|
@ -192,6 +192,62 @@ func (a *ACL) GetPolicies(args *structs.ACLPolicySetRequest, reply *structs.ACLP
|
|||
return a.srv.blockingRPC(&opts)
|
||||
}
|
||||
|
||||
// Bootstrap is used to bootstrap the initial token
|
||||
func (a *ACL) Bootstrap(args *structs.ACLTokenBootstrapRequest, reply *structs.ACLTokenUpsertResponse) error {
|
||||
if done, err := a.srv.forward("ACL.Bootstrap", args, args, reply); done {
|
||||
return err
|
||||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "acl", "bootstrap"}, time.Now())
|
||||
|
||||
// Snapshot the state
|
||||
state, err := a.srv.State().Snapshot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify bootstrap is possible. The state store method re-verifies this,
|
||||
// but we do an early check to avoid raft transactions when possible.
|
||||
ok, err := state.CanBootstrapACLToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("ACL bootstrap already done")
|
||||
}
|
||||
|
||||
// Create a new global management token, override any parameter
|
||||
args.Token = &structs.ACLToken{
|
||||
AccessorID: structs.GenerateUUID(),
|
||||
SecretID: structs.GenerateUUID(),
|
||||
Name: "Bootstrap Token",
|
||||
Type: structs.ACLManagementToken,
|
||||
Global: true,
|
||||
CreateTime: time.Now().UTC(),
|
||||
}
|
||||
|
||||
// Update via Raft
|
||||
_, index, err := a.srv.raftApply(structs.ACLTokenBootstrapRequestType, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Populate the response. We do a lookup against the state to
|
||||
// pickup the proper create / modify times.
|
||||
state, err = a.srv.State().Snapshot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := state.ACLTokenByAccessorID(nil, args.Token.AccessorID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("token lookup failed: %v", err)
|
||||
}
|
||||
reply.Tokens = append(reply.Tokens, out)
|
||||
|
||||
// Update the index
|
||||
reply.Index = index
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpsertTokens is used to create or update a set of tokens
|
||||
func (a *ACL) UpsertTokens(args *structs.ACLTokenUpsertRequest, reply *structs.ACLTokenUpsertResponse) error {
|
||||
if done, err := a.srv.forward("ACL.UpsertTokens", args, args, reply); done {
|
||||
|
|
|
@ -771,6 +771,39 @@ func TestACLEndpoint_DeleteTokens(t *testing.T) {
|
|||
assert.NotEqual(t, uint64(0), resp.Index)
|
||||
}
|
||||
|
||||
func TestACLEndpoint_Bootstrap(t *testing.T) {
|
||||
t.Parallel()
|
||||
s1 := testServer(t, nil)
|
||||
defer s1.Shutdown()
|
||||
codec := rpcClient(t, s1)
|
||||
testutil.WaitForLeader(t, s1.RPC)
|
||||
|
||||
// Lookup the tokens
|
||||
req := &structs.ACLTokenBootstrapRequest{
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.ACLTokenUpsertResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "ACL.Bootstrap", req, &resp); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
assert.NotEqual(t, uint64(0), resp.Index)
|
||||
assert.NotNil(t, resp.Tokens[0])
|
||||
|
||||
// Get the token out from the response
|
||||
created := resp.Tokens[0]
|
||||
assert.NotEqual(t, "", created.AccessorID)
|
||||
assert.NotEqual(t, "", created.SecretID)
|
||||
assert.NotEqual(t, time.Time{}, created.CreateTime)
|
||||
assert.Equal(t, structs.ACLManagementToken, created.Type)
|
||||
assert.Equal(t, "Bootstrap Token", created.Name)
|
||||
assert.Equal(t, true, created.Global)
|
||||
|
||||
// Check we created the token
|
||||
out, err := s1.fsm.State().ACLTokenByAccessorID(nil, created.AccessorID)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, created, out)
|
||||
}
|
||||
|
||||
func TestACLEndpoint_UpsertTokens(t *testing.T) {
|
||||
t.Parallel()
|
||||
s1 := testServer(t, nil)
|
||||
|
|
17
nomad/fsm.go
17
nomad/fsm.go
|
@ -177,6 +177,8 @@ func (n *nomadFSM) Apply(log *raft.Log) interface{} {
|
|||
return n.applyACLTokenUpsert(buf[1:], log.Index)
|
||||
case structs.ACLTokenDeleteRequestType:
|
||||
return n.applyACLTokenDelete(buf[1:], log.Index)
|
||||
case structs.ACLTokenBootstrapRequestType:
|
||||
return n.applyACLTokenBootstrap(buf[1:], log.Index)
|
||||
default:
|
||||
if ignoreUnknown {
|
||||
n.logger.Printf("[WARN] nomad.fsm: ignoring unknown message type (%d), upgrade to newer version", msgType)
|
||||
|
@ -739,6 +741,21 @@ func (n *nomadFSM) applyACLTokenDelete(buf []byte, index uint64) interface{} {
|
|||
return nil
|
||||
}
|
||||
|
||||
// applyACLTokenBootstrap is used to bootstrap an ACL token
|
||||
func (n *nomadFSM) applyACLTokenBootstrap(buf []byte, index uint64) interface{} {
|
||||
defer metrics.MeasureSince([]string{"nomad", "fsm", "apply_acl_token_bootstrap"}, time.Now())
|
||||
var req structs.ACLTokenBootstrapRequest
|
||||
if err := structs.Decode(buf, &req); err != nil {
|
||||
panic(fmt.Errorf("failed to decode request: %v", err))
|
||||
}
|
||||
|
||||
if err := n.state.BootstrapACLTokens(index, req.Token); err != nil {
|
||||
n.logger.Printf("[ERR] nomad.fsm: BootstrapACLToken failed: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nomadFSM) Snapshot() (raft.FSMSnapshot, error) {
|
||||
// Create a new snapshot
|
||||
snap, err := n.state.Snapshot()
|
||||
|
|
|
@ -1571,6 +1571,30 @@ func TestFSM_DeleteACLPolicies(t *testing.T) {
|
|||
assert.Nil(t, out)
|
||||
}
|
||||
|
||||
func TestFSM_BootstrapACLTokens(t *testing.T) {
|
||||
t.Parallel()
|
||||
fsm := testFSM(t)
|
||||
|
||||
token := mock.ACLToken()
|
||||
req := structs.ACLTokenBootstrapRequest{
|
||||
Token: token,
|
||||
}
|
||||
buf, err := structs.Encode(structs.ACLTokenBootstrapRequestType, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
resp := fsm.Apply(makeLog(buf))
|
||||
if resp != nil {
|
||||
t.Fatalf("resp: %v", resp)
|
||||
}
|
||||
|
||||
// Verify we are registered
|
||||
out, err := fsm.State().ACLTokenByAccessorID(nil, token.AccessorID)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, out)
|
||||
}
|
||||
|
||||
func TestFSM_UpsertACLTokens(t *testing.T) {
|
||||
t.Parallel()
|
||||
fsm := testFSM(t)
|
||||
|
|
|
@ -67,6 +67,7 @@ const (
|
|||
ACLPolicyDeleteRequestType
|
||||
ACLTokenUpsertRequestType
|
||||
ACLTokenDeleteRequestType
|
||||
ACLTokenBootstrapRequestType
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -5560,6 +5561,12 @@ type ACLTokenDeleteRequest struct {
|
|||
WriteRequest
|
||||
}
|
||||
|
||||
// ACLTokenBootstrapRequest is used to bootstrap ACLs
|
||||
type ACLTokenBootstrapRequest struct {
|
||||
Token *ACLToken // Not client specifiable
|
||||
WriteRequest
|
||||
}
|
||||
|
||||
// ACLTokenUpsertRequest is used to upsert a set of tokens
|
||||
type ACLTokenUpsertRequest struct {
|
||||
Tokens []*ACLToken
|
||||
|
|
Loading…
Reference in New Issue