mirror of
https://github.com/hashicorp/vault.git
synced 2026-06-07 15:52:16 -04:00
Merge pull request #1171 from hashicorp/capabilities-endpoint
Capabilities endpoint
This commit is contained in:
commit
3ad525fb7a
15 changed files with 632 additions and 0 deletions
48
api/sys_capabilities.go
Normal file
48
api/sys_capabilities.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package api
|
||||
|
||||
func (c *Sys) CapabilitiesSelf(path string) ([]string, error) {
|
||||
body := map[string]string{
|
||||
"path": path,
|
||||
}
|
||||
|
||||
r := c.c.NewRequest("POST", "/v1/sys/capabilities-self")
|
||||
if err := r.SetJSONBody(body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result CapabilitiesResponse
|
||||
err = resp.DecodeJSON(&result)
|
||||
return result.Capabilities, err
|
||||
}
|
||||
|
||||
func (c *Sys) Capabilities(token, path string) ([]string, error) {
|
||||
body := map[string]string{
|
||||
"token": token,
|
||||
"path": path,
|
||||
}
|
||||
|
||||
r := c.c.NewRequest("POST", "/v1/sys/capabilities")
|
||||
if err := r.SetJSONBody(body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result CapabilitiesResponse
|
||||
err = resp.DecodeJSON(&result)
|
||||
return result.Capabilities, err
|
||||
}
|
||||
|
||||
type CapabilitiesResponse struct {
|
||||
Capabilities []string `json:"capabilities"`
|
||||
}
|
||||
|
|
@ -290,6 +290,12 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
|
|||
}, nil
|
||||
},
|
||||
|
||||
"capabilities": func() (cli.Command, error) {
|
||||
return &command.CapabilitiesCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"version": func() (cli.Command, error) {
|
||||
versionInfo := version.GetVersion()
|
||||
|
||||
|
|
|
|||
86
command/capabilities.go
Normal file
86
command/capabilities.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CapabilitiesCommand is a Command that enables a new endpoint.
|
||||
type CapabilitiesCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (c *CapabilitiesCommand) Run(args []string) int {
|
||||
flags := c.Meta.FlagSet("capabilities", FlagSetDefault)
|
||||
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
args = flags.Args()
|
||||
if len(args) > 2 {
|
||||
flags.Usage()
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"\ncapabilities expects at most two arguments"))
|
||||
return 1
|
||||
}
|
||||
|
||||
var token string
|
||||
var path string
|
||||
switch {
|
||||
case len(args) == 1:
|
||||
path = args[0]
|
||||
case len(args) == 2:
|
||||
token = args[0]
|
||||
path = args[1]
|
||||
default:
|
||||
flags.Usage()
|
||||
c.Ui.Error(fmt.Sprintf("\ncapabilities expects at least one argument"))
|
||||
return 1
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error initializing client: %s", err))
|
||||
return 2
|
||||
}
|
||||
|
||||
var capabilities []string
|
||||
if token == "" {
|
||||
capabilities, err = client.Sys().CapabilitiesSelf(path)
|
||||
} else {
|
||||
capabilities, err = client.Sys().Capabilities(token, path)
|
||||
}
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error retrieving capabilities: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Output(fmt.Sprintf("Capabilities: %s", capabilities))
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *CapabilitiesCommand) Synopsis() string {
|
||||
return "Fetch the capabilities of a token on a given path"
|
||||
}
|
||||
|
||||
func (c *CapabilitiesCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault capabilities [options] [token] path
|
||||
|
||||
Fetch the capabilities of a token on a given path.
|
||||
If a token is provided as an argument, the '/sys/capabilities' endpoint will be invoked
|
||||
with the given token; otherwise the '/sys/capabilities-self' endpoint will be invoked
|
||||
with the client token.
|
||||
|
||||
If a token does not have any capability on a given path, or if any of the policies
|
||||
belonging to the token explicitly have ["deny"] capability, or if the argument path
|
||||
is invalid, this command will respond with a ["deny"].
|
||||
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage()
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
44
command/capabilities_test.go
Normal file
44
command/capabilities_test.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestCapabilities_Basic(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := http.TestServer(t, core)
|
||||
defer ln.Close()
|
||||
ui := new(cli.MockUi)
|
||||
c := &CapabilitiesCommand{
|
||||
Meta: Meta{
|
||||
ClientToken: token,
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
var args []string
|
||||
|
||||
args = []string{"-address", addr}
|
||||
if code := c.Run(args); code == 0 {
|
||||
t.Fatalf("expected failure due to no args")
|
||||
}
|
||||
|
||||
args = []string{"-address", addr, "testpath"}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
args = []string{"-address", addr, token, "test"}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
args = []string{"-address", addr, "invalidtoken", "test"}
|
||||
if code := c.Run(args); code == 0 {
|
||||
t.Fatalf("expected failure due to invalid token")
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,8 @@ func Handler(core *vault.Core) http.Handler {
|
|||
mux.Handle("/v1/sys/generate-root/update", handleSysGenerateRootUpdate(core))
|
||||
mux.Handle("/v1/sys/rekey/init", handleSysRekeyInit(core))
|
||||
mux.Handle("/v1/sys/rekey/update", handleSysRekeyUpdate(core))
|
||||
mux.Handle("/v1/sys/capabilities", handleSysCapabilities(core))
|
||||
mux.Handle("/v1/sys/capabilities-self", handleSysCapabilities(core))
|
||||
mux.Handle("/v1/sys/", handleLogical(core, true))
|
||||
mux.Handle("/v1/", handleLogical(core, false))
|
||||
|
||||
|
|
|
|||
61
http/sys_capabilities.go
Normal file
61
http/sys_capabilities.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func handleSysCapabilities(core *vault.Core) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "PUT":
|
||||
case "POST":
|
||||
default:
|
||||
respondError(w, http.StatusMethodNotAllowed, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the auth for the request so we can access the token directly
|
||||
req := requestAuth(r, &logical.Request{})
|
||||
|
||||
// Parse the request if we can
|
||||
var data capabilitiesRequest
|
||||
if err := parseRequest(r, &data); err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/v1/sys/capabilities-self") {
|
||||
data.Token = req.ClientToken
|
||||
}
|
||||
|
||||
capabilities, err := core.Capabilities(data.Token, data.Path)
|
||||
if err != nil {
|
||||
if errwrap.ContainsType(err, new(vault.ErrUserInput)) {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
} else {
|
||||
respondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
respondOk(w, &capabilitiesResponse{
|
||||
Capabilities: capabilities,
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
type capabilitiesResponse struct {
|
||||
Capabilities []string `json:"capabilities"`
|
||||
}
|
||||
|
||||
type capabilitiesRequest struct {
|
||||
Token string `json:"token"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
82
http/sys_capabilities_test.go
Normal file
82
http/sys_capabilities_test.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func TestSysCapabilities(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
// Send both token and path
|
||||
resp := testHttpPost(t, token, addr+"/v1/sys/capabilities", map[string]interface{}{
|
||||
"token": token,
|
||||
"path": "testpath",
|
||||
})
|
||||
|
||||
var actual map[string][]string
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
|
||||
expected := map[string][]string{
|
||||
"capabilities": []string{"root"},
|
||||
}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||||
}
|
||||
|
||||
// Send only path to capabilities-self
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/capabilities-self", map[string]interface{}{
|
||||
"path": "testpath",
|
||||
})
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||||
}
|
||||
|
||||
// Testing for non-root tokens
|
||||
|
||||
// Create a policy first
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/policy/foo", map[string]interface{}{
|
||||
"rules": `path "testpath" {capabilities = ["read","sudo"]}`,
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
// Create a token against the test policy
|
||||
resp = testHttpPost(t, token, addr+"/v1/auth/token/create", map[string]interface{}{
|
||||
"policies": []string{"foo"},
|
||||
})
|
||||
|
||||
var tokenResp map[string]interface{}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &tokenResp)
|
||||
|
||||
// Check if desired policies are present in the token
|
||||
auth := tokenResp["auth"].(map[string]interface{})
|
||||
actualPolicies := auth["policies"]
|
||||
expectedPolicies := []interface{}{"default", "foo"}
|
||||
if !reflect.DeepEqual(actualPolicies, expectedPolicies) {
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actualPolicies, expectedPolicies)
|
||||
}
|
||||
|
||||
// Check the capabilities with the created non-root token
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/capabilities", map[string]interface{}{
|
||||
"token": auth["client_token"],
|
||||
"path": "testpath",
|
||||
})
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
|
||||
expected = map[string][]string{
|
||||
"capabilities": []string{"sudo", "read"},
|
||||
}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||||
}
|
||||
}
|
||||
50
vault/acl.go
50
vault/acl.go
|
|
@ -71,6 +71,56 @@ func NewACL(policies []*Policy) (*ACL, error) {
|
|||
return a, nil
|
||||
}
|
||||
|
||||
func (a *ACL) Capabilities(path string) (pathCapabilities []string) {
|
||||
// Fast-path root
|
||||
if a.root {
|
||||
return []string{RootCapability}
|
||||
}
|
||||
|
||||
// Find an exact matching rule, look for glob if no match
|
||||
var capabilities uint32
|
||||
raw, ok := a.exactRules.Get(path)
|
||||
if ok {
|
||||
capabilities = raw.(uint32)
|
||||
goto CHECK
|
||||
}
|
||||
|
||||
// Find a glob rule, default deny if no match
|
||||
_, raw, ok = a.globRules.LongestPrefix(path)
|
||||
if !ok {
|
||||
return []string{DenyCapability}
|
||||
} else {
|
||||
capabilities = raw.(uint32)
|
||||
}
|
||||
|
||||
CHECK:
|
||||
if capabilities&SudoCapabilityInt > 0 {
|
||||
pathCapabilities = append(pathCapabilities, SudoCapability)
|
||||
}
|
||||
if capabilities&ReadCapabilityInt > 0 {
|
||||
pathCapabilities = append(pathCapabilities, ReadCapability)
|
||||
}
|
||||
if capabilities&ListCapabilityInt > 0 {
|
||||
pathCapabilities = append(pathCapabilities, ListCapability)
|
||||
}
|
||||
if capabilities&UpdateCapabilityInt > 0 {
|
||||
pathCapabilities = append(pathCapabilities, UpdateCapability)
|
||||
}
|
||||
if capabilities&DeleteCapabilityInt > 0 {
|
||||
pathCapabilities = append(pathCapabilities, DeleteCapability)
|
||||
}
|
||||
if capabilities&CreateCapabilityInt > 0 {
|
||||
pathCapabilities = append(pathCapabilities, CreateCapability)
|
||||
}
|
||||
|
||||
// If "deny" is explicitly set or if the path has no capabilities at all,
|
||||
// set the path capabilities to "deny"
|
||||
if capabilities&DenyCapabilityInt > 0 || len(pathCapabilities) == 0 {
|
||||
pathCapabilities = []string{DenyCapability}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AllowOperation is used to check if the given operation is permitted. The
|
||||
// first bool indicates if an op is allowed, the second whether sudo priviliges
|
||||
// exist for that op and path.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,56 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
func TestACL_Capabilities(t *testing.T) {
|
||||
// Create the root policy ACL
|
||||
policy := []*Policy{&Policy{Name: "root"}}
|
||||
acl, err := NewACL(policy)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
actual := acl.Capabilities("any/path")
|
||||
expected := []string{"root"}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||||
}
|
||||
|
||||
policies, err := Parse(aclPolicy)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
acl, err = NewACL([]*Policy{policies})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
actual = acl.Capabilities("dev")
|
||||
expected = []string{"deny"}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: path:%s\ngot\n%#v\nexpected\n%#v\n", "deny", actual, expected)
|
||||
}
|
||||
|
||||
actual = acl.Capabilities("dev/")
|
||||
expected = []string{"sudo", "read", "list", "update", "delete", "create"}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: path:%s\ngot\n%#v\nexpected\n%#v\n", "dev/", actual, expected)
|
||||
}
|
||||
|
||||
actual = acl.Capabilities("stage/aws/test")
|
||||
expected = []string{"sudo", "read", "list", "update"}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: path:%s\ngot\n%#v\nexpected\n%#v\n", "stage/aws/test", actual, expected)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestACL_Root(t *testing.T) {
|
||||
// Create the root policy ACL
|
||||
policy := []*Policy{&Policy{Name: "root"}}
|
||||
|
|
|
|||
62
vault/capabilities.go
Normal file
62
vault/capabilities.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package vault
|
||||
|
||||
// Struct to identify user input errors.
|
||||
// This is helpful in responding the appropriate status codes to clients
|
||||
// from the HTTP endpoints.
|
||||
type ErrUserInput struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
// Implementing error interface
|
||||
func (e *ErrUserInput) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// Capabilities is used to fetch the capabilities of the given token on the given path
|
||||
func (c *Core) Capabilities(token, path string) ([]string, error) {
|
||||
if path == "" {
|
||||
return nil, &ErrUserInput{
|
||||
Message: "missing path",
|
||||
}
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
return nil, &ErrUserInput{
|
||||
Message: "missing token",
|
||||
}
|
||||
}
|
||||
|
||||
te, err := c.tokenStore.Lookup(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if te == nil {
|
||||
return nil, &ErrUserInput{
|
||||
Message: "invalid token",
|
||||
}
|
||||
}
|
||||
|
||||
if te.Policies == nil {
|
||||
return []string{DenyCapability}, nil
|
||||
}
|
||||
|
||||
var policies []*Policy
|
||||
for _, tePolicy := range te.Policies {
|
||||
policy, err := c.policyStore.GetPolicy(tePolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policies = append(policies, policy)
|
||||
}
|
||||
|
||||
if len(policies) == 0 {
|
||||
return []string{DenyCapability}, nil
|
||||
}
|
||||
|
||||
acl, err := NewACL(policies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return acl.Capabilities(path), nil
|
||||
}
|
||||
45
vault/capabilities_test.go
Normal file
45
vault/capabilities_test.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCapabilities_Basic(t *testing.T) {
|
||||
c, _, token := TestCoreUnsealed(t)
|
||||
|
||||
actual, err := c.Capabilities(token, "path")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
expected := []string{"root"}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||||
}
|
||||
|
||||
// Create a policy
|
||||
policy, _ := Parse(aclPolicy)
|
||||
err = c.policyStore.SetPolicy(policy)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Create a token for the policy
|
||||
ent := &TokenEntry{
|
||||
ID: "capabilitiestoken",
|
||||
Path: "testpath",
|
||||
Policies: []string{"dev"},
|
||||
}
|
||||
if err := c.tokenStore.create(ent); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
actual, err = c.Capabilities("capabilitiestoken", "foo/bar")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
expected = []string{"sudo", "read", "create"}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ const (
|
|||
DeleteCapability = "delete"
|
||||
ListCapability = "list"
|
||||
SudoCapability = "sudo"
|
||||
RootCapability = "root"
|
||||
|
||||
// Backwards compatibility
|
||||
OldDenyPathPolicy = "deny"
|
||||
|
|
|
|||
44
website/source/docs/http/sys-capabilities-self.html.md
Normal file
44
website/source/docs/http/sys-capabilities-self.html.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
layout: "http"
|
||||
page_title: "HTTP API: /sys/capabilities-self"
|
||||
sidebar_current: "docs-http-auth-capabilities-self"
|
||||
description: |-
|
||||
The `/sys/capabilities-self` endpoint is used to fetch the capabilities of client token on a given path.
|
||||
---
|
||||
|
||||
# /sys/capabilities-self
|
||||
|
||||
## POST
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Returns the capabilities of client token on the given path.
|
||||
Client token is the Vault token with which this API call is made.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>POST</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">path</span>
|
||||
<span class="param-flags">required</span>
|
||||
Path on which the client token's capabilities will be checked.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```javascript
|
||||
{
|
||||
"capabilities": ["read", "list"]
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
48
website/source/docs/http/sys-capabilities.html.md
Normal file
48
website/source/docs/http/sys-capabilities.html.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
layout: "http"
|
||||
page_title: "HTTP API: /sys/capabilities"
|
||||
sidebar_current: "docs-http-auth-capabilities"
|
||||
description: |-
|
||||
The `/sys/capabilities` endpoint is used to fetch the capabilities of a token on a given path.
|
||||
---
|
||||
|
||||
# /sys/capabilities
|
||||
|
||||
## POST
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Returns the capabilities of the token on the given path.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>POST</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">token</span>
|
||||
<span class="param-flags">required</span>
|
||||
Token for which capabilities are being queried.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">path</span>
|
||||
<span class="param-flags">required</span>
|
||||
Path on which the token's capabilities will be checked.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```javascript
|
||||
{
|
||||
"capabilities": ["read", "list"]
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
|
@ -69,6 +69,14 @@
|
|||
<li<%= sidebar_current("docs-http-auth-policy") %>>
|
||||
<a href="/docs/http/sys-policy.html">/sys/policy</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-http-auth-capabilities") %>>
|
||||
<a href="/docs/http/sys-capabilities.html">/sys/capabilities</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-http-auth-capabilities-self") %>>
|
||||
<a href="/docs/http/sys-capabilities-self.html">/sys/capabilities-self</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue