package nomad import ( "fmt" "io/ioutil" "os" "path/filepath" "strings" "testing" "time" msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc" "github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestACLEndpoint_GetPolicy(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request policy := mock.ACLPolicy() s1.fsm.State().UpsertACLPolicies(structs.MsgTypeTestSetup, 1000, []*structs.ACLPolicy{policy}) anonymousPolicy := mock.ACLPolicy() anonymousPolicy.Name = "anonymous" s1.fsm.State().UpsertACLPolicies(structs.MsgTypeTestSetup, 1001, []*structs.ACLPolicy{anonymousPolicy}) // Create a token with one the policy token := mock.ACLToken() token.Policies = []string{policy.Name} s1.fsm.State().UpsertACLTokens(structs.MsgTypeTestSetup, 1002, []*structs.ACLToken{token}) // Lookup the policy get := &structs.ACLPolicySpecificRequest{ Name: policy.Name, QueryOptions: structs.QueryOptions{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.SingleACLPolicyResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicy", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp.Index) assert.Equal(t, policy, resp.Policy) // Lookup non-existing policy get.Name = uuid.Generate() if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicy", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1001), resp.Index) assert.Nil(t, resp.Policy) // Lookup the policy with the token get = &structs.ACLPolicySpecificRequest{ Name: policy.Name, QueryOptions: structs.QueryOptions{ Region: "global", AuthToken: token.SecretID, }, } var resp2 structs.SingleACLPolicyResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicy", get, &resp2); err != nil { t.Fatalf("err: %v", err) } assert.EqualValues(t, 1000, resp2.Index) assert.Equal(t, policy, resp2.Policy) // Lookup the anonymous policy with no token get = &structs.ACLPolicySpecificRequest{ Name: anonymousPolicy.Name, QueryOptions: structs.QueryOptions{ Region: "global", }, } var resp3 structs.SingleACLPolicyResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicy", get, &resp3); err != nil { require.NoError(t, err) } assert.EqualValues(t, 1001, resp3.Index) assert.Equal(t, anonymousPolicy, resp3.Policy) // Lookup non-anonoymous policy with no token get = &structs.ACLPolicySpecificRequest{ Name: policy.Name, QueryOptions: structs.QueryOptions{ Region: "global", }, } var resp4 structs.SingleACLPolicyResponse err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicy", get, &resp4) require.Error(t, err) require.Contains(t, err.Error(), structs.ErrPermissionDenied.Error()) } func TestACLEndpoint_GetPolicy_Blocking(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() state := s1.fsm.State() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the policies p1 := mock.ACLPolicy() p2 := mock.ACLPolicy() // First create an unrelated policy time.AfterFunc(100*time.Millisecond, func() { err := state.UpsertACLPolicies(structs.MsgTypeTestSetup, 100, []*structs.ACLPolicy{p1}) if err != nil { t.Fatalf("err: %v", err) } }) // Upsert the policy we are watching later time.AfterFunc(200*time.Millisecond, func() { err := state.UpsertACLPolicies(structs.MsgTypeTestSetup, 200, []*structs.ACLPolicy{p2}) if err != nil { t.Fatalf("err: %v", err) } }) // Lookup the policy req := &structs.ACLPolicySpecificRequest{ Name: p2.Name, QueryOptions: structs.QueryOptions{ Region: "global", MinQueryIndex: 150, AuthToken: root.SecretID, }, } var resp structs.SingleACLPolicyResponse start := time.Now() if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicy", req, &resp); err != nil { t.Fatalf("err: %v", err) } if elapsed := time.Since(start); elapsed < 200*time.Millisecond { t.Fatalf("should block (returned in %s) %#v", elapsed, resp) } if resp.Index != 200 { t.Fatalf("Bad index: %d %d", resp.Index, 200) } if resp.Policy == nil || resp.Policy.Name != p2.Name { t.Fatalf("bad: %#v", resp.Policy) } // Eval delete triggers watches time.AfterFunc(100*time.Millisecond, func() { err := state.DeleteACLPolicies(structs.MsgTypeTestSetup, 300, []string{p2.Name}) if err != nil { t.Fatalf("err: %v", err) } }) req.QueryOptions.MinQueryIndex = 250 var resp2 structs.SingleACLPolicyResponse start = time.Now() if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicy", req, &resp2); err != nil { t.Fatalf("err: %v", err) } if elapsed := time.Since(start); elapsed < 100*time.Millisecond { t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) } if resp2.Index != 300 { t.Fatalf("Bad index: %d %d", resp2.Index, 300) } if resp2.Policy != nil { t.Fatalf("bad: %#v", resp2.Policy) } } func TestACLEndpoint_GetPolicies(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request policy := mock.ACLPolicy() policy2 := mock.ACLPolicy() s1.fsm.State().UpsertACLPolicies(structs.MsgTypeTestSetup, 1000, []*structs.ACLPolicy{policy, policy2}) // Lookup the policy get := &structs.ACLPolicySetRequest{ Names: []string{policy.Name, policy2.Name}, QueryOptions: structs.QueryOptions{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.ACLPolicySetResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicies", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp.Index) assert.Equal(t, 2, len(resp.Policies)) assert.Equal(t, policy, resp.Policies[policy.Name]) assert.Equal(t, policy2, resp.Policies[policy2.Name]) // Lookup non-existing policy get.Names = []string{uuid.Generate()} resp = structs.ACLPolicySetResponse{} if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicies", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp.Index) assert.Equal(t, 0, len(resp.Policies)) } func TestACLEndpoint_GetPolicies_TokenSubset(t *testing.T) { t.Parallel() s1, _, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request policy := mock.ACLPolicy() policy2 := mock.ACLPolicy() s1.fsm.State().UpsertACLPolicies(structs.MsgTypeTestSetup, 1000, []*structs.ACLPolicy{policy, policy2}) token := mock.ACLToken() token.Policies = []string{policy.Name} s1.fsm.State().UpsertACLTokens(structs.MsgTypeTestSetup, 1000, []*structs.ACLToken{token}) // Lookup the policy which is a subset of our tokens get := &structs.ACLPolicySetRequest{ Names: []string{policy.Name}, QueryOptions: structs.QueryOptions{ Region: "global", AuthToken: token.SecretID, }, } var resp structs.ACLPolicySetResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicies", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp.Index) assert.Equal(t, 1, len(resp.Policies)) assert.Equal(t, policy, resp.Policies[policy.Name]) // Lookup non-associated policy get.Names = []string{policy2.Name} resp = structs.ACLPolicySetResponse{} if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicies", get, &resp); err == nil { t.Fatalf("expected error") } } func TestACLEndpoint_GetPolicies_Blocking(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() state := s1.fsm.State() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the policies p1 := mock.ACLPolicy() p2 := mock.ACLPolicy() // First create an unrelated policy time.AfterFunc(100*time.Millisecond, func() { err := state.UpsertACLPolicies(structs.MsgTypeTestSetup, 100, []*structs.ACLPolicy{p1}) if err != nil { t.Fatalf("err: %v", err) } }) // Upsert the policy we are watching later time.AfterFunc(200*time.Millisecond, func() { err := state.UpsertACLPolicies(structs.MsgTypeTestSetup, 200, []*structs.ACLPolicy{p2}) if err != nil { t.Fatalf("err: %v", err) } }) // Lookup the policy req := &structs.ACLPolicySetRequest{ Names: []string{p2.Name}, QueryOptions: structs.QueryOptions{ Region: "global", MinQueryIndex: 150, AuthToken: root.SecretID, }, } var resp structs.ACLPolicySetResponse start := time.Now() if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicies", req, &resp); err != nil { t.Fatalf("err: %v", err) } if elapsed := time.Since(start); elapsed < 200*time.Millisecond { t.Fatalf("should block (returned in %s) %#v", elapsed, resp) } if resp.Index != 200 { t.Fatalf("Bad index: %d %d", resp.Index, 200) } if len(resp.Policies) == 0 || resp.Policies[p2.Name] == nil { t.Fatalf("bad: %#v", resp.Policies) } // Eval delete triggers watches time.AfterFunc(100*time.Millisecond, func() { err := state.DeleteACLPolicies(structs.MsgTypeTestSetup, 300, []string{p2.Name}) if err != nil { t.Fatalf("err: %v", err) } }) req.QueryOptions.MinQueryIndex = 250 var resp2 structs.ACLPolicySetResponse start = time.Now() if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicies", req, &resp2); err != nil { t.Fatalf("err: %v", err) } if elapsed := time.Since(start); elapsed < 100*time.Millisecond { t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) } if resp2.Index != 300 { t.Fatalf("Bad index: %d %d", resp2.Index, 300) } if len(resp2.Policies) != 0 { t.Fatalf("bad: %#v", resp2.Policies) } } func TestACLEndpoint_ListPolicies(t *testing.T) { assert := assert.New(t) t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request p1 := mock.ACLPolicy() p2 := mock.ACLPolicy() p1.Name = "aaaaaaaa-3350-4b4b-d185-0e1992ed43e9" p2.Name = "aaaabbbb-3350-4b4b-d185-0e1992ed43e9" s1.fsm.State().UpsertACLPolicies(structs.MsgTypeTestSetup, 1000, []*structs.ACLPolicy{p1, p2}) // Create a token with one of those policies token := mock.ACLToken() token.Policies = []string{p1.Name} s1.fsm.State().UpsertACLTokens(structs.MsgTypeTestSetup, 1001, []*structs.ACLToken{token}) // Lookup the policies get := &structs.ACLPolicyListRequest{ QueryOptions: structs.QueryOptions{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.ACLPolicyListResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.ListPolicies", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.EqualValues(1000, resp.Index) assert.Len(resp.Policies, 2) // Lookup the policies by prefix get = &structs.ACLPolicyListRequest{ QueryOptions: structs.QueryOptions{ Region: "global", Prefix: "aaaabb", AuthToken: root.SecretID, }, } var resp2 structs.ACLPolicyListResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.ListPolicies", get, &resp2); err != nil { t.Fatalf("err: %v", err) } assert.EqualValues(1000, resp2.Index) assert.Len(resp2.Policies, 1) // List policies using the created token get = &structs.ACLPolicyListRequest{ QueryOptions: structs.QueryOptions{ Region: "global", AuthToken: token.SecretID, }, } var resp3 structs.ACLPolicyListResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.ListPolicies", get, &resp3); err != nil { t.Fatalf("err: %v", err) } assert.EqualValues(1000, resp3.Index) if assert.Len(resp3.Policies, 1) { assert.Equal(resp3.Policies[0].Name, p1.Name) } } // TestACLEndpoint_ListPolicies_Unauthenticated asserts that // unauthenticated ListPolicies returns anonymous policy if one // exists, otherwise, empty func TestACLEndpoint_ListPolicies_Unauthenticated(t *testing.T) { t.Parallel() s1, _, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) listPolicies := func() (*structs.ACLPolicyListResponse, error) { // Lookup the policies get := &structs.ACLPolicyListRequest{ QueryOptions: structs.QueryOptions{ Region: "global", }, } var resp structs.ACLPolicyListResponse err := msgpackrpc.CallWithCodec(codec, "ACL.ListPolicies", get, &resp) if err != nil { return nil, err } return &resp, nil } p1 := mock.ACLPolicy() p1.Name = "aaaaaaaa-3350-4b4b-d185-0e1992ed43e9" s1.fsm.State().UpsertACLPolicies(structs.MsgTypeTestSetup, 1000, []*structs.ACLPolicy{p1}) t.Run("no anonymous policy", func(t *testing.T) { resp, err := listPolicies() require.NoError(t, err) require.Empty(t, resp.Policies) require.Equal(t, uint64(1000), resp.Index) }) // now try with anonymous policy p2 := mock.ACLPolicy() p2.Name = "anonymous" s1.fsm.State().UpsertACLPolicies(structs.MsgTypeTestSetup, 1001, []*structs.ACLPolicy{p2}) t.Run("with anonymous policy", func(t *testing.T) { resp, err := listPolicies() require.NoError(t, err) require.Len(t, resp.Policies, 1) require.Equal(t, "anonymous", resp.Policies[0].Name) require.Equal(t, uint64(1001), resp.Index) }) } func TestACLEndpoint_ListPolicies_Blocking(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() state := s1.fsm.State() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the policy policy := mock.ACLPolicy() // Upsert eval triggers watches time.AfterFunc(100*time.Millisecond, func() { if err := state.UpsertACLPolicies(structs.MsgTypeTestSetup, 2, []*structs.ACLPolicy{policy}); err != nil { t.Fatalf("err: %v", err) } }) req := &structs.ACLPolicyListRequest{ QueryOptions: structs.QueryOptions{ Region: "global", MinQueryIndex: 1, AuthToken: root.SecretID, }, } start := time.Now() var resp structs.ACLPolicyListResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.ListPolicies", req, &resp); err != nil { t.Fatalf("err: %v", err) } if elapsed := time.Since(start); elapsed < 100*time.Millisecond { t.Fatalf("should block (returned in %s) %#v", elapsed, resp) } assert.Equal(t, uint64(2), resp.Index) if len(resp.Policies) != 1 || resp.Policies[0].Name != policy.Name { t.Fatalf("bad: %#v", resp.Policies) } // Eval deletion triggers watches time.AfterFunc(100*time.Millisecond, func() { if err := state.DeleteACLPolicies(structs.MsgTypeTestSetup, 3, []string{policy.Name}); err != nil { t.Fatalf("err: %v", err) } }) req.MinQueryIndex = 2 start = time.Now() var resp2 structs.ACLPolicyListResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.ListPolicies", req, &resp2); err != nil { t.Fatalf("err: %v", err) } if elapsed := time.Since(start); elapsed < 100*time.Millisecond { t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) } assert.Equal(t, uint64(3), resp2.Index) assert.Equal(t, 0, len(resp2.Policies)) } func TestACLEndpoint_DeletePolicies(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request p1 := mock.ACLPolicy() s1.fsm.State().UpsertACLPolicies(structs.MsgTypeTestSetup, 1000, []*structs.ACLPolicy{p1}) // Lookup the policies req := &structs.ACLPolicyDeleteRequest{ Names: []string{p1.Name}, WriteRequest: structs.WriteRequest{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.GenericResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.DeletePolicies", req, &resp); err != nil { t.Fatalf("err: %v", err) } assert.NotEqual(t, uint64(0), resp.Index) } func TestACLEndpoint_UpsertPolicies(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request p1 := mock.ACLPolicy() // Lookup the policies req := &structs.ACLPolicyUpsertRequest{ Policies: []*structs.ACLPolicy{p1}, WriteRequest: structs.WriteRequest{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.GenericResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.UpsertPolicies", req, &resp); err != nil { t.Fatalf("err: %v", err) } assert.NotEqual(t, uint64(0), resp.Index) // Check we created the policy out, err := s1.fsm.State().ACLPolicyByName(nil, p1.Name) assert.Nil(t, err) assert.NotNil(t, out) } func TestACLEndpoint_UpsertPolicies_Invalid(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request p1 := mock.ACLPolicy() p1.Rules = "blah blah invalid" // Lookup the policies req := &structs.ACLPolicyUpsertRequest{ Policies: []*structs.ACLPolicy{p1}, WriteRequest: structs.WriteRequest{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.GenericResponse err := msgpackrpc.CallWithCodec(codec, "ACL.UpsertPolicies", req, &resp) assert.NotNil(t, err) if !strings.Contains(err.Error(), "failed to parse") { t.Fatalf("bad: %s", err) } } func TestACLEndpoint_GetToken(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request token := mock.ACLToken() s1.fsm.State().UpsertACLTokens(structs.MsgTypeTestSetup, 1000, []*structs.ACLToken{token}) // Lookup the token get := &structs.ACLTokenSpecificRequest{ AccessorID: token.AccessorID, QueryOptions: structs.QueryOptions{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.SingleACLTokenResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.GetToken", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp.Index) assert.Equal(t, token, resp.Token) // Lookup non-existing token get.AccessorID = uuid.Generate() if err := msgpackrpc.CallWithCodec(codec, "ACL.GetToken", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp.Index) assert.Nil(t, resp.Token) // Lookup the token by accessor id using the tokens secret ID get.AccessorID = token.AccessorID get.AuthToken = token.SecretID var resp2 structs.SingleACLTokenResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.GetToken", get, &resp2); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp2.Index) assert.Equal(t, token, resp2.Token) } func TestACLEndpoint_GetToken_Blocking(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() state := s1.fsm.State() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the tokens p1 := mock.ACLToken() p2 := mock.ACLToken() // First create an unrelated token time.AfterFunc(100*time.Millisecond, func() { err := state.UpsertACLTokens(structs.MsgTypeTestSetup, 100, []*structs.ACLToken{p1}) if err != nil { t.Fatalf("err: %v", err) } }) // Upsert the token we are watching later time.AfterFunc(200*time.Millisecond, func() { err := state.UpsertACLTokens(structs.MsgTypeTestSetup, 200, []*structs.ACLToken{p2}) if err != nil { t.Fatalf("err: %v", err) } }) // Lookup the token req := &structs.ACLTokenSpecificRequest{ AccessorID: p2.AccessorID, QueryOptions: structs.QueryOptions{ Region: "global", MinQueryIndex: 150, AuthToken: root.SecretID, }, } var resp structs.SingleACLTokenResponse start := time.Now() if err := msgpackrpc.CallWithCodec(codec, "ACL.GetToken", req, &resp); err != nil { t.Fatalf("err: %v", err) } if elapsed := time.Since(start); elapsed < 200*time.Millisecond { t.Fatalf("should block (returned in %s) %#v", elapsed, resp) } if resp.Index != 200 { t.Fatalf("Bad index: %d %d", resp.Index, 200) } if resp.Token == nil || resp.Token.AccessorID != p2.AccessorID { t.Fatalf("bad: %#v", resp.Token) } // Eval delete triggers watches time.AfterFunc(100*time.Millisecond, func() { err := state.DeleteACLTokens(structs.MsgTypeTestSetup, 300, []string{p2.AccessorID}) if err != nil { t.Fatalf("err: %v", err) } }) req.QueryOptions.MinQueryIndex = 250 var resp2 structs.SingleACLTokenResponse start = time.Now() if err := msgpackrpc.CallWithCodec(codec, "ACL.GetToken", req, &resp2); err != nil { t.Fatalf("err: %v", err) } if elapsed := time.Since(start); elapsed < 100*time.Millisecond { t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) } if resp2.Index != 300 { t.Fatalf("Bad index: %d %d", resp2.Index, 300) } if resp2.Token != nil { t.Fatalf("bad: %#v", resp2.Token) } } func TestACLEndpoint_GetTokens(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request token := mock.ACLToken() token2 := mock.ACLToken() s1.fsm.State().UpsertACLTokens(structs.MsgTypeTestSetup, 1000, []*structs.ACLToken{token, token2}) // Lookup the token get := &structs.ACLTokenSetRequest{ AccessorIDS: []string{token.AccessorID, token2.AccessorID}, QueryOptions: structs.QueryOptions{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.ACLTokenSetResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.GetTokens", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp.Index) assert.Equal(t, 2, len(resp.Tokens)) assert.Equal(t, token, resp.Tokens[token.AccessorID]) // Lookup non-existing token get.AccessorIDS = []string{uuid.Generate()} resp = structs.ACLTokenSetResponse{} if err := msgpackrpc.CallWithCodec(codec, "ACL.GetTokens", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp.Index) assert.Equal(t, 0, len(resp.Tokens)) } func TestACLEndpoint_GetTokens_Blocking(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() state := s1.fsm.State() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the tokens p1 := mock.ACLToken() p2 := mock.ACLToken() // First create an unrelated token time.AfterFunc(100*time.Millisecond, func() { err := state.UpsertACLTokens(structs.MsgTypeTestSetup, 100, []*structs.ACLToken{p1}) if err != nil { t.Fatalf("err: %v", err) } }) // Upsert the token we are watching later time.AfterFunc(200*time.Millisecond, func() { err := state.UpsertACLTokens(structs.MsgTypeTestSetup, 200, []*structs.ACLToken{p2}) if err != nil { t.Fatalf("err: %v", err) } }) // Lookup the token req := &structs.ACLTokenSetRequest{ AccessorIDS: []string{p2.AccessorID}, QueryOptions: structs.QueryOptions{ Region: "global", MinQueryIndex: 150, AuthToken: root.SecretID, }, } var resp structs.ACLTokenSetResponse start := time.Now() if err := msgpackrpc.CallWithCodec(codec, "ACL.GetTokens", req, &resp); err != nil { t.Fatalf("err: %v", err) } if elapsed := time.Since(start); elapsed < 200*time.Millisecond { t.Fatalf("should block (returned in %s) %#v", elapsed, resp) } if resp.Index != 200 { t.Fatalf("Bad index: %d %d", resp.Index, 200) } if len(resp.Tokens) == 0 || resp.Tokens[p2.AccessorID] == nil { t.Fatalf("bad: %#v", resp.Tokens) } // Eval delete triggers watches time.AfterFunc(100*time.Millisecond, func() { err := state.DeleteACLTokens(structs.MsgTypeTestSetup, 300, []string{p2.AccessorID}) if err != nil { t.Fatalf("err: %v", err) } }) req.QueryOptions.MinQueryIndex = 250 var resp2 structs.ACLTokenSetResponse start = time.Now() if err := msgpackrpc.CallWithCodec(codec, "ACL.GetTokens", req, &resp2); err != nil { t.Fatalf("err: %v", err) } if elapsed := time.Since(start); elapsed < 100*time.Millisecond { t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) } if resp2.Index != 300 { t.Fatalf("Bad index: %d %d", resp2.Index, 300) } if len(resp2.Tokens) != 0 { t.Fatalf("bad: %#v", resp2.Tokens) } } func TestACLEndpoint_ListTokens(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request p1 := mock.ACLToken() p2 := mock.ACLToken() p2.Global = true p1.AccessorID = "aaaaaaaa-3350-4b4b-d185-0e1992ed43e9" p2.AccessorID = "aaaabbbb-3350-4b4b-d185-0e1992ed43e9" s1.fsm.State().UpsertACLTokens(structs.MsgTypeTestSetup, 1000, []*structs.ACLToken{p1, p2}) // Lookup the tokens get := &structs.ACLTokenListRequest{ QueryOptions: structs.QueryOptions{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.ACLTokenListResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.ListTokens", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp.Index) assert.Equal(t, 3, len(resp.Tokens)) // Lookup the tokens by prefix get = &structs.ACLTokenListRequest{ QueryOptions: structs.QueryOptions{ Region: "global", Prefix: "aaaabb", AuthToken: root.SecretID, }, } var resp2 structs.ACLTokenListResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.ListTokens", get, &resp2); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp2.Index) assert.Equal(t, 1, len(resp2.Tokens)) // Lookup the global tokens get = &structs.ACLTokenListRequest{ GlobalOnly: true, QueryOptions: structs.QueryOptions{ Region: "global", AuthToken: root.SecretID, }, } var resp3 structs.ACLTokenListResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.ListTokens", get, &resp3); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp3.Index) assert.Equal(t, 2, len(resp3.Tokens)) } func TestACLEndpoint_ListTokens_Blocking(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() state := s1.fsm.State() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the token token := mock.ACLToken() // Upsert eval triggers watches time.AfterFunc(100*time.Millisecond, func() { if err := state.UpsertACLTokens(structs.MsgTypeTestSetup, 3, []*structs.ACLToken{token}); err != nil { t.Fatalf("err: %v", err) } }) req := &structs.ACLTokenListRequest{ QueryOptions: structs.QueryOptions{ Region: "global", MinQueryIndex: 2, AuthToken: root.SecretID, }, } start := time.Now() var resp structs.ACLTokenListResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.ListTokens", req, &resp); err != nil { t.Fatalf("err: %v", err) } if elapsed := time.Since(start); elapsed < 100*time.Millisecond { t.Fatalf("should block (returned in %s) %#v", elapsed, resp) } assert.Equal(t, uint64(3), resp.Index) if len(resp.Tokens) != 2 { t.Fatalf("bad: %#v", resp.Tokens) } // Eval deletion triggers watches time.AfterFunc(100*time.Millisecond, func() { if err := state.DeleteACLTokens(structs.MsgTypeTestSetup, 4, []string{token.AccessorID}); err != nil { t.Fatalf("err: %v", err) } }) req.MinQueryIndex = 3 start = time.Now() var resp2 structs.ACLTokenListResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.ListTokens", req, &resp2); err != nil { t.Fatalf("err: %v", err) } if elapsed := time.Since(start); elapsed < 100*time.Millisecond { t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) } assert.Equal(t, uint64(4), resp2.Index) assert.Equal(t, 1, len(resp2.Tokens)) } func TestACLEndpoint_DeleteTokens(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request p1 := mock.ACLToken() s1.fsm.State().UpsertACLTokens(structs.MsgTypeTestSetup, 1000, []*structs.ACLToken{p1}) // Lookup the tokens req := &structs.ACLTokenDeleteRequest{ AccessorIDs: []string{p1.AccessorID}, WriteRequest: structs.WriteRequest{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.GenericResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.DeleteTokens", req, &resp); err != nil { t.Fatalf("err: %v", err) } assert.NotEqual(t, uint64(0), resp.Index) } func TestACLEndpoint_DeleteTokens_WithNonexistentToken(t *testing.T) { t.Parallel() assert := assert.New(t) s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) nonexistentToken := mock.ACLToken() // Lookup the policies req := &structs.ACLTokenDeleteRequest{ AccessorIDs: []string{nonexistentToken.AccessorID}, WriteRequest: structs.WriteRequest{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.GenericResponse err := msgpackrpc.CallWithCodec(codec, "ACL.DeleteTokens", req, &resp) assert.NotNil(err) expectedError := fmt.Sprintf("Cannot delete nonexistent tokens: %s", nonexistentToken.AccessorID) assert.Contains(err.Error(), expectedError) } func TestACLEndpoint_Bootstrap(t *testing.T) { t.Parallel() s1, cleanupS1 := TestServer(t, func(c *Config) { c.ACLEnabled = true }) defer cleanupS1() 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_Bootstrap_Reset(t *testing.T) { t.Parallel() dir := tmpDir(t) defer os.RemoveAll(dir) s1, cleanupS1 := TestServer(t, func(c *Config) { c.ACLEnabled = true c.DataDir = dir c.DevMode = false }) defer cleanupS1() 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]) resetIdx := resp.Tokens[0].CreateIndex // Try again, should fail if err := msgpackrpc.CallWithCodec(codec, "ACL.Bootstrap", req, &resp); err == nil { t.Fatalf("expected err") } // Create the reset file output := []byte(fmt.Sprintf("%d", resetIdx)) path := filepath.Join(dir, aclBootstrapReset) assert.Nil(t, ioutil.WriteFile(path, output, 0755)) // Try again, should work with reset 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) // Try again, should fail if err := msgpackrpc.CallWithCodec(codec, "ACL.Bootstrap", req, &resp); err == nil { t.Fatalf("expected err") } } func TestACLEndpoint_UpsertTokens(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request p1 := mock.ACLToken() p1.AccessorID = "" // Blank to create // Lookup the tokens req := &structs.ACLTokenUpsertRequest{ Tokens: []*structs.ACLToken{p1}, WriteRequest: structs.WriteRequest{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.ACLTokenUpsertResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.UpsertTokens", req, &resp); err != nil { t.Fatalf("err: %v", err) } assert.NotEqual(t, uint64(0), resp.Index) // 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, p1.Type, created.Type) assert.Equal(t, p1.Policies, created.Policies) assert.Equal(t, p1.Name, created.Name) // Check we created the token out, err := s1.fsm.State().ACLTokenByAccessorID(nil, created.AccessorID) assert.Nil(t, err) assert.Equal(t, created, out) // Update the token type req.Tokens[0] = created created.Type = "management" created.Policies = nil // Upsert again if err := msgpackrpc.CallWithCodec(codec, "ACL.UpsertTokens", req, &resp); err != nil { t.Fatalf("err: %v", err) } assert.NotEqual(t, uint64(0), resp.Index) // Check we modified the token out, err = s1.fsm.State().ACLTokenByAccessorID(nil, created.AccessorID) assert.Nil(t, err) assert.Equal(t, created, out) } func TestACLEndpoint_UpsertTokens_Invalid(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request p1 := mock.ACLToken() p1.Type = "blah blah" // Lookup the tokens req := &structs.ACLTokenUpsertRequest{ Tokens: []*structs.ACLToken{p1}, WriteRequest: structs.WriteRequest{ Region: "global", AuthToken: root.SecretID, }, } var resp structs.GenericResponse err := msgpackrpc.CallWithCodec(codec, "ACL.UpsertTokens", req, &resp) assert.NotNil(t, err) if !strings.Contains(err.Error(), "client or management") { t.Fatalf("bad: %s", err) } } func TestACLEndpoint_ResolveToken(t *testing.T) { t.Parallel() s1, _, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // Create the register request token := mock.ACLToken() s1.fsm.State().UpsertACLTokens(structs.MsgTypeTestSetup, 1000, []*structs.ACLToken{token}) // Lookup the token get := &structs.ResolveACLTokenRequest{ SecretID: token.SecretID, QueryOptions: structs.QueryOptions{Region: "global"}, } var resp structs.ResolveACLTokenResponse if err := msgpackrpc.CallWithCodec(codec, "ACL.ResolveToken", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp.Index) assert.Equal(t, token, resp.Token) // Lookup non-existing token get.SecretID = uuid.Generate() if err := msgpackrpc.CallWithCodec(codec, "ACL.ResolveToken", get, &resp); err != nil { t.Fatalf("err: %v", err) } assert.Equal(t, uint64(1000), resp.Index) assert.Nil(t, resp.Token) } func TestACLEndpoint_OneTimeToken(t *testing.T) { t.Parallel() s1, root, cleanupS1 := TestACLServer(t, nil) defer cleanupS1() codec := rpcClient(t, s1) testutil.WaitForLeader(t, s1.RPC) // create an ACL token p1 := mock.ACLToken() p1.AccessorID = "" // has to be blank to create aclReq := &structs.ACLTokenUpsertRequest{ Tokens: []*structs.ACLToken{p1}, WriteRequest: structs.WriteRequest{ Region: "global", AuthToken: root.SecretID, }, } var aclResp structs.ACLTokenUpsertResponse err := msgpackrpc.CallWithCodec(codec, "ACL.UpsertTokens", aclReq, &aclResp) require.NoError(t, err) aclToken := aclResp.Tokens[0] // Generate a one-time token for this ACL token upReq := &structs.OneTimeTokenUpsertRequest{ WriteRequest: structs.WriteRequest{ Region: "global", AuthToken: aclToken.SecretID, }} var upResp structs.OneTimeTokenUpsertResponse // Call the upsert RPC err = msgpackrpc.CallWithCodec(codec, "ACL.UpsertOneTimeToken", upReq, &upResp) require.NoError(t, err) result := upResp.OneTimeToken require.True(t, time.Now().Before(result.ExpiresAt)) require.Equal(t, aclToken.AccessorID, result.AccessorID) // make sure we can get it back out ott, err := s1.fsm.State().OneTimeTokenBySecret(nil, result.OneTimeSecretID) require.NoError(t, err) require.NotNil(t, ott) exReq := &structs.OneTimeTokenExchangeRequest{ OneTimeSecretID: result.OneTimeSecretID, WriteRequest: structs.WriteRequest{ Region: "global", // note: not authenticated! }} var exResp structs.OneTimeTokenExchangeResponse // Call the exchange RPC err = msgpackrpc.CallWithCodec(codec, "ACL.ExchangeOneTimeToken", exReq, &exResp) require.NoError(t, err) token := exResp.Token require.Equal(t, aclToken.AccessorID, token.AccessorID) require.Equal(t, aclToken.SecretID, token.SecretID) // Make sure the one-time token is gone ott, err = s1.fsm.State().OneTimeTokenBySecret(nil, result.OneTimeSecretID) require.NoError(t, err) require.Nil(t, ott) // directly write the OTT to the state store so that we can write an // expired OTT, and query to ensure it's been written index := exResp.Index index += 10 ott = &structs.OneTimeToken{ OneTimeSecretID: uuid.Generate(), AccessorID: token.AccessorID, ExpiresAt: time.Now().Add(-1 * time.Minute), } err = s1.fsm.State().UpsertOneTimeToken(structs.MsgTypeTestSetup, index, ott) require.NoError(t, err) ott, err = s1.fsm.State().OneTimeTokenBySecret(nil, ott.OneTimeSecretID) require.NoError(t, err) require.NotNil(t, ott) // Call the exchange RPC; we should not get an exchange for an expired // token err = msgpackrpc.CallWithCodec(codec, "ACL.ExchangeOneTimeToken", exReq, &exResp) require.EqualError(t, err, structs.ErrPermissionDenied.Error()) // expired token should be left in place (until GC comes along) ott, err = s1.fsm.State().OneTimeTokenBySecret(nil, ott.OneTimeSecretID) require.NoError(t, err) require.NotNil(t, ott) // Call the delete RPC, should fail without proper auth expReq := &structs.OneTimeTokenExpireRequest{ WriteRequest: structs.WriteRequest{ Region: "global", AuthToken: aclToken.SecretID, }, } err = msgpackrpc.CallWithCodec(codec, "ACL.ExpireOneTimeTokens", expReq, &structs.GenericResponse{}) require.EqualError(t, err, structs.ErrPermissionDenied.Error(), "one-time token garbage collection requires management ACL") // should not have caused an expiration either! ott, err = s1.fsm.State().OneTimeTokenBySecret(nil, ott.OneTimeSecretID) require.NoError(t, err) require.NotNil(t, ott) // Call with correct permissions expReq.WriteRequest.AuthToken = root.SecretID err = msgpackrpc.CallWithCodec(codec, "ACL.ExpireOneTimeTokens", expReq, &structs.GenericResponse{}) require.NoError(t, err) // Now the expired OTT should be gone ott, err = s1.fsm.State().OneTimeTokenBySecret(nil, result.OneTimeSecretID) require.NoError(t, err) require.Nil(t, ott) }