Skip to content

Commit

Permalink
acl: allow auth methods created in the primary datacenter to optional…
Browse files Browse the repository at this point in the history
…ly create global tokens (#7899)
  • Loading branch information
rboyer authored Jun 1, 2020
1 parent a78aa80 commit 833211c
Show file tree
Hide file tree
Showing 11 changed files with 266 additions and 23 deletions.
20 changes: 17 additions & 3 deletions agent/consul/acl_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,9 +442,6 @@ func (a *ACL) tokenSetInternal(args *structs.ACLTokenSetRequest, reply *structs.
if token.AuthMethod == "" {
return fmt.Errorf("AuthMethod field is required during Login")
}
if !token.Local {
return fmt.Errorf("Cannot create Global token via Login")
}
} else {
if token.AuthMethod != "" {
return fmt.Errorf("AuthMethod field is disallowed outside of Login")
Expand Down Expand Up @@ -2128,6 +2125,16 @@ func (a *ACL) AuthMethodSet(args *structs.ACLAuthMethodSetRequest, reply *struct
}
}

switch method.TokenLocality {
case "local", "":
case "global":
if !a.srv.InACLDatacenter() {
return fmt.Errorf("Invalid Auth Method: TokenLocality 'global' can only be used in the primary datacenter")
}
default:
return fmt.Errorf("Invalid Auth Method: TokenLocality should be one of 'local' or 'global'")
}

// Instantiate a validator but do not cache it yet. This will validate the
// configuration.
validator, err := authmethod.NewValidator(a.srv.logger, method)
Expand Down Expand Up @@ -2386,6 +2393,13 @@ func (a *ACL) tokenSetFromAuthMethod(
EnterpriseMeta: *targetMeta,
}

if method.TokenLocality == "global" {
if !a.srv.InACLDatacenter() {
return errors.New("creating global tokens via auth methods is only permitted in the primary datacenter")
}
createReq.ACLToken.Local = false
}

createReq.ACLToken.ACLAuthMethodEnterpriseMeta.FillWithEnterpriseMeta(entMeta)

// 5. return token information like a TokenCreate would
Expand Down
102 changes: 102 additions & 0 deletions agent/consul/acl_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5106,6 +5106,108 @@ func TestACLEndpoint_Login_with_MaxTokenTTL(t *testing.T) {
require.Equal(t, got, expect)
}

func TestACLEndpoint_Login_with_TokenLocality(t *testing.T) {
t.Parallel()

dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.ACLDatacenter = "dc1"
c.ACLsEnabled = true
c.ACLMasterToken = "root"
})
defer os.RemoveAll(dir1)
defer s1.Shutdown()
codec := rpcClient(t, s1)
defer codec.Close()

testrpc.WaitForLeader(t, s1.RPC, "dc1")

acl := ACL{srv: s1}

testSessionID := testauth.StartSession()
defer testauth.ResetSession(testSessionID)

testauth.InstallSessionToken(
testSessionID,
"fake-web", // no rules
"default", "web", "abc123",
)

cases := map[string]struct {
tokenLocality string
expectLocal bool
}{
"empty": {tokenLocality: "", expectLocal: true},
"local": {tokenLocality: "local", expectLocal: true},
"global": {tokenLocality: "global", expectLocal: false},
}

for name, tc := range cases {
tc := tc
t.Run(name, func(t *testing.T) {
method, err := upsertTestCustomizedAuthMethod(codec, "root", "dc1", func(method *structs.ACLAuthMethod) {
method.TokenLocality = tc.tokenLocality
method.Config = map[string]interface{}{
"SessionID": testSessionID,
}
})
require.NoError(t, err)

_, err = upsertTestBindingRule(
codec, "root", "dc1", method.Name,
"",
structs.BindingRuleBindTypeService,
"web",
)
require.NoError(t, err)

// Create a token.
req := structs.ACLLoginRequest{
Auth: &structs.ACLLoginParams{
AuthMethod: method.Name,
BearerToken: "fake-web",
Meta: map[string]string{"pod": "pod1"},
},
Datacenter: "dc1",
}

resp := structs.ACLToken{}
require.NoError(t, acl.Login(&req, &resp))

secretID := resp.SecretID

got := &resp
got.CreateIndex = 0
got.ModifyIndex = 0
got.AccessorID = ""
got.SecretID = ""
got.Hash = nil

defaultEntMeta := structs.DefaultEnterpriseMeta()
expect := &structs.ACLToken{
AuthMethod: method.Name,
Description: `token created via login: {"pod":"pod1"}`,
Local: tc.expectLocal,
CreateTime: got.CreateTime,
ServiceIdentities: []*structs.ACLServiceIdentity{
{ServiceName: "web"},
},
EnterpriseMeta: *defaultEntMeta,
}
expect.ACLAuthMethodEnterpriseMeta.FillWithEnterpriseMeta(defaultEntMeta)
require.Equal(t, got, expect)

// Now turn around and nuke it.
logoutReq := structs.ACLLogoutRequest{
Datacenter: "dc1",
WriteRequest: structs.WriteRequest{Token: secretID},
}

var ignored bool
require.NoError(t, acl.Logout(&logoutReq, &ignored))
})
}
}

func TestACLEndpoint_Login_k8s(t *testing.T) {
t.Parallel()

Expand Down
4 changes: 4 additions & 0 deletions agent/structs/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,10 @@ type ACLAuthMethod struct {
// MaxTokenTTL this is the maximum life of a token created by this method.
MaxTokenTTL time.Duration `json:",omitempty"`

// TokenLocality defines the kind of token that this auth method produces.
// This can be either 'local' or 'global'. If empty 'local' is assumed.
TokenLocality string `json:",omitempty"`

// Configuration is arbitrary configuration for the auth method. This
// should only contain primitive values and containers (such as lists and
// maps).
Expand Down
4 changes: 4 additions & 0 deletions api/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ type ACLAuthMethod struct {
Description string `json:",omitempty"`
MaxTokenTTL time.Duration `json:",omitempty"`

// TokenLocality defines the kind of token that this auth method produces.
// This can be either 'local' or 'global'. If empty 'local' is assumed.
TokenLocality string `json:",omitempty"`

// Configuration is arbitrary configuration for the auth method. This
// should only contain primitive values and containers (such as lists and
// maps).
Expand Down
17 changes: 13 additions & 4 deletions command/acl/authmethod/create/authmethod_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type cmd struct {
displayName string
description string
maxTokenTTL time.Duration
tokenLocality string
config string

k8sHost string
Expand Down Expand Up @@ -87,6 +88,13 @@ func (c *cmd) init() {
0,
"Duration of time all tokens created by this auth method should be valid for",
)
c.flags.StringVar(
&c.tokenLocality,
"token-locality",
"",
"Defines the kind of token that this auth method should produce. "+
"This can be either 'local' or 'global'. If empty the value of 'local' is assumed.",
)

c.flags.StringVar(
&c.k8sHost,
Expand Down Expand Up @@ -157,10 +165,11 @@ func (c *cmd) Run(args []string) int {
}

newAuthMethod := &api.ACLAuthMethod{
Type: c.authMethodType,
Name: c.name,
DisplayName: c.displayName,
Description: c.description,
Type: c.authMethodType,
Name: c.name,
DisplayName: c.displayName,
Description: c.description,
TokenLocality: c.tokenLocality,
}
if c.maxTokenTTL > 0 {
newAuthMethod.MaxTokenTTL = c.maxTokenTTL
Expand Down
79 changes: 79 additions & 0 deletions command/acl/authmethod/create/authmethod_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,36 @@ func TestAuthMethodCreateCommand(t *testing.T) {
}
require.Equal(t, expect, got)
})

t.Run("create testing with token type global", func(t *testing.T) {
name := getTestName(t)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-token=root",
"-type=testing",
"-name", name,
"-description=desc",
"-display-name=display",
"-token-locality=global",
}

ui := cli.NewMockUi()
cmd := New(ui)

code := cmd.Run(args)
require.Equal(t, code, 0, "err: "+ui.ErrorWriter.String())
require.Empty(t, ui.ErrorWriter.String())

got := getTestMethod(t, client, name)
expect := &api.ACLAuthMethod{
Name: name,
Type: "testing",
DisplayName: "display",
Description: "desc",
TokenLocality: "global",
}
require.Equal(t, expect, got)
})
}

func TestAuthMethodCreateCommand_JSON(t *testing.T) {
Expand Down Expand Up @@ -272,6 +302,55 @@ func TestAuthMethodCreateCommand_JSON(t *testing.T) {
"Config": nil,
}, raw)
})

t.Run("create testing with token type global", func(t *testing.T) {
name := getTestName(t)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-token=root",
"-type=testing",
"-name", name,
"-description=desc",
"-display-name=display",
"-token-locality=global",
"-format=json",
}

ui := cli.NewMockUi()
cmd := New(ui)

code := cmd.Run(args)
out := ui.OutputWriter.String()

require.Equal(t, code, 0)
require.Empty(t, ui.ErrorWriter.String())
require.Contains(t, out, name)

got := getTestMethod(t, client, name)
expect := &api.ACLAuthMethod{
Name: name,
Type: "testing",
DisplayName: "display",
Description: "desc",
TokenLocality: "global",
}
require.Equal(t, expect, got)

var raw map[string]interface{}
require.NoError(t, json.Unmarshal([]byte(out), &raw))
delete(raw, "CreateIndex")
delete(raw, "ModifyIndex")
delete(raw, "Namespace")

require.Equal(t, map[string]interface{}{
"Name": name,
"Type": "testing",
"DisplayName": "display",
"Description": "desc",
"TokenLocality": "global",
"Config": nil,
}, raw)
})
}

