mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 18:17:55 +00:00
Command: token capabilities using accessor (#24479)
* Command: token capabilities using accessor * release note * Apply suggestions from code review Co-authored-by: Marc Boudreau <marc.boudreau@hashicorp.com> --------- Co-authored-by: Marc Boudreau <marc.boudreau@hashicorp.com>
This commit is contained in:
committed by
GitHub
parent
dc5c3e8d97
commit
e4ffe8979c
@@ -78,3 +78,56 @@ func (c *Sys) CapabilitiesWithContext(ctx context.Context, token, path string) (
|
|||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Sys) CapabilitiesAccessor(accessor, path string) ([]string, error) {
|
||||||
|
return c.CapabilitiesAccessorWithContext(context.Background(), accessor, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Sys) CapabilitiesAccessorWithContext(ctx context.Context, accessor, path string) ([]string, error) {
|
||||||
|
ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
body := map[string]string{
|
||||||
|
"accessor": accessor,
|
||||||
|
"path": path,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqPath := "/v1/sys/capabilities-accessor"
|
||||||
|
|
||||||
|
r := c.c.NewRequest(http.MethodPost, reqPath)
|
||||||
|
if err := r.SetJSONBody(body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.c.rawRequestWithContext(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
secret, err := ParseSecret(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if secret == nil || secret.Data == nil {
|
||||||
|
return nil, errors.New("data from server response is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []string
|
||||||
|
err = mapstructure.Decode(secret.Data[path], &res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) == 0 {
|
||||||
|
_, ok := secret.Data["capabilities"]
|
||||||
|
if ok {
|
||||||
|
err = mapstructure.Decode(secret.Data["capabilities"], &res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|||||||
3
changelog/24479.txt
Normal file
3
changelog/24479.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:improvement
|
||||||
|
command/token-capabilities: allow using accessor when listing token capabilities on a path
|
||||||
|
```
|
||||||
@@ -19,6 +19,8 @@ var (
|
|||||||
|
|
||||||
type TokenCapabilitiesCommand struct {
|
type TokenCapabilitiesCommand struct {
|
||||||
*BaseCommand
|
*BaseCommand
|
||||||
|
|
||||||
|
flagAccessor bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TokenCapabilitiesCommand) Synopsis() string {
|
func (c *TokenCapabilitiesCommand) Synopsis() string {
|
||||||
@@ -27,12 +29,15 @@ func (c *TokenCapabilitiesCommand) Synopsis() string {
|
|||||||
|
|
||||||
func (c *TokenCapabilitiesCommand) Help() string {
|
func (c *TokenCapabilitiesCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: vault token capabilities [options] [TOKEN] PATH
|
Usage: vault token capabilities [options] [TOKEN | ACCESSOR] PATH
|
||||||
|
|
||||||
Fetches the capabilities of a token for a given path. If a TOKEN is provided
|
Fetches the capabilities of a token or accessor for a given path. If a TOKEN
|
||||||
as an argument, the "/sys/capabilities" endpoint and permission is used. If
|
is provided as an argument, the "/sys/capabilities" endpoint is used, which
|
||||||
no TOKEN is provided, the "/sys/capabilities-self" endpoint and permission
|
returns the capabilities of the provided TOKEN. If an ACCESSOR is provided
|
||||||
is used with the locally authenticated token.
|
as an argument along with the -accessor option, the "/sys/capabilities-accessor"
|
||||||
|
endpoint is used, which returns the capabilities of the token referenced by
|
||||||
|
ACCESSOR. If no TOKEN is provided, the "/sys/capabilities-self" endpoint
|
||||||
|
is used, which returns the capabilities of the locally authenticated token.
|
||||||
|
|
||||||
List capabilities for the local token on the "secret/foo" path:
|
List capabilities for the local token on the "secret/foo" path:
|
||||||
|
|
||||||
@@ -42,6 +47,10 @@ Usage: vault token capabilities [options] [TOKEN] PATH
|
|||||||
|
|
||||||
$ vault token capabilities 96ddf4bc-d217-f3ba-f9bd-017055595017 cubbyhole/foo
|
$ vault token capabilities 96ddf4bc-d217-f3ba-f9bd-017055595017 cubbyhole/foo
|
||||||
|
|
||||||
|
List capabilities for a token on the "cubbyhole/foo" path via its accessor:
|
||||||
|
|
||||||
|
$ vault token capabilities -accessor 9793c9b3-e04a-46f3-e7b8-748d7da248da cubbyhole/foo
|
||||||
|
|
||||||
For a full list of examples, please see the documentation.
|
For a full list of examples, please see the documentation.
|
||||||
|
|
||||||
` + c.Flags().Help()
|
` + c.Flags().Help()
|
||||||
@@ -50,7 +59,20 @@ Usage: vault token capabilities [options] [TOKEN] PATH
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *TokenCapabilitiesCommand) Flags() *FlagSets {
|
func (c *TokenCapabilitiesCommand) Flags() *FlagSets {
|
||||||
return c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||||
|
|
||||||
|
f := set.NewFlagSet("Command Options")
|
||||||
|
|
||||||
|
f.BoolVar(&BoolVar{
|
||||||
|
Name: "accessor",
|
||||||
|
Target: &c.flagAccessor,
|
||||||
|
Default: false,
|
||||||
|
EnvVar: "",
|
||||||
|
Completion: complete.PredictNothing,
|
||||||
|
Usage: "Treat the argument as an accessor instead of a token.",
|
||||||
|
})
|
||||||
|
|
||||||
|
return set
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TokenCapabilitiesCommand) AutocompleteArgs() complete.Predictor {
|
func (c *TokenCapabilitiesCommand) AutocompleteArgs() complete.Predictor {
|
||||||
@@ -72,13 +94,19 @@ func (c *TokenCapabilitiesCommand) Run(args []string) int {
|
|||||||
token := ""
|
token := ""
|
||||||
path := ""
|
path := ""
|
||||||
args = f.Args()
|
args = f.Args()
|
||||||
switch len(args) {
|
switch {
|
||||||
case 0:
|
case c.flagAccessor && len(args) < 2:
|
||||||
|
c.UI.Error(fmt.Sprintf("Not enough arguments with -accessor (expected 2, got %d)", len(args)))
|
||||||
|
return 1
|
||||||
|
case c.flagAccessor && len(args) > 2:
|
||||||
|
c.UI.Error(fmt.Sprintf("Too many arguments with -accessor (expected 2, got %d)", len(args)))
|
||||||
|
return 1
|
||||||
|
case len(args) == 0:
|
||||||
c.UI.Error("Not enough arguments (expected 1-2, got 0)")
|
c.UI.Error("Not enough arguments (expected 1-2, got 0)")
|
||||||
return 1
|
return 1
|
||||||
case 1:
|
case len(args) == 1:
|
||||||
path = args[0]
|
path = args[0]
|
||||||
case 2:
|
case len(args) == 2:
|
||||||
token, path = args[0], args[1]
|
token, path = args[0], args[1]
|
||||||
default:
|
default:
|
||||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1-2, got %d)", len(args)))
|
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1-2, got %d)", len(args)))
|
||||||
@@ -92,11 +120,15 @@ func (c *TokenCapabilitiesCommand) Run(args []string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var capabilities []string
|
var capabilities []string
|
||||||
if token == "" {
|
switch {
|
||||||
|
case token == "":
|
||||||
capabilities, err = client.Sys().CapabilitiesSelf(path)
|
capabilities, err = client.Sys().CapabilitiesSelf(path)
|
||||||
} else {
|
case c.flagAccessor:
|
||||||
|
capabilities, err = client.Sys().CapabilitiesAccessor(token, path)
|
||||||
|
default:
|
||||||
capabilities, err = client.Sys().Capabilities(token, path)
|
capabilities, err = client.Sys().Capabilities(token, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.UI.Error(fmt.Sprintf("Error listing capabilities: %s", err))
|
c.UI.Error(fmt.Sprintf("Error listing capabilities: %s", err))
|
||||||
return 2
|
return 2
|
||||||
|
|||||||
@@ -31,6 +31,24 @@ func TestTokenCapabilitiesCommand_Run(t *testing.T) {
|
|||||||
out string
|
out string
|
||||||
code int
|
code int
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
"accessor_no_args",
|
||||||
|
[]string{"-accessor"},
|
||||||
|
"Not enough arguments",
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessor_too_few_args",
|
||||||
|
[]string{"-accessor", "abcd1234"},
|
||||||
|
"Not enough arguments",
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessor_too_many_args",
|
||||||
|
[]string{"-accessor", "abcd1234", "efgh5678", "ijkl9012"},
|
||||||
|
"Too many arguments",
|
||||||
|
1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"too_many_args",
|
"too_many_args",
|
||||||
[]string{"foo", "bar", "zip"},
|
[]string{"foo", "bar", "zip"},
|
||||||
@@ -103,6 +121,48 @@ func TestTokenCapabilitiesCommand_Run(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("accessor", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
client, closer := testVaultServer(t)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
policy := `path "secret/foo" { capabilities = ["read"] }`
|
||||||
|
if err := client.Sys().PutPolicy("policy", policy); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
|
||||||
|
Policies: []string{"policy"},
|
||||||
|
TTL: "30m",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if secret == nil || secret.Auth == nil || secret.Auth.ClientToken == "" {
|
||||||
|
t.Fatalf("missing auth data: %#v", secret)
|
||||||
|
}
|
||||||
|
accessor := secret.Auth.Accessor
|
||||||
|
|
||||||
|
ui, cmd := testTokenCapabilitiesCommand(t)
|
||||||
|
cmd.client = client
|
||||||
|
|
||||||
|
code := cmd.Run([]string{
|
||||||
|
"-accessor",
|
||||||
|
accessor,
|
||||||
|
"secret/foo",
|
||||||
|
})
|
||||||
|
if exp := 0; code != exp {
|
||||||
|
t.Errorf("expected %d to be %d", code, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "read"
|
||||||
|
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||||
|
if !strings.Contains(combined, expected) {
|
||||||
|
t.Errorf("expected %q to contain %q", combined, expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("local", func(t *testing.T) {
|
t.Run("local", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user