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:
		 Mattias Fjellström
					Mattias Fjellström
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						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