func TestAuthMethodCreateCommand_k8s(t *testing.T) {
Expand Down
19 changes: 11 additions & 8 deletions command/acl/authmethod/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,20 @@ type prettyFormatter struct {
func (f *prettyFormatter) FormatAuthMethod(method *api.ACLAuthMethod) (string, error) {
var buffer bytes.Buffer

buffer.WriteString(fmt.Sprintf("Name: %s\n", method.Name))
buffer.WriteString(fmt.Sprintf("Type: %s\n", method.Type))
buffer.WriteString(fmt.Sprintf("Name: %s\n", method.Name))
buffer.WriteString(fmt.Sprintf("Type: %s\n", method.Type))
if method.Namespace != "" {
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", method.Namespace))
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", method.Namespace))
}
if method.DisplayName != "" {
buffer.WriteString(fmt.Sprintf("DisplayName: %s\n", method.DisplayName))
buffer.WriteString(fmt.Sprintf("DisplayName: %s\n", method.DisplayName))
}
buffer.WriteString(fmt.Sprintf("Description: %s\n", method.Description))
buffer.WriteString(fmt.Sprintf("Description: %s\n", method.Description))
if method.MaxTokenTTL > 0 {
buffer.WriteString(fmt.Sprintf("MaxTokenTTL: %s\n", method.MaxTokenTTL))
buffer.WriteString(fmt.Sprintf("MaxTokenTTL: %s\n", method.MaxTokenTTL))
}
if method.TokenLocality != "" {
buffer.WriteString(fmt.Sprintf("TokenLocality: %s\n", method.TokenLocality))
}
if len(method.NamespaceRules) > 0 {
buffer.WriteString(fmt.Sprintln("NamespaceRules:"))
Expand All @@ -69,8 +72,8 @@ func (f *prettyFormatter) FormatAuthMethod(method *api.ACLAuthMethod) (string, e
}
}
if f.showMeta {
buffer.WriteString(fmt.Sprintf("Create Index: %d\n", method.CreateIndex))
buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", method.ModifyIndex))
buffer.WriteString(fmt.Sprintf("Create Index: %d\n", method.CreateIndex))
buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", method.ModifyIndex))
}
buffer.WriteString(fmt.Sprintln("Config:"))
output, err := json.MarshalIndent(method.Config, "", " ")
Expand Down
28 changes: 20 additions & 8 deletions command/acl/authmethod/update/authmethod_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ type cmd struct {

name string

displayName string
description string
maxTokenTTL time.Duration
config string
displayName string
description string
maxTokenTTL time.Duration
tokenLocality string
config string

k8sHost string
k8sCACert string
Expand Down Expand Up @@ -85,6 +86,13 @@ func (c *cmd) init() {
0,
"Duration of time all tokens created by this auth method should be valid for",
)
c.flags.StringVar(
&c.tokenLocality,
"token-locality",
"",
"Defines the kind of token that this auth method should produce. "+
"This can be either 'local' or 'global'. If empty the value of 'local' is assumed.",
)

c.flags.StringVar(
&c.config,
Expand Down Expand Up @@ -179,10 +187,11 @@ func (c *cmd) Run(args []string) int {
var method *api.ACLAuthMethod
if c.noMerge {
method = &api.ACLAuthMethod{
Name: currentAuthMethod.Name,
Type: currentAuthMethod.Type,
DisplayName: c.displayName,
Description: c.description,
Name: currentAuthMethod.Name,
Type: currentAuthMethod.Type,
DisplayName: c.displayName,
Description: c.description,
TokenLocality: c.tokenLocality,
}
if c.maxTokenTTL > 0 {
method.MaxTokenTTL = c.maxTokenTTL
Expand Down Expand Up @@ -239,6 +248,9 @@ func (c *cmd) Run(args []string) int {
if c.maxTokenTTL > 0 {
method.MaxTokenTTL = c.maxTokenTTL
}
if c.tokenLocality != "" {
method.TokenLocality = c.tokenLocality
}
if err := c.enterprisePopulateAuthMethod(method); err != nil {
c.UI.Error(err.Error())
return 1
Expand Down
Loading

0 comments on commit 833211c

Please sign in to comment.