acl: JWT auth CLI (#16532)

This commit is contained in:
Piotr Kazmierczak
2023-03-20 10:33:46 +01:00
parent 2d35911415
commit 46ee09ca7f
8 changed files with 62 additions and 15 deletions

View File

@@ -84,14 +84,21 @@ func formatAuthMethod(authMethod *api.ACLAuthMethod) string {
func formatAuthMethodConfig(config *api.ACLAuthMethodConfig) string {
out := []string{
fmt.Sprintf("JWT Validation Public Keys|%s", strings.Join(config.JWTValidationPubKeys, ",")),
fmt.Sprintf("JWKS URL|%s", config.JWKSURL),
fmt.Sprintf("OIDC Discovery URL|%s", config.OIDCDiscoveryURL),
fmt.Sprintf("OIDC Client ID|%s", config.OIDCClientID),
fmt.Sprintf("OIDC Client Secret|%s", config.OIDCClientSecret),
fmt.Sprintf("OIDC Scopes|%s", strings.Join(config.OIDCScopes, ",")),
fmt.Sprintf("Bound audiences|%s", strings.Join(config.BoundAudiences, ",")),
fmt.Sprintf("Bound issuer|%s", strings.Join(config.BoundIssuer, ",")),
fmt.Sprintf("Allowed redirects URIs|%s", strings.Join(config.AllowedRedirectURIs, ",")),
fmt.Sprintf("Discovery CA pem|%s", strings.Join(config.DiscoveryCaPem, ",")),
fmt.Sprintf("JWKS CA cert|%s", config.JWKSCACert),
fmt.Sprintf("Signing algorithms|%s", strings.Join(config.SigningAlgs, ",")),
fmt.Sprintf("Expiration Leeway|%s", config.ExpirationLeeway.String()),
fmt.Sprintf("NotBefore Leeway|%s", config.NotBeforeLeeway.String()),
fmt.Sprintf("ClockSkew Leeway|%s", config.ClockSkewLeeway.String()),
fmt.Sprintf("Claim mappings|%s", strings.Join(formatMap(config.ClaimMappings), "; ")),
fmt.Sprintf("List claim mappings|%s", strings.Join(formatMap(config.ListClaimMappings), "; ")),
}

View File

@@ -50,8 +50,7 @@ ACL Auth Method Create Options:
between 1-128 characters and is a required parameter.
-type
Sets the type of the auth method. Currently the only supported type is
'OIDC'.
Sets the type of the auth method. Supported types are 'OIDC' and 'JWT'.
-max-token-ttl
Sets the duration of time all tokens created by this auth method should be
@@ -83,7 +82,7 @@ func (a *ACLAuthMethodCreateCommand) AutocompleteFlags() complete.Flags {
return mergeAutocompleteFlags(a.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-name": complete.PredictAnything,
"-type": complete.PredictSet("OIDC"),
"-type": complete.PredictSet("OIDC", "JWT"),
"-max-token-ttl": complete.PredictAnything,
"-token-locality": complete.PredictSet("local", "global"),
"-default": complete.PredictSet("true", "false"),
@@ -140,8 +139,8 @@ func (a *ACLAuthMethodCreateCommand) Run(args []string) int {
a.Ui.Error("Max token TTL must be set to a value between min and max TTL configured for the server.")
return 1
}
if strings.ToUpper(a.methodType) != "OIDC" {
a.Ui.Error("ACL auth method type must be set to 'OIDC'")
if !slices.Contains([]string{"OIDC", "JWT"}, strings.ToUpper(a.methodType)) {
a.Ui.Error("ACL auth method type must be set to 'OIDC' or 'JWT'")
return 1
}
if len(a.config) == 0 {

View File

@@ -64,7 +64,7 @@ func TestACLAuthMethodCreateCommand_Run(t *testing.T) {
args := []string{
"-address=" + url, "-token=" + rootACLToken.SecretID, "-name=acl-auth-method-cli-test",
"-type=OIDC", "-token-locality=global", "-default=true", "-max-token-ttl=3600s",
"-config={\"OIDCDiscoveryURL\":\"http://example.com\"}",
"-config={\"OIDCDiscoveryURL\":\"http://example.com\", \"ExpirationLeeway\": \"1h\"}",
}
must.Eq(t, 0, cmd.Run(args))
s := ui.OutputWriter.String()

View File

@@ -46,8 +46,7 @@ General Options:
ACL Auth Method Update Options:
-type
Updates the type of the auth method. Currently the only supported type is
'OIDC'.
Updates the type of the auth method. Supported types are 'OIDC' and 'JWT'.
-max-token-ttl
Updates the duration of time all tokens created by this auth method should be
@@ -79,7 +78,7 @@ ACL Auth Method Update Options:
func (a *ACLAuthMethodUpdateCommand) AutocompleteFlags() complete.Flags {
return mergeAutocompleteFlags(a.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-type": complete.PredictSet("OIDC"),
"-type": complete.PredictSet("OIDC", "JWT"),
"-max-token-ttl": complete.PredictAnything,
"-token-locality": complete.PredictSet("local", "global"),
"-default": complete.PredictSet("true", "false"),
@@ -161,8 +160,8 @@ func (a *ACLAuthMethodUpdateCommand) Run(args []string) int {
}
if slices.Contains(setFlags, "type") {
if strings.ToLower(a.methodType) != "oidc" {
a.Ui.Error("ACL auth method type must be set to 'OIDC'")
if !slices.Contains([]string{"OIDC", "JWT"}, strings.ToUpper(a.methodType)) {
a.Ui.Error("ACL auth method type must be set to 'OIDC' or 'JWT'")
return 1
}
updatedMethod.Type = a.methodType

View File

@@ -25,6 +25,7 @@ type LoginCommand struct {
authMethodType string // deprecated in 1.5.2, left for backwards compat
authMethodName string
callbackAddr string
loginToken string
template string
json bool
@@ -52,6 +53,10 @@ Login Options:
The address to use for the local OIDC callback server. This should be given
in the form of <IP>:<PORT> and defaults to "localhost:4649".
-login-token
Login token used for authentication that will be exchanged for a Nomad ACL
Token. It is only required if using auth method type other than OIDC.
-json
Output the ACL token in JSON format.
@@ -71,6 +76,7 @@ func (l *LoginCommand) AutocompleteFlags() complete.Flags {
complete.Flags{
"-method": complete.PredictAnything,
"-oidc-callback-addr": complete.PredictAnything,
"-login-token": complete.PredictAnything,
"-json": complete.PredictNothing,
"-t": complete.PredictAnything,
})
@@ -86,6 +92,7 @@ func (l *LoginCommand) Run(args []string) int {
flags.Usage = func() { l.Ui.Output(l.Help()) }
flags.StringVar(&l.authMethodName, "method", "", "")
flags.StringVar(&l.authMethodType, "type", "", "")
flags.StringVar(&l.loginToken, "login-token", "", "")
flags.StringVar(&l.callbackAddr, "oidc-callback-addr", "localhost:4649", "")
flags.BoolVar(&l.json, "json", false, "")
flags.StringVar(&l.template, "t", "", "")
@@ -154,6 +161,12 @@ func (l *LoginCommand) Run(args []string) int {
}
}
// Make sure we got the login token if we're not using OIDC
if methodType != api.ACLAuthMethodTypeOIDC && l.loginToken == "" {
l.Ui.Error("You need to provide a login token.")
return 1
}
// Each login type should implement a function which matches this signature
// for the specific login implementation. This allows the command to have
// reusable and generic handling of errors and outputs.
@@ -162,6 +175,8 @@ func (l *LoginCommand) Run(args []string) int {
switch methodType {
case api.ACLAuthMethodTypeOIDC:
authFn = l.loginOIDC
case api.ACLAuthMethodTypeJWT:
authFn = l.loginJWT
default:
l.Ui.Error(fmt.Sprintf("Unsupported authentication type %q", methodType))
return 1
@@ -244,6 +259,15 @@ func (l *LoginCommand) loginOIDC(ctx context.Context, client *api.Client) (*api.
return token, err
}
func (l *LoginCommand) loginJWT(ctx context.Context, client *api.Client) (*api.ACLToken, error) {
authArgs := api.ACLLoginRequest{
AuthMethodName: l.authMethodName,
LoginToken: l.loginToken,
}
token, _, err := client.ACLAuth().Login(&authArgs, nil)
return token, err
}
const (
// oidcErrorVisitURLMsg is a message to show users when opening the OIDC
// provider URL automatically fails. This type of message is otherwise not

View File

@@ -5,6 +5,7 @@ import (
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/command/agent"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/testutil"
"github.com/mitchellh/cli"
"github.com/shoenig/test/must"
@@ -51,6 +52,25 @@ func TestLoginCommand_Run(t *testing.T) {
ui.OutputWriter.Reset()
ui.ErrorWriter.Reset()
// Store a default auth method
state := srv.Agent.Server().State()
method := &structs.ACLAuthMethod{
Name: "test-auth-method",
Default: true,
Type: "JWT",
Config: &structs.ACLAuthMethodConfig{
OIDCDiscoveryURL: "http://example.com",
},
}
method.SetHash()
must.NoError(t, state.UpsertACLAuthMethods(1000, []*structs.ACLAuthMethod{method}))
// Try logging in with non-OIDC method and no token (expected error)
must.Eq(t, 1, cmd.Run([]string{"-address=" + agentURL}))
must.StrContains(t, ui.ErrorWriter.String(), "You need to provide a login token.")
ui.OutputWriter.Reset()
ui.ErrorWriter.Reset()
// TODO(jrasell) find a way to test the full login flow from the CLI
// perspective.
}

View File

@@ -29,8 +29,7 @@ via flags detailed below.
- `-description`: A free form text description of the auth-method that must not exceed
256 characters.
- `-type`: Sets the type of the auth method. Currently the only supported type
is `OIDC`.
- `-type`: Sets the type of the auth method. Supported types are `OIDC` and `JWT`.
- `-max-token-ttl`: Sets the duration of time all tokens created by this auth
method should be valid for.

View File

@@ -37,8 +37,7 @@ The `acl auth-method update` command requires an existing method's name.
to the command. Instead, overwrite all fields with the exception of the role
ID which is immutable.
- `-type`: Updates the type of the auth method. Currently the only supported
type is `OIDC`.
- `-type`: Updates the type of the auth method. Supported types are `OIDC` and `JWT`.
- `-max-token-ttl`: Updates the duration of time all tokens created by this auth
method should be valid for.