nomad: adding ACL bootstrap endpoints

This commit is contained in:
Armon Dadgar 2017-08-20 18:19:26 -07:00
parent 1ace912341
commit e24a4abf2c
5 changed files with 137 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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