acl: sso auth methods cli commands (#15322)
This PR implements CLI commands to interact with SSO auth methods. This PR is part of the SSO work captured under ☂️ ticket #13120.
This commit is contained in:
parent
726d419da1
commit
db9316c4d3
|
@ -0,0 +1,103 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/nomad/api"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// Ensure ACLAuthMethodCommand satisfies the cli.Command interface.
|
||||
var _ cli.Command = &ACLAuthMethodCommand{}
|
||||
|
||||
// ACLAuthMethodCommand implements cli.Command.
|
||||
type ACLAuthMethodCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
// Help satisfies the cli.Command Help function.
|
||||
func (a *ACLAuthMethodCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad acl auth-method <subcommand> [options] [args]
|
||||
|
||||
This command groups subcommands for interacting with ACL auth methods.
|
||||
|
||||
Create an ACL auth method:
|
||||
|
||||
$ nomad acl auth-method create -name="name" -type="OIDC" -max-token-ttl="3600s"
|
||||
|
||||
List all ACL auth methods:
|
||||
|
||||
$ nomad acl auth-method list
|
||||
|
||||
Lookup a specific ACL auth method:
|
||||
|
||||
$ nomad acl auth-method info <acl_auth_method_name>
|
||||
|
||||
Update an ACL auth method:
|
||||
|
||||
$ nomad acl auth-method update -type="updated-type" <acl_auth_method_name>
|
||||
|
||||
Delete an ACL auth method:
|
||||
|
||||
$ nomad acl auth-method delete <acl_auth_method_name>
|
||||
|
||||
Please see the individual subcommand help for detailed usage information.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
// Synopsis satisfies the cli.Command Synopsis function.
|
||||
func (a *ACLAuthMethodCommand) Synopsis() string { return "Interact with ACL auth methods" }
|
||||
|
||||
// Name returns the name of this command.
|
||||
func (a *ACLAuthMethodCommand) Name() string { return "acl auth-method" }
|
||||
|
||||
// Run satisfies the cli.Command Run function.
|
||||
func (a *ACLAuthMethodCommand) Run(_ []string) int { return cli.RunResultHelp }
|
||||
|
||||
// formatAuthMethod formats and converts the ACL auth method API object into a
|
||||
// string KV representation suitable for console output.
|
||||
func formatAuthMethod(authMethod *api.ACLAuthMethod) string {
|
||||
out := []string{
|
||||
fmt.Sprintf("Name|%s", authMethod.Name),
|
||||
fmt.Sprintf("Type|%s", authMethod.Type),
|
||||
fmt.Sprintf("Locality|%s", authMethod.TokenLocality),
|
||||
fmt.Sprintf("MaxTokenTTL|%s", authMethod.MaxTokenTTL.String()),
|
||||
fmt.Sprintf("Default|%t", authMethod.Default),
|
||||
}
|
||||
|
||||
if authMethod.Config != nil {
|
||||
out = append(out, formatAuthMethodConfig(authMethod.Config)...)
|
||||
}
|
||||
out = append(out,
|
||||
[]string{fmt.Sprintf("Create Index|%d", authMethod.CreateIndex),
|
||||
fmt.Sprintf("Modify Index|%d", authMethod.ModifyIndex),
|
||||
}...,
|
||||
)
|
||||
|
||||
return formatKV(out)
|
||||
}
|
||||
|
||||
func formatAuthMethodConfig(config *api.ACLAuthMethodConfig) []string {
|
||||
return []string{
|
||||
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("Bound audiences|%s", strings.Join(config.BoundAudiences, ",")),
|
||||
fmt.Sprintf("Allowed redirects URIs|%s", strings.Join(config.AllowedRedirectURIs, ",")),
|
||||
fmt.Sprintf("Discovery CA pem|%s", strings.Join(config.DiscoveryCaPem, ",")),
|
||||
fmt.Sprintf("Signing algorithms|%s", strings.Join(config.SigningAlgs, ",")),
|
||||
fmt.Sprintf("Claim mappings|%s", formatMap(config.ClaimMappings)),
|
||||
fmt.Sprintf("List claim mappings|%s", formatMap(config.ListClaimMappings)),
|
||||
}
|
||||
}
|
||||
|
||||
func formatMap(m map[string]string) string {
|
||||
out := []string{}
|
||||
for k, v := range m {
|
||||
out = append(out, fmt.Sprintf("%s/%s", k, v))
|
||||
}
|
||||
return formatKV(out)
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/nomad/api"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Ensure ACLAuthMethodCreateCommand satisfies the cli.Command interface.
|
||||
var _ cli.Command = &ACLAuthMethodCreateCommand{}
|
||||
|
||||
// ACLAuthMethodCreateCommand implements cli.Command.
|
||||
type ACLAuthMethodCreateCommand struct {
|
||||
Meta
|
||||
|
||||
name string
|
||||
methodType string
|
||||
tokenLocality string
|
||||
maxTokenTTL time.Duration
|
||||
isDefault bool
|
||||
config string
|
||||
|
||||
testStdin io.Reader
|
||||
}
|
||||
|
||||
// Help satisfies the cli.Command Help function.
|
||||
func (a *ACLAuthMethodCreateCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad acl auth-method create [options]
|
||||
|
||||
Create is used to create new ACL auth methods. Use requires a management token.
|
||||
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
|
||||
|
||||
ACL Auth Method Create Options:
|
||||
|
||||
-name
|
||||
Sets the human readable name for the ACL auth method. The name must be
|
||||
between 1-128 characters and is a required parameter.
|
||||
|
||||
-type
|
||||
Sets the type of the auth method. Currently the only supported type is
|
||||
'OIDC'.
|
||||
|
||||
-max-token-ttl
|
||||
Sets the duration of time all tokens created by this auth method should be
|
||||
valid for.
|
||||
|
||||
-token-locality
|
||||
Defines the kind of token that this auth method should produce. This can be
|
||||
either 'local' or 'global'.
|
||||
|
||||
-default
|
||||
Specifies whether this auth method should be treated as a default one in
|
||||
case no auth method is explicitly specified for a login command.
|
||||
|
||||
-config
|
||||
Auth method configuration in JSON format. May be prefixed with '@' to
|
||||
indicate that the value is a file path to load the config from. '-' may
|
||||
also be given to indicate that the config is available on stdin.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (a *ACLAuthMethodCreateCommand) AutocompleteFlags() complete.Flags {
|
||||
return mergeAutocompleteFlags(a.Meta.AutocompleteFlags(FlagSetClient),
|
||||
complete.Flags{
|
||||
"-name": complete.PredictAnything,
|
||||
"-type": complete.PredictSet("OIDC"),
|
||||
"-max-token-ttl": complete.PredictAnything,
|
||||
"-token-locality": complete.PredictSet("local", "global"),
|
||||
"-default": complete.PredictSet("true", "false"),
|
||||
"-config": complete.PredictNothing,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLAuthMethodCreateCommand) AutocompleteArgs() complete.Predictor {
|
||||
return complete.PredictNothing
|
||||
}
|
||||
|
||||
// Synopsis satisfies the cli.Command Synopsis function.
|
||||
func (a *ACLAuthMethodCreateCommand) Synopsis() string { return "Create a new ACL auth method" }
|
||||
|
||||
// Name returns the name of this command.
|
||||
func (a *ACLAuthMethodCreateCommand) Name() string { return "acl auth-method create" }
|
||||
|
||||
// Run satisfies the cli.Command Run function.
|
||||
func (a *ACLAuthMethodCreateCommand) Run(args []string) int {
|
||||
|
||||
flags := a.Meta.FlagSet(a.Name(), FlagSetClient)
|
||||
flags.Usage = func() { a.Ui.Output(a.Help()) }
|
||||
flags.StringVar(&a.name, "name", "", "")
|
||||
flags.StringVar(&a.methodType, "type", "", "")
|
||||
flags.StringVar(&a.tokenLocality, "token-locality", "", "")
|
||||
flags.DurationVar(&a.maxTokenTTL, "max-token-ttl", 0, "")
|
||||
flags.BoolVar(&a.isDefault, "default", false, "")
|
||||
flags.StringVar(&a.config, "config", "", "")
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check that we got no arguments.
|
||||
if len(flags.Args()) != 0 {
|
||||
a.Ui.Error("This command takes no arguments")
|
||||
a.Ui.Error(commandErrorText(a))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Perform some basic validation
|
||||
if a.name == "" {
|
||||
a.Ui.Error("ACL auth method name must be specified using the -name flag")
|
||||
return 1
|
||||
}
|
||||
if !slices.Contains([]string{"global", "local"}, a.tokenLocality) {
|
||||
a.Ui.Error("Token locality must be set to either 'local' or 'global'")
|
||||
return 1
|
||||
}
|
||||
if a.maxTokenTTL < 1 {
|
||||
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'")
|
||||
return 1
|
||||
}
|
||||
if len(a.config) == 0 {
|
||||
a.Ui.Error("Must provide ACL auth method config in JSON format")
|
||||
return 1
|
||||
}
|
||||
|
||||
config, err := loadDataSource(a.config, a.testStdin)
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error loading configuration: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
configJSON := api.ACLAuthMethodConfig{}
|
||||
err = json.Unmarshal([]byte(config), &configJSON)
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Unable to parse config: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Set up the auth method with the passed parameters.
|
||||
authMethod := api.ACLAuthMethod{
|
||||
Name: a.name,
|
||||
Type: strings.ToUpper(a.methodType),
|
||||
TokenLocality: a.tokenLocality,
|
||||
MaxTokenTTL: a.maxTokenTTL,
|
||||
Default: a.isDefault,
|
||||
Config: &configJSON,
|
||||
}
|
||||
|
||||
// Get the HTTP client.
|
||||
client, err := a.Meta.Client()
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Create the auth method via the API.
|
||||
_, err = client.ACLAuthMethods().Create(&authMethod, nil)
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error creating ACL auth method: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
a.Ui.Output(fmt.Sprintf("Created ACL auth method %s", a.name))
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/ci"
|
||||
"github.com/hashicorp/nomad/command/agent"
|
||||
"github.com/hashicorp/nomad/testutil"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/shoenig/test/must"
|
||||
)
|
||||
|
||||
func TestACLAuthMethodCreateCommand_Run(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
// Build a test server with ACLs enabled.
|
||||
srv, _, url := testServer(t, false, func(c *agent.Config) {
|
||||
c.ACL.Enabled = true
|
||||
})
|
||||
defer srv.Shutdown()
|
||||
|
||||
// Wait for the server to start fully and ensure we have a bootstrap token.
|
||||
testutil.WaitForLeader(t, srv.Agent.RPC)
|
||||
rootACLToken := srv.RootToken
|
||||
must.NotNil(t, rootACLToken)
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := &ACLAuthMethodCreateCommand{
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
flagAddress: url,
|
||||
},
|
||||
}
|
||||
|
||||
// Test the basic validation on the command.
|
||||
must.Eq(t, 1, cmd.Run([]string{"-address=" + url, "this-command-does-not-take-args"}))
|
||||
must.StrContains(t, ui.ErrorWriter.String(), "This command takes no arguments")
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
ui.ErrorWriter.Reset()
|
||||
|
||||
must.Eq(t, 1, cmd.Run([]string{"-address=" + url}))
|
||||
must.StrContains(t, ui.ErrorWriter.String(), "ACL auth method name must be specified using the -name flag")
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
ui.ErrorWriter.Reset()
|
||||
|
||||
must.Eq(t, 1, cmd.Run([]string{"-address=" + url, "-name=foobar", "-token-locality=global", "-max-token-ttl=3600s"}))
|
||||
must.StrContains(t, ui.ErrorWriter.String(), "ACL auth method type must be set to 'OIDC'")
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
ui.ErrorWriter.Reset()
|
||||
|
||||
must.Eq(t, 1, cmd.Run([]string{"-address=" + url, "-name=foobar", "-type=OIDC", "-token-locality=global", "-max-token-ttl=3600s"}))
|
||||
must.StrContains(t, ui.ErrorWriter.String(), "Must provide ACL auth method config in JSON format")
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
ui.ErrorWriter.Reset()
|
||||
|
||||
// Create an auth method
|
||||
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\"}",
|
||||
}
|
||||
must.Eq(t, 0, cmd.Run(args))
|
||||
s := ui.OutputWriter.String()
|
||||
must.StrContains(t, s, "acl-auth-method-cli-test")
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
ui.ErrorWriter.Reset()
|
||||
|
||||
// Create an auth method with a config from file
|
||||
configFile, err := os.CreateTemp("", "config.json")
|
||||
defer os.Remove(configFile.Name())
|
||||
must.Nil(t, err)
|
||||
|
||||
conf := map[string]interface{}{"OIDCDiscoveryURL": "http://example.com"}
|
||||
jsonData, err := json.Marshal(conf)
|
||||
must.Nil(t, err)
|
||||
|
||||
_, err = configFile.Write(jsonData)
|
||||
must.Nil(t, err)
|
||||
|
||||
args = []string{
|
||||
"-address=" + url, "-token=" + rootACLToken.SecretID, "-name=acl-auth-method-cli-test",
|
||||
"-type=OIDC", "-token-locality=global", "-default=true", "-max-token-ttl=3600s",
|
||||
fmt.Sprintf("-config=@%s", configFile.Name()),
|
||||
}
|
||||
must.Eq(t, 0, cmd.Run(args))
|
||||
s = ui.OutputWriter.String()
|
||||
must.StrContains(t, s, "acl-auth-method-cli-test")
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
ui.ErrorWriter.Reset()
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
// Ensure ACLAuthMethodDeleteCommand satisfies the cli.Command interface.
|
||||
var _ cli.Command = &ACLAuthMethodDeleteCommand{}
|
||||
|
||||
// ACLAuthMethodDeleteCommand implements cli.Command.
|
||||
type ACLAuthMethodDeleteCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
// Help satisfies the cli.Command Help function.
|
||||
func (a *ACLAuthMethodDeleteCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad acl auth-method delete <acl_method_name>
|
||||
|
||||
Delete is used to delete an existing ACL auth method. Use requires a
|
||||
management token.
|
||||
|
||||
General Options:
|
||||
|
||||
|
||||
` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace)
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (a *ACLAuthMethodDeleteCommand) AutocompleteFlags() complete.Flags {
|
||||
return mergeAutocompleteFlags(a.Meta.AutocompleteFlags(FlagSetClient),
|
||||
complete.Flags{})
|
||||
}
|
||||
|
||||
func (a *ACLAuthMethodDeleteCommand) AutocompleteArgs() complete.Predictor {
|
||||
return complete.PredictNothing
|
||||
}
|
||||
|
||||
// Synopsis satisfies the cli.Command Synopsis function.
|
||||
func (a *ACLAuthMethodDeleteCommand) Synopsis() string { return "Delete an existing ACL auth method" }
|
||||
|
||||
// Name returns the name of this command.
|
||||
func (a *ACLAuthMethodDeleteCommand) Name() string { return "acl auth-method delete" }
|
||||
|
||||
// Run satisfies the cli.Command Run function.
|
||||
func (a *ACLAuthMethodDeleteCommand) Run(args []string) int {
|
||||
|
||||
flags := a.Meta.FlagSet(a.Name(), FlagSetClient)
|
||||
flags.Usage = func() { a.Ui.Output(a.Help()) }
|
||||
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check that the last argument is the auth method name to delete.
|
||||
if len(flags.Args()) != 1 {
|
||||
a.Ui.Error("This command takes one argument: <acl_auth_method_name>")
|
||||
a.Ui.Error(commandErrorText(a))
|
||||
return 1
|
||||
}
|
||||
|
||||
methodName := flags.Args()[0]
|
||||
|
||||
// Get the HTTP client.
|
||||
client, err := a.Meta.Client()
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Delete the specified method
|
||||
_, err = client.ACLAuthMethods().Delete(methodName, nil)
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error deleting ACL auth method: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Give some feedback to indicate the deletion was successful.
|
||||
a.Ui.Output(fmt.Sprintf("ACL auth method %s successfully deleted", methodName))
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/ci"
|
||||
"github.com/hashicorp/nomad/command/agent"
|
||||
"github.com/hashicorp/nomad/nomad/mock"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/shoenig/test/must"
|
||||
)
|
||||
|
||||
func TestACLAuthMethodDeleteCommand(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
config := func(c *agent.Config) {
|
||||
c.ACL.Enabled = true
|
||||
}
|
||||
|
||||
srv, _, url := testServer(t, true, config)
|
||||
defer stopTestAgent(srv)
|
||||
|
||||
state := srv.Agent.Server().State()
|
||||
|
||||
// Bootstrap an initial ACL token
|
||||
token := srv.RootToken
|
||||
must.NotNil(t, token)
|
||||
|
||||
// Create a test auth method
|
||||
method := &structs.ACLAuthMethod{
|
||||
Name: "test-auth-method",
|
||||
}
|
||||
method.SetHash()
|
||||
must.NoError(t, state.UpsertACLAuthMethods(1000, []*structs.ACLAuthMethod{method}))
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := &ACLAuthMethodDeleteCommand{Meta: Meta{Ui: ui, flagAddress: url}}
|
||||
|
||||
// Delete the method without a valid token fails
|
||||
invalidToken := mock.ACLToken()
|
||||
code := cmd.Run([]string{"-address=" + url, "-token=" + invalidToken.SecretID, method.Name})
|
||||
must.One(t, code)
|
||||
|
||||
// Delete the method with a valid management token
|
||||
code = cmd.Run([]string{"-address=" + url, "-token=" + token.SecretID, method.Name})
|
||||
must.Zero(t, code)
|
||||
|
||||
// Check the output
|
||||
out := ui.OutputWriter.String()
|
||||
must.StrContains(t, out, fmt.Sprintf("%s successfully deleted", method.Name))
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
// Ensure ACLAuthMethodInfoCommand satisfies the cli.Command interface.
|
||||
var _ cli.Command = &ACLAuthMethodInfoCommand{}
|
||||
|
||||
// ACLAuthMethodInfoCommand implements cli.Command.
|
||||
type ACLAuthMethodInfoCommand struct {
|
||||
Meta
|
||||
|
||||
json bool
|
||||
tmpl string
|
||||
}
|
||||
|
||||
// Help satisfies the cli.Command Help function.
|
||||
func (a *ACLAuthMethodInfoCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad acl auth-method info [options] <acl_method_name>
|
||||
|
||||
Info is used to fetch information on an existing ACL auth method. Requires a
|
||||
management token.
|
||||
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
|
||||
|
||||
ACL Info Options:
|
||||
|
||||
-json
|
||||
Output the ACL role in a JSON format.
|
||||
|
||||
-t
|
||||
Format and display the ACL role using a Go template.
|
||||
`
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (a *ACLAuthMethodInfoCommand) AutocompleteFlags() complete.Flags {
|
||||
return mergeAutocompleteFlags(a.Meta.AutocompleteFlags(FlagSetClient),
|
||||
complete.Flags{
|
||||
"-json": complete.PredictNothing,
|
||||
"-t": complete.PredictAnything,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLAuthMethodInfoCommand) AutocompleteArgs() complete.Predictor {
|
||||
return complete.PredictNothing
|
||||
}
|
||||
|
||||
// Synopsis satisfies the cli.Command Synopsis function.
|
||||
func (a *ACLAuthMethodInfoCommand) Synopsis() string {
|
||||
return "Fetch information on an existing ACL auth method"
|
||||
}
|
||||
|
||||
// Name returns the name of this command.
|
||||
func (a *ACLAuthMethodInfoCommand) Name() string { return "acl auth-method info" }
|
||||
|
||||
// Run satisfies the cli.Command Run function.
|
||||
func (a *ACLAuthMethodInfoCommand) Run(args []string) int {
|
||||
var json bool
|
||||
var tmpl string
|
||||
|
||||
flags := a.Meta.FlagSet(a.Name(), FlagSetClient)
|
||||
flags.Usage = func() { a.Ui.Output(a.Help()) }
|
||||
flags.BoolVar(&json, "json", false, "")
|
||||
flags.StringVar(&tmpl, "t", "", "")
|
||||
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check that we have exactly one argument.
|
||||
if len(flags.Args()) != 1 {
|
||||
a.Ui.Error("This command takes one argument: <acl_auth_method_name>")
|
||||
a.Ui.Error(commandErrorText(a))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Get the HTTP client.
|
||||
client, err := a.Meta.Client()
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
methodName := flags.Args()[0]
|
||||
|
||||
method, _, apiErr := client.ACLAuthMethods().Get(methodName, nil)
|
||||
|
||||
// Handle any error from the API.
|
||||
if apiErr != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error reading ACL auth method: %s", apiErr))
|
||||
return 1
|
||||
}
|
||||
|
||||
if json || len(tmpl) > 0 {
|
||||
out, err := Format(json, tmpl, method)
|
||||
if err != nil {
|
||||
a.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
a.Ui.Output(out)
|
||||
return 0
|
||||
}
|
||||
|
||||
// Format the output.
|
||||
a.Ui.Output(formatAuthMethod(method))
|
||||
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/ci"
|
||||
"github.com/hashicorp/nomad/command/agent"
|
||||
"github.com/hashicorp/nomad/nomad/mock"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/shoenig/test/must"
|
||||
)
|
||||
|
||||
func TestACLAuthMethodInfoCommand(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
config := func(c *agent.Config) {
|
||||
c.ACL.Enabled = true
|
||||
}
|
||||
|
||||
srv, _, url := testServer(t, true, config)
|
||||
state := srv.Agent.Server().State()
|
||||
defer stopTestAgent(srv)
|
||||
|
||||
// Bootstrap an initial ACL token
|
||||
token := srv.RootToken
|
||||
must.NotNil(t, token)
|
||||
|
||||
// Create a test auth method
|
||||
method := &structs.ACLAuthMethod{
|
||||
Name: "test-auth-method",
|
||||
Config: &structs.ACLAuthMethodConfig{
|
||||
OIDCDiscoveryURL: "http://example.com",
|
||||
},
|
||||
}
|
||||
method.SetHash()
|
||||
must.NoError(t, state.UpsertACLAuthMethods(1000, []*structs.ACLAuthMethod{method}))
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := &ACLAuthMethodInfoCommand{Meta: Meta{Ui: ui, flagAddress: url}}
|
||||
|
||||
// Attempt to get info without a valid management token
|
||||
invalidToken := mock.ACLToken()
|
||||
code := cmd.Run([]string{"-address=" + url, "-token=" + invalidToken.SecretID, method.Name})
|
||||
must.One(t, code)
|
||||
|
||||
// Get info with a valid management token
|
||||
code = cmd.Run([]string{"-address=" + url, "-token=" + token.SecretID, method.Name})
|
||||
must.Zero(t, code)
|
||||
|
||||
// Check the output
|
||||
out := ui.OutputWriter.String()
|
||||
must.StrContains(t, out, method.Name)
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/nomad/api"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
// Ensure ACLAuthMethodListCommand satisfies the cli.Command interface.
|
||||
var _ cli.Command = &ACLAuthMethodListCommand{}
|
||||
|
||||
// ACLAuthMethodListCommand implements cli.Command.
|
||||
type ACLAuthMethodListCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
// Help satisfies the cli.Command Help function.
|
||||
func (a *ACLAuthMethodListCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad acl auth-method list [options]
|
||||
|
||||
List is used to list existing ACL auth methods.
|
||||
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
|
||||
|
||||
ACL List Options:
|
||||
|
||||
-json
|
||||
Output the ACL auth methods in a JSON format.
|
||||
|
||||
-t
|
||||
Format and display the ACL auth methods using a Go template.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (a *ACLAuthMethodListCommand) AutocompleteFlags() complete.Flags {
|
||||
return mergeAutocompleteFlags(a.Meta.AutocompleteFlags(FlagSetClient),
|
||||
complete.Flags{
|
||||
"-json": complete.PredictNothing,
|
||||
"-t": complete.PredictAnything,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLAuthMethodListCommand) AutocompleteArgs() complete.Predictor {
|
||||
return complete.PredictNothing
|
||||
}
|
||||
|
||||
// Synopsis satisfies the cli.Command Synopsis function.
|
||||
func (a *ACLAuthMethodListCommand) Synopsis() string { return "List ACL auth methods" }
|
||||
|
||||
// Name returns the name of this command.
|
||||
func (a *ACLAuthMethodListCommand) Name() string { return "acl auth-method list" }
|
||||
|
||||
// Run satisfies the cli.Command Run function.
|
||||
func (a *ACLAuthMethodListCommand) Run(args []string) int {
|
||||
var json bool
|
||||
var tmpl string
|
||||
|
||||
flags := a.Meta.FlagSet(a.Name(), FlagSetClient)
|
||||
flags.Usage = func() { a.Ui.Output(a.Help()) }
|
||||
flags.BoolVar(&json, "json", false, "")
|
||||
flags.StringVar(&tmpl, "t", "", "")
|
||||
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check that we got no arguments
|
||||
if len(flags.Args()) != 0 {
|
||||
a.Ui.Error("This command takes no arguments")
|
||||
a.Ui.Error(commandErrorText(a))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Get the HTTP client
|
||||
client, err := a.Meta.Client()
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Fetch info on the method
|
||||
methods, _, err := client.ACLAuthMethods().List(nil)
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error listing ACL auth methods: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
if json || len(tmpl) > 0 {
|
||||
out, err := Format(json, tmpl, methods)
|
||||
if err != nil {
|
||||
a.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
a.Ui.Output(out)
|
||||
return 0
|
||||
}
|
||||
|
||||
a.Ui.Output(formatAuthMethods(methods))
|
||||
return 0
|
||||
}
|
||||
|
||||
func formatAuthMethods(methods []*api.ACLAuthMethodListStub) string {
|
||||
if len(methods) == 0 {
|
||||
return "No ACL auth methods found"
|
||||
}
|
||||
|
||||
output := make([]string, 0, len(methods)+1)
|
||||
output = append(output, "Name|Default")
|
||||
for _, method := range methods {
|
||||
output = append(output, fmt.Sprintf(
|
||||
"%s|%v",
|
||||
method.Name, method.Default))
|
||||
}
|
||||
|
||||
return formatList(output)
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/ci"
|
||||
"github.com/hashicorp/nomad/command/agent"
|
||||
"github.com/hashicorp/nomad/nomad/mock"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/shoenig/test/must"
|
||||
)
|
||||
|
||||
func TestACLAuthMethodListCommand(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
config := func(c *agent.Config) {
|
||||
c.ACL.Enabled = true
|
||||
}
|
||||
|
||||
srv, _, url := testServer(t, true, config)
|
||||
state := srv.Agent.Server().State()
|
||||
defer stopTestAgent(srv)
|
||||
|
||||
// Bootstrap an initial ACL token
|
||||
token := srv.RootToken
|
||||
must.NotNil(t, token)
|
||||
|
||||
// Create a test auth method
|
||||
method := &structs.ACLAuthMethod{
|
||||
Name: "test-auth-method",
|
||||
Config: &structs.ACLAuthMethodConfig{
|
||||
OIDCDiscoveryURL: "http://example.com",
|
||||
},
|
||||
}
|
||||
method.SetHash()
|
||||
must.NoError(t, state.UpsertACLAuthMethods(1000, []*structs.ACLAuthMethod{method}))
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := &ACLAuthMethodListCommand{Meta: Meta{Ui: ui, flagAddress: url}}
|
||||
|
||||
// Attempt to list auth methods without a valid management token
|
||||
invalidToken := mock.ACLToken()
|
||||
code := cmd.Run([]string{"-address=" + url, "-token=" + invalidToken.SecretID})
|
||||
must.One(t, code)
|
||||
|
||||
// List with a valid management token
|
||||
code = cmd.Run([]string{"-address=" + url, "-token=" + token.SecretID})
|
||||
must.Zero(t, code)
|
||||
|
||||
// Check the output
|
||||
out := ui.OutputWriter.String()
|
||||
must.StrContains(t, out, method.Name)
|
||||
|
||||
// List json
|
||||
must.Zero(t, cmd.Run([]string{"-address=" + url, "-token=" + token.SecretID, "-json"}))
|
||||
|
||||
out = ui.OutputWriter.String()
|
||||
must.StrContains(t, out, "CreateIndex")
|
||||
ui.OutputWriter.Reset()
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/nomad/api"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Ensure ACLAuthMethodUpdateCommand satisfies the cli.Command interface.
|
||||
var _ cli.Command = &ACLAuthMethodUpdateCommand{}
|
||||
|
||||
// ACLAuthMethodUpdateCommand implements cli.Command.
|
||||
type ACLAuthMethodUpdateCommand struct {
|
||||
Meta
|
||||
|
||||
methodType string
|
||||
tokenLocality string
|
||||
maxTokenTTL time.Duration
|
||||
isDefault bool
|
||||
config string
|
||||
|
||||
testStdin io.Reader
|
||||
}
|
||||
|
||||
// Help satisfies the cli.Command Help function.
|
||||
func (a *ACLAuthMethodUpdateCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad acl auth-method update [options] <acl_auth_method_name>
|
||||
|
||||
Update is used to update ACL auth methods. Use requires a management token.
|
||||
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
|
||||
|
||||
ACL Auth Method Update Options:
|
||||
|
||||
-type
|
||||
Updates the type of the auth method. Currently the only supported type is
|
||||
'OIDC'.
|
||||
|
||||
-max-token-ttl
|
||||
Updates the duration of time all tokens created by this auth method should be
|
||||
valid for.
|
||||
|
||||
-token-locality
|
||||
Updates the kind of token that this auth method should produce. This can be
|
||||
either 'local' or 'global'.
|
||||
|
||||
-default
|
||||
Specifies whether this auth method should be treated as a default one in
|
||||
case no auth method is explicitly specified for a login command.
|
||||
|
||||
-config
|
||||
Updates auth method configuration (in JSON format). May be prefixed with
|
||||
'@' to indicate that the value is a file path to load the config from. '-'
|
||||
may also be given to indicate that the config is available on stdin.
|
||||
`
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (a *ACLAuthMethodUpdateCommand) AutocompleteFlags() complete.Flags {
|
||||
return mergeAutocompleteFlags(a.Meta.AutocompleteFlags(FlagSetClient),
|
||||
complete.Flags{
|
||||
"-type": complete.PredictSet("OIDC"),
|
||||
"-max-token-ttl": complete.PredictAnything,
|
||||
"-token-locality": complete.PredictSet("local", "global"),
|
||||
"-default": complete.PredictSet("true", "false"),
|
||||
"-config": complete.PredictNothing,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLAuthMethodUpdateCommand) AutocompleteArgs() complete.Predictor {
|
||||
return complete.PredictNothing
|
||||
}
|
||||
|
||||
// Synopsis satisfies the cli.Command Synopsis function.
|
||||
func (a *ACLAuthMethodUpdateCommand) Synopsis() string { return "Update an existing ACL auth method" }
|
||||
|
||||
// Name returns the name of this command.
|
||||
func (*ACLAuthMethodUpdateCommand) Name() string { return "acl auth-method update" }
|
||||
|
||||
// Run satisfies the cli.Command Run function.
|
||||
func (a *ACLAuthMethodUpdateCommand) Run(args []string) int {
|
||||
|
||||
flags := a.Meta.FlagSet(a.Name(), FlagSetClient)
|
||||
flags.Usage = func() { a.Ui.Output(a.Help()) }
|
||||
flags.StringVar(&a.methodType, "type", "", "")
|
||||
flags.StringVar(&a.tokenLocality, "token-locality", "", "")
|
||||
flags.DurationVar(&a.maxTokenTTL, "max-token-ttl", 0, "")
|
||||
flags.StringVar(&a.config, "config", "", "")
|
||||
flags.BoolVar(&a.isDefault, "default", false, "")
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check that the last argument is the auth method name to delete.
|
||||
if len(flags.Args()) != 1 {
|
||||
a.Ui.Error("This command takes one argument: <acl_auth_method_name>")
|
||||
a.Ui.Error(commandErrorText(a))
|
||||
return 1
|
||||
}
|
||||
|
||||
originalMethodName := flags.Args()[0]
|
||||
|
||||
// Get the HTTP client.
|
||||
client, err := a.Meta.Client()
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check if the method we want to update exists
|
||||
originalMethod, _, err := client.ACLAuthMethods().Get(originalMethodName, nil)
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error when retrieving ACL auth method: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check if any command-specific flags were set
|
||||
setFlags := []string{}
|
||||
for _, f := range []string{"type", "token-locality", "max-token-ttl", "config", "default"} {
|
||||
if flagPassed(flags, f) {
|
||||
setFlags = append(setFlags, f)
|
||||
}
|
||||
}
|
||||
if len(setFlags) == 0 {
|
||||
a.Ui.Error("Please provide at least one flag to update the ACL auth method")
|
||||
return 1
|
||||
}
|
||||
|
||||
updatedMethod := *originalMethod
|
||||
|
||||
if slices.Contains(setFlags, "token-locality") {
|
||||
if !slices.Contains([]string{"global", "local"}, a.tokenLocality) {
|
||||
a.Ui.Error("Token locality must be set to either 'local' or 'global'")
|
||||
return 1
|
||||
}
|
||||
updatedMethod.TokenLocality = a.tokenLocality
|
||||
}
|
||||
|
||||
if slices.Contains(setFlags, "type") {
|
||||
if strings.ToLower(a.methodType) != "oidc" {
|
||||
a.Ui.Error("ACL auth method type must be set to 'OIDC'")
|
||||
return 1
|
||||
}
|
||||
updatedMethod.Type = a.methodType
|
||||
}
|
||||
|
||||
if slices.Contains(setFlags, "max-token-ttl") {
|
||||
if a.maxTokenTTL < 1 {
|
||||
a.Ui.Error("Max token TTL must be set to a value between min and max TTL configured for the server.")
|
||||
return 1
|
||||
}
|
||||
updatedMethod.MaxTokenTTL = a.maxTokenTTL
|
||||
}
|
||||
|
||||
if slices.Contains(setFlags, "default") {
|
||||
updatedMethod.Default = a.isDefault
|
||||
}
|
||||
|
||||
if len(a.config) != 0 {
|
||||
config, err := loadDataSource(a.config, a.testStdin)
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error loading configuration: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
configJSON := api.ACLAuthMethodConfig{}
|
||||
err = json.Unmarshal([]byte(config), &configJSON)
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Unable to parse config: %v", err))
|
||||
return 1
|
||||
}
|
||||
updatedMethod.Config = &configJSON
|
||||
}
|
||||
|
||||
// Update the auth method via the API.
|
||||
_, err = client.ACLAuthMethods().Update(&updatedMethod, nil)
|
||||
if err != nil {
|
||||
a.Ui.Error(fmt.Sprintf("Error updating ACL auth method: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
a.Ui.Output(fmt.Sprintf("Updated ACL auth method %s", originalMethodName))
|
||||
return 0
|
||||
}
|
||||
|
||||
func flagPassed(flags *flag.FlagSet, name string) bool {
|
||||
found := false
|
||||
flags.Visit(func(f *flag.Flag) {
|
||||
if f.Name == name {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
return found
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestACLAuthMethodUpdateCommand_Run(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
// Build a test server with ACLs enabled.
|
||||
srv, _, url := testServer(t, false, func(c *agent.Config) {
|
||||
c.ACL.Enabled = true
|
||||
})
|
||||
defer srv.Shutdown()
|
||||
|
||||
// Wait for the server to start fully and ensure we have a bootstrap token.
|
||||
testutil.WaitForLeader(t, srv.Agent.RPC)
|
||||
rootACLToken := srv.RootToken
|
||||
must.NotNil(t, rootACLToken)
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := &ACLAuthMethodUpdateCommand{
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
flagAddress: url,
|
||||
},
|
||||
}
|
||||
|
||||
// Try calling the command without setting the method name argument
|
||||
must.One(t, cmd.Run([]string{"-address=" + url}))
|
||||
must.StrContains(t, ui.ErrorWriter.String(), "This command takes one argument")
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
ui.ErrorWriter.Reset()
|
||||
|
||||
// Try calling the command with a method name that doesn't exist
|
||||
code := cmd.Run([]string{"-address=" + url, "-token=" + rootACLToken.SecretID, "catch-me-if-you-can"})
|
||||
must.One(t, code)
|
||||
must.StrContains(t, ui.ErrorWriter.String(), "ACL auth-method not found")
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
ui.ErrorWriter.Reset()
|
||||
|
||||
// Create a test auth method
|
||||
ttl, _ := time.ParseDuration("3600s")
|
||||
method := &structs.ACLAuthMethod{
|
||||
Name: "test-auth-method",
|
||||
Type: "OIDC",
|
||||
MaxTokenTTL: ttl,
|
||||
TokenLocality: "local",
|
||||
Config: &structs.ACLAuthMethodConfig{
|
||||
OIDCDiscoveryURL: "http://example.com",
|
||||
},
|
||||
}
|
||||
method.SetHash()
|
||||
must.NoError(t, srv.Agent.Server().State().UpsertACLAuthMethods(1000, []*structs.ACLAuthMethod{method}))
|
||||
|
||||
// Try an update without setting any parameters to update.
|
||||
code = cmd.Run([]string{"-address=" + url, "-token=" + rootACLToken.SecretID, method.Name})
|
||||
must.One(t, code)
|
||||
must.StrContains(t, ui.ErrorWriter.String(), "Please provide at least one flag to update the ACL auth method")
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
ui.ErrorWriter.Reset()
|
||||
|
||||
// Update the token locality
|
||||
code = cmd.Run([]string{
|
||||
"-address=" + url, "-token=" + rootACLToken.SecretID, "-token-locality=global", method.Name})
|
||||
must.Zero(t, code)
|
||||
s := ui.OutputWriter.String()
|
||||
must.StrContains(t, s, method.Name)
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
ui.ErrorWriter.Reset()
|
||||
|
||||
// Update an auth method with a config from file
|
||||
configFile, err := os.CreateTemp("", "config.json")
|
||||
defer os.Remove(configFile.Name())
|
||||
must.Nil(t, err)
|
||||
|
||||
conf := map[string]interface{}{"OIDCDiscoveryURL": "http://example.com"}
|
||||
jsonData, err := json.Marshal(conf)
|
||||
must.Nil(t, err)
|
||||
|
||||
_, err = configFile.Write(jsonData)
|
||||
must.Nil(t, err)
|
||||
|
||||
code = cmd.Run([]string{
|
||||
"-address=" + url,
|
||||
"-token=" + rootACLToken.SecretID,
|
||||
fmt.Sprintf("-config=@%s", configFile.Name()),
|
||||
method.Name,
|
||||
})
|
||||
must.Zero(t, code)
|
||||
s = ui.OutputWriter.String()
|
||||
must.StrContains(t, s, method.Name)
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
ui.ErrorWriter.Reset()
|
||||
}
|
|
@ -77,6 +77,36 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory {
|
|||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"acl auth-method": func() (cli.Command, error) {
|
||||
return &ACLAuthMethodCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"acl auth-method create": func() (cli.Command, error) {
|
||||
return &ACLAuthMethodCreateCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"acl auth-method delete": func() (cli.Command, error) {
|
||||
return &ACLAuthMethodDeleteCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"acl auth-method info": func() (cli.Command, error) {
|
||||
return &ACLAuthMethodInfoCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"acl auth-method list": func() (cli.Command, error) {
|
||||
return &ACLAuthMethodListCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"acl auth-method update": func() (cli.Command, error) {
|
||||
return &ACLAuthMethodUpdateCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"acl bootstrap": func() (cli.Command, error) {
|
||||
return &ACLBootstrapCommand{
|
||||
Meta: meta,
|
||||
|
|
|
@ -600,3 +600,43 @@ func (w *uiErrorWriter) Close() error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadDataSource(data string, testStdin io.Reader) (string, error) {
|
||||
// Handle empty quoted shell parameters
|
||||
if len(data) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
switch data[0] {
|
||||
case '@':
|
||||
return loadFromFile(data[1:])
|
||||
case '-':
|
||||
if len(data) > 1 {
|
||||
return data, nil
|
||||
}
|
||||
return loadFromStdin(testStdin)
|
||||
default:
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func loadFromFile(path string) (string, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to read file: %v", err)
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func loadFromStdin(testStdin io.Reader) (string, error) {
|
||||
var stdin io.Reader = os.Stdin
|
||||
if testStdin != nil {
|
||||
stdin = testStdin
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
if _, err := io.Copy(&b, stdin); err != nil {
|
||||
return "", fmt.Errorf("Failed to read stdin: %v", err)
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue