From ed3e2c6c05d9d3f1836e6a5580431eab37c0b712 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 2 Mar 2016 13:42:32 -0500 Subject: [PATCH 01/17] Added sys/capabililties endpoint --- api/sys_capabilities.go | 48 +++++++++++++++++ cli/commands.go | 6 +++ command/capabilities.go | 85 +++++++++++++++++++++++++++++ http/sys_capabilities.go | 99 ++++++++++++++++++++++++++++++++++ vault/logical_system.go | 112 +++++++++++++++++++++++++++++++++++++++ vault/token_store.go | 42 +++++++++++++++ 6 files changed, 392 insertions(+) create mode 100644 api/sys_capabilities.go create mode 100644 command/capabilities.go create mode 100644 http/sys_capabilities.go diff --git a/api/sys_capabilities.go b/api/sys_capabilities.go new file mode 100644 index 0000000000..7c23c56d43 --- /dev/null +++ b/api/sys_capabilities.go @@ -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 capabilitiesResp + 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 capabilitiesResp + err = resp.DecodeJSON(&result) + return result.Capabilities, err +} + +type capabilitiesResp struct { + Capabilities []string `json:"capabilities"` +} diff --git a/cli/commands.go b/cli/commands.go index 1f5b89f911..ec6cb27359 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -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() diff --git a/command/capabilities.go b/command/capabilities.go new file mode 100644 index 0000000000..861f11b25e --- /dev/null +++ b/command/capabilities.go @@ -0,0 +1,85 @@ +package command + +import ( + "fmt" + "log" + "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 len(args) { + case 1: + // only path is provided + log.Printf("only path is provided") + path = args[0] + case 2: + // both token and path are provided + log.Printf("both token and path are provided") + token = args[0] + path = args[1] + default: + } + + client, err := c.Client() + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error initializing client: %s", err)) + return 2 + } + + log.Printf("vishal: token:'%s' path:'%s'\n", token, path) + 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 given 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 given to the command '/sys/capabilities' will be called with + the given token; otherwise '/sys/capabilities-self' will be called with the + client token. + +General Options: + + ` + generalOptionsUsage() + return strings.TrimSpace(helpText) +} diff --git a/http/sys_capabilities.go b/http/sys_capabilities.go new file mode 100644 index 0000000000..764c86b1d6 --- /dev/null +++ b/http/sys_capabilities.go @@ -0,0 +1,99 @@ +package http + +import ( + "log" + "net/http" + + "github.com/hashicorp/vault/logical" + "github.com/hashicorp/vault/vault" +) + +func handleSysCapabilitiesSelf(core *vault.Core) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" && r.Method != "PUT" { + respondError(w, http.StatusMethodNotAllowed, nil) + return + } + log.Printf("vishal: handleSysCapabilitiesSelf: r:%#v, r.URL:%s r.URL.Path:%s\n", r, r.URL, r.URL.Path) + // Parse the request if we can + var req capabilitiesRequest + if err := parseRequest(r, &req); err != nil { + respondError(w, http.StatusBadRequest, err) + return + } + + resp, ok := request(core, w, r, requestAuth(r, &logical.Request{ + Operation: logical.UpdateOperation, + Path: "sys/capabilities-self", + // Connection: getConnection(r), + Data: map[string]interface{}{ + "path": req.Path, + }, + })) + if !ok { + return + } + if resp == nil { + respondError(w, http.StatusNotFound, nil) + return + } + + var capabilities []string + capabilitiesRaw, ok := resp.Data["keys"] + if ok { + capabilities = capabilitiesRaw.([]string) + } + + respondOk(w, &capabilitiesResponse{Capabilities: capabilities}) + }) +} + +func handleSysCapabilities(core *vault.Core) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" && r.Method != "PUT" { + respondError(w, http.StatusMethodNotAllowed, nil) + return + } + log.Printf("vishal: handleSysCapabilities: r: %#v\n", r) + // Parse the request if we can + var req capabilitiesRequest + if err := parseRequest(r, &req); err != nil { + respondError(w, http.StatusBadRequest, err) + return + } + + resp, ok := request(core, w, r, requestAuth(r, &logical.Request{ + Operation: logical.UpdateOperation, + Path: "sys/capabilities", + Connection: getConnection(r), + Data: map[string]interface{}{ + "token": req.Token, + "path": req.Path, + }, + })) + if !ok { + return + } + if resp == nil { + respondError(w, http.StatusNotFound, nil) + return + } + + var capabilities []string + capabilitiesRaw, ok := resp.Data["keys"] + if ok { + capabilities = capabilitiesRaw.([]string) + } + + respondOk(w, &capabilitiesResponse{Capabilities: capabilities}) + }) +} + +type capabilitiesResponse struct { + Capabilities []string `json:"policies"` +} + +type capabilitiesRequest struct { + Token string `json:"token"` + Path string `json:"path"` +} diff --git a/vault/logical_system.go b/vault/logical_system.go index 5217c97e7a..7bdbd3b4b3 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -1,7 +1,9 @@ package vault import ( + "errors" "fmt" + "log" "strings" "time" @@ -277,6 +279,50 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) logical.Backend HelpDescription: strings.TrimSpace(sysHelp["policy"][1]), }, + &framework.Path{ + Pattern: "capabilities-self", + + Fields: map[string]*framework.FieldSchema{ + "token": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Token of which capabilities are being requested", + }, + "path": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Path for which token's capabilities are being fetched", + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.handleCapabilitiesUpdate, + }, + + HelpSynopsis: strings.TrimSpace(sysHelp["policy"][0]), + HelpDescription: strings.TrimSpace(sysHelp["policy"][1]), + }, + + &framework.Path{ + Pattern: "capabilities", + + Fields: map[string]*framework.FieldSchema{ + "token": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Token of which capabilities are being requested", + }, + "path": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Path for which token's capabilities are being fetched", + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.handleCapabilitiesUpdate, + }, + + HelpSynopsis: strings.TrimSpace(sysHelp["policy"][0]), + HelpDescription: strings.TrimSpace(sysHelp["policy"][1]), + }, + &framework.Path{ Pattern: "audit-hash/(?P.+)", @@ -381,6 +427,17 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) logical.Backend HelpSynopsis: strings.TrimSpace(sysHelp["rotate"][0]), HelpDescription: strings.TrimSpace(sysHelp["rotate"][1]), }, + + &framework.Path{ + Pattern: "rotate$", + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.handleRotate, + }, + + HelpSynopsis: strings.TrimSpace(sysHelp["rotate"][0]), + HelpDescription: strings.TrimSpace(sysHelp["rotate"][1]), + }, }, } @@ -853,6 +910,61 @@ func (b *SystemBackend) handlePolicyRead( }, nil } +// handleTokenCapabilitiesUpdate handles the "token-capabilities" endpoint to +// fetch the capabilities of a token on a given path +func (b *SystemBackend) handleCapabilitiesUpdate(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + log.Printf("\n\nvishal: logical_system: handleCapabilitiesUpdate: req:%#v data:%#v\n", req, data) + tokenStore := b.Core.tokenStore + token := data.Get("token").(string) + if token == "" { + token = req.ClientToken + } + if token == "" { + return logical.ErrorResponse("missing token"), nil + } + path := data.Get("path").(string) + if path == "" { + return logical.ErrorResponse("missing path"), nil + } + log.Printf("vishal: received: clientToken:%s token:%s path:%s\n", req.ClientToken, token, path) + te, err := tokenStore.Lookup(token) + if err != nil { + return nil, err + } + if te == nil { + return nil, errors.New("invalid token") + } + log.Printf("vishal: tokenEntry.Policies: %#v\n", te.Policies) + if te.Policies == nil { + return nil, nil + } + for _, tePolicy := range te.Policies { + log.Printf("vishal: tePolicy:%s", tePolicy) + if tePolicy == "root" { + // Add all the capabilities + } + policy, err := b.Core.policyStore.GetPolicy(tePolicy) + if err != nil { + return nil, err + } + if policy == nil { + return logical.ErrorResponse(fmt.Sprintf("policy '%s' not found", tePolicy)), nil + } + if policy.Paths == nil { + return logical.ErrorResponse(fmt.Sprintf("policy '%s' does not contain any paths", tePolicy)), nil + } + for _, pathCapability := range policy.Paths { + log.Printf("vishal: pathCapability: %#v\n", pathCapability) + log.Printf("vishal: pathCapability.Prefix: %s\n", pathCapability.Prefix) + log.Printf("vishal: pathCapability.Capabilities: %#v\n", pathCapability.Capabilities) + if path == pathCapability.Prefix { + log.Printf("vishal: found a match!!!!!!!!!!!\n") + } + } + } + return nil, nil +} + // handlePolicySet handles the "policy/" endpoint to set a policy func (b *SystemBackend) handlePolicySet( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { diff --git a/vault/token_store.go b/vault/token_store.go index 0e8e5dcff9..b5368b963f 100644 --- a/vault/token_store.go +++ b/vault/token_store.go @@ -3,6 +3,7 @@ package vault import ( "encoding/json" "fmt" + "log" "regexp" "sort" "strings" @@ -253,6 +254,28 @@ func NewTokenStore(c *Core, config *logical.BackendConfig) (*TokenStore, error) HelpSynopsis: strings.TrimSpace(tokenRenewHelp), HelpDescription: strings.TrimSpace(tokenRenewHelp), }, + + &framework.Path{ + Pattern: "capabilities", + + Fields: map[string]*framework.FieldSchema{ + "token": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Token of which capabilities are being requested", + }, + "path": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Path for which token's capabilities are being fetched", + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: t.handleCapabilitiesUpdate, + }, + + HelpSynopsis: strings.TrimSpace(tokenCapabilitiesHelp), + HelpDescription: strings.TrimSpace(tokenCapabilitiesHelp), + }, }, } @@ -530,6 +553,24 @@ func (ts *TokenStore) revokeTreeSalted(saltedId string) error { return nil } +// handleCapabilitiesUpdate handles the auth/token/capabilities path for fetching +// capabilities of a token on a given path +func (ts *TokenStore) handleCapabilitiesUpdate( + req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + log.Printf("vishal: vault/token_store.go: handleCapabilitiesRead: req:%#v data:%#v\n", req, d) + log.Println(d.Get("token").(string)) + te, err := ts.Lookup(d.Get("token").(string)) + if err != nil { + log.Printf("vishal: token lookup err:%#v\n", err) + } + if te == nil { + return logical.ErrorResponse("token does not exist"), nil + } + log.Printf("vishal: te.Policies:%#v\n", te.Policies) + log.Println(d.Get("path").(string)) + return ts.handleCreateCommon(req, d, true) +} + // handleCreate handles the auth/token/create path for creation of new orphan // tokens func (ts *TokenStore) handleCreateOrphan( @@ -936,4 +977,5 @@ as revocation of tokens. The tokens are renewable if associated with a lease.` tokenRevokePrefixHelp = `This endpoint will delete all tokens generated under a prefix with their child tokens.` tokenRenewHelp = `This endpoint will renew the given token and prevent expiration.` tokenRenewSelfHelp = `This endpoint will renew the token used to call it and prevent expiration.` + tokenCapabilitiesHelp = `This endpoint will return the capabilities of the given token on a given path.` ) From 258b4e6b10c39607b84b1c7a81387f2a6a7e3e5a Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 2 Mar 2016 21:32:52 -0500 Subject: [PATCH 02/17] Add vault/capabilities.go --- vault/capabilities.go | 91 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 vault/capabilities.go diff --git a/vault/capabilities.go b/vault/capabilities.go new file mode 100644 index 0000000000..88d7ad0ad5 --- /dev/null +++ b/vault/capabilities.go @@ -0,0 +1,91 @@ +package vault + +import ( + "fmt" + "sort" + "strings" +) + +// CapabilitiesResult holds the result of fetching the capabilities of token on a path +type CapabilitiesResult struct { + Capabilities []string +} + +// Capabilities is used to fetch the capabilities of the given token on the given path +func (c *Core) Capabilities(token, path string) (*CapabilitiesResult, error) { + if path == "" { + return nil, fmt.Errorf("missing path") + } + + if token == "" { + return nil, fmt.Errorf("missing token") + } + + te, err := c.tokenStore.Lookup(token) + if err != nil { + return nil, err + } + if te == nil { + return nil, fmt.Errorf("invalid token") + } + + if te.Policies == nil { + return nil, nil + } + + maps := make(map[string]bool) + for _, tePolicy := range te.Policies { + if tePolicy == "root" { + //TODO: check if the path is actually a valid path. Otherwise, there is no + // meaning in returning the capabilities + // Add all the capabilities + maps["create"] = true + maps["read"] = true + maps["update"] = true + maps["delete"] = true + maps["list"] = true + maps["sudo"] = true + break + } + policy, err := c.policyStore.GetPolicy(tePolicy) + if err != nil { + return nil, err + } + if policy == nil { + return nil, fmt.Errorf("policy '%s' not found", tePolicy) + } + + if policy.Paths == nil { + return nil, fmt.Errorf("policy '%s' does not contain any paths", tePolicy) + } + for _, pathCapability := range policy.Paths { + switch pathCapability.Glob { + case true: + if strings.HasPrefix(path, pathCapability.Prefix) { + for _, capability := range pathCapability.Capabilities { + if _, ok := maps[capability]; !ok { + maps[capability] = true + } + } + } + case false: + if path == pathCapability.Prefix { + for _, capability := range pathCapability.Capabilities { + if _, ok := maps[capability]; !ok { + maps[capability] = true + } + } + } + } + } + } + + var capabilities []string + for capability, _ := range maps { + capabilities = append(capabilities, capability) + } + sort.Strings(capabilities) + return &CapabilitiesResult{ + Capabilities: capabilities, + }, nil +} From 7dcc48a562578b4eeb0f79397ba08dbcb223b764 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 2 Mar 2016 21:34:06 -0500 Subject: [PATCH 03/17] Refactor http/sys_capabilities.go --- http/sys_capabilities.go | 89 +++++++++++----------------------------- 1 file changed, 25 insertions(+), 64 deletions(-) diff --git a/http/sys_capabilities.go b/http/sys_capabilities.go index 764c86b1d6..2952a5368b 100644 --- a/http/sys_capabilities.go +++ b/http/sys_capabilities.go @@ -8,89 +8,50 @@ import ( "github.com/hashicorp/vault/vault" ) -func handleSysCapabilitiesSelf(core *vault.Core) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" && r.Method != "PUT" { - respondError(w, http.StatusMethodNotAllowed, nil) - return - } - log.Printf("vishal: handleSysCapabilitiesSelf: r:%#v, r.URL:%s r.URL.Path:%s\n", r, r.URL, r.URL.Path) - // Parse the request if we can - var req capabilitiesRequest - if err := parseRequest(r, &req); err != nil { - respondError(w, http.StatusBadRequest, err) - return - } - - resp, ok := request(core, w, r, requestAuth(r, &logical.Request{ - Operation: logical.UpdateOperation, - Path: "sys/capabilities-self", - // Connection: getConnection(r), - Data: map[string]interface{}{ - "path": req.Path, - }, - })) - if !ok { - return - } - if resp == nil { - respondError(w, http.StatusNotFound, nil) - return - } - - var capabilities []string - capabilitiesRaw, ok := resp.Data["keys"] - if ok { - capabilities = capabilitiesRaw.([]string) - } - - respondOk(w, &capabilitiesResponse{Capabilities: capabilities}) - }) -} - func handleSysCapabilities(core *vault.Core) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" && r.Method != "PUT" { + switch r.Method { + case "PUT": + case "POST": + default: respondError(w, http.StatusMethodNotAllowed, nil) return } - log.Printf("vishal: handleSysCapabilities: r: %#v\n", r) + + log.Printf("r.URL.Path: %s\n", r.URL.Path) + // Get the auth for the request so we can access the token directly + req := requestAuth(r, &logical.Request{}) + log.Printf("handleSysCapabilities req:%#v\n", req) + // Parse the request if we can - var req capabilitiesRequest - if err := parseRequest(r, &req); err != nil { + var data capabilitiesRequest + if err := parseRequest(r, &data); err != nil { respondError(w, http.StatusBadRequest, err) return } + if data.Token == "" { + data.Token = req.ClientToken + } - resp, ok := request(core, w, r, requestAuth(r, &logical.Request{ - Operation: logical.UpdateOperation, - Path: "sys/capabilities", - Connection: getConnection(r), - Data: map[string]interface{}{ - "token": req.Token, - "path": req.Path, - }, - })) - if !ok { + capabilities, err := core.Capabilities(data.Token, data.Path) + if err != nil { + respondError(w, http.StatusInternalServerError, err) return } - if resp == nil { - respondError(w, http.StatusNotFound, nil) + if capabilities == nil { + respondOk(w, &capabilitiesResponse{Capabilities: nil}) return } - var capabilities []string - capabilitiesRaw, ok := resp.Data["keys"] - if ok { - capabilities = capabilitiesRaw.([]string) - } - - respondOk(w, &capabilitiesResponse{Capabilities: capabilities}) + respondOk(w, &capabilitiesResponse{ + Capabilities: capabilities.Capabilities, + }) }) + } type capabilitiesResponse struct { - Capabilities []string `json:"policies"` + Capabilities []string `json:"capabilities"` } type capabilitiesRequest struct { From c0d2b3fb3e80656eb69da8fc3c893a2a6957c4dc Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 2 Mar 2016 21:35:07 -0500 Subject: [PATCH 04/17] Remove capabilities changes from logical_system.go --- vault/logical_system.go | 112 ---------------------------------------- 1 file changed, 112 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index 7bdbd3b4b3..5217c97e7a 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -1,9 +1,7 @@ package vault import ( - "errors" "fmt" - "log" "strings" "time" @@ -279,50 +277,6 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) logical.Backend HelpDescription: strings.TrimSpace(sysHelp["policy"][1]), }, - &framework.Path{ - Pattern: "capabilities-self", - - Fields: map[string]*framework.FieldSchema{ - "token": &framework.FieldSchema{ - Type: framework.TypeString, - Description: "Token of which capabilities are being requested", - }, - "path": &framework.FieldSchema{ - Type: framework.TypeString, - Description: "Path for which token's capabilities are being fetched", - }, - }, - - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.UpdateOperation: b.handleCapabilitiesUpdate, - }, - - HelpSynopsis: strings.TrimSpace(sysHelp["policy"][0]), - HelpDescription: strings.TrimSpace(sysHelp["policy"][1]), - }, - - &framework.Path{ - Pattern: "capabilities", - - Fields: map[string]*framework.FieldSchema{ - "token": &framework.FieldSchema{ - Type: framework.TypeString, - Description: "Token of which capabilities are being requested", - }, - "path": &framework.FieldSchema{ - Type: framework.TypeString, - Description: "Path for which token's capabilities are being fetched", - }, - }, - - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.UpdateOperation: b.handleCapabilitiesUpdate, - }, - - HelpSynopsis: strings.TrimSpace(sysHelp["policy"][0]), - HelpDescription: strings.TrimSpace(sysHelp["policy"][1]), - }, - &framework.Path{ Pattern: "audit-hash/(?P.+)", @@ -427,17 +381,6 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) logical.Backend HelpSynopsis: strings.TrimSpace(sysHelp["rotate"][0]), HelpDescription: strings.TrimSpace(sysHelp["rotate"][1]), }, - - &framework.Path{ - Pattern: "rotate$", - - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.UpdateOperation: b.handleRotate, - }, - - HelpSynopsis: strings.TrimSpace(sysHelp["rotate"][0]), - HelpDescription: strings.TrimSpace(sysHelp["rotate"][1]), - }, }, } @@ -910,61 +853,6 @@ func (b *SystemBackend) handlePolicyRead( }, nil } -// handleTokenCapabilitiesUpdate handles the "token-capabilities" endpoint to -// fetch the capabilities of a token on a given path -func (b *SystemBackend) handleCapabilitiesUpdate(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { - log.Printf("\n\nvishal: logical_system: handleCapabilitiesUpdate: req:%#v data:%#v\n", req, data) - tokenStore := b.Core.tokenStore - token := data.Get("token").(string) - if token == "" { - token = req.ClientToken - } - if token == "" { - return logical.ErrorResponse("missing token"), nil - } - path := data.Get("path").(string) - if path == "" { - return logical.ErrorResponse("missing path"), nil - } - log.Printf("vishal: received: clientToken:%s token:%s path:%s\n", req.ClientToken, token, path) - te, err := tokenStore.Lookup(token) - if err != nil { - return nil, err - } - if te == nil { - return nil, errors.New("invalid token") - } - log.Printf("vishal: tokenEntry.Policies: %#v\n", te.Policies) - if te.Policies == nil { - return nil, nil - } - for _, tePolicy := range te.Policies { - log.Printf("vishal: tePolicy:%s", tePolicy) - if tePolicy == "root" { - // Add all the capabilities - } - policy, err := b.Core.policyStore.GetPolicy(tePolicy) - if err != nil { - return nil, err - } - if policy == nil { - return logical.ErrorResponse(fmt.Sprintf("policy '%s' not found", tePolicy)), nil - } - if policy.Paths == nil { - return logical.ErrorResponse(fmt.Sprintf("policy '%s' does not contain any paths", tePolicy)), nil - } - for _, pathCapability := range policy.Paths { - log.Printf("vishal: pathCapability: %#v\n", pathCapability) - log.Printf("vishal: pathCapability.Prefix: %s\n", pathCapability.Prefix) - log.Printf("vishal: pathCapability.Capabilities: %#v\n", pathCapability.Capabilities) - if path == pathCapability.Prefix { - log.Printf("vishal: found a match!!!!!!!!!!!\n") - } - } - } - return nil, nil -} - // handlePolicySet handles the "policy/" endpoint to set a policy func (b *SystemBackend) handlePolicySet( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { From b2f394d779a47017534c55d8e4c54c04a9d688dd Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 2 Mar 2016 21:36:50 -0500 Subject: [PATCH 05/17] Added capabilities and capabilities-self endpoints to http muxer --- command/capabilities.go | 6 +----- http/handler.go | 2 ++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/command/capabilities.go b/command/capabilities.go index 861f11b25e..1293908de3 100644 --- a/command/capabilities.go +++ b/command/capabilities.go @@ -2,7 +2,6 @@ package command import ( "fmt" - "log" "strings" ) @@ -31,11 +30,9 @@ func (c *CapabilitiesCommand) Run(args []string) int { switch len(args) { case 1: // only path is provided - log.Printf("only path is provided") path = args[0] case 2: // both token and path are provided - log.Printf("both token and path are provided") token = args[0] path = args[1] default: @@ -48,7 +45,6 @@ func (c *CapabilitiesCommand) Run(args []string) int { return 2 } - log.Printf("vishal: token:'%s' path:'%s'\n", token, path) var capabilities []string if token == "" { capabilities, err = client.Sys().CapabilitiesSelf(path) @@ -61,7 +57,7 @@ func (c *CapabilitiesCommand) Run(args []string) int { return 1 } - c.Ui.Output(fmt.Sprintf("Capabilities: '%s'", capabilities)) + c.Ui.Output(fmt.Sprintf("Capabilities: %s", capabilities)) return 0 } diff --git a/http/handler.go b/http/handler.go index a9be257e35..0a6a1081b5 100644 --- a/http/handler.go +++ b/http/handler.go @@ -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)) From 93a1ebe743b3b338432b5ec3aa541d88a6a6a01f Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Thu, 3 Mar 2016 09:08:27 -0500 Subject: [PATCH 06/17] remove changes from token_store.go --- vault/token_store.go | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/vault/token_store.go b/vault/token_store.go index b5368b963f..0e8e5dcff9 100644 --- a/vault/token_store.go +++ b/vault/token_store.go @@ -3,7 +3,6 @@ package vault import ( "encoding/json" "fmt" - "log" "regexp" "sort" "strings" @@ -254,28 +253,6 @@ func NewTokenStore(c *Core, config *logical.BackendConfig) (*TokenStore, error) HelpSynopsis: strings.TrimSpace(tokenRenewHelp), HelpDescription: strings.TrimSpace(tokenRenewHelp), }, - - &framework.Path{ - Pattern: "capabilities", - - Fields: map[string]*framework.FieldSchema{ - "token": &framework.FieldSchema{ - Type: framework.TypeString, - Description: "Token of which capabilities are being requested", - }, - "path": &framework.FieldSchema{ - Type: framework.TypeString, - Description: "Path for which token's capabilities are being fetched", - }, - }, - - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.UpdateOperation: t.handleCapabilitiesUpdate, - }, - - HelpSynopsis: strings.TrimSpace(tokenCapabilitiesHelp), - HelpDescription: strings.TrimSpace(tokenCapabilitiesHelp), - }, }, } @@ -553,24 +530,6 @@ func (ts *TokenStore) revokeTreeSalted(saltedId string) error { return nil } -// handleCapabilitiesUpdate handles the auth/token/capabilities path for fetching -// capabilities of a token on a given path -func (ts *TokenStore) handleCapabilitiesUpdate( - req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - log.Printf("vishal: vault/token_store.go: handleCapabilitiesRead: req:%#v data:%#v\n", req, d) - log.Println(d.Get("token").(string)) - te, err := ts.Lookup(d.Get("token").(string)) - if err != nil { - log.Printf("vishal: token lookup err:%#v\n", err) - } - if te == nil { - return logical.ErrorResponse("token does not exist"), nil - } - log.Printf("vishal: te.Policies:%#v\n", te.Policies) - log.Println(d.Get("path").(string)) - return ts.handleCreateCommon(req, d, true) -} - // handleCreate handles the auth/token/create path for creation of new orphan // tokens func (ts *TokenStore) handleCreateOrphan( @@ -977,5 +936,4 @@ as revocation of tokens. The tokens are renewable if associated with a lease.` tokenRevokePrefixHelp = `This endpoint will delete all tokens generated under a prefix with their child tokens.` tokenRenewHelp = `This endpoint will renew the given token and prevent expiration.` tokenRenewSelfHelp = `This endpoint will renew the token used to call it and prevent expiration.` - tokenCapabilitiesHelp = `This endpoint will return the capabilities of the given token on a given path.` ) From f00261785a05959ee369626e50fc35055850423f Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Thu, 3 Mar 2016 11:08:27 -0500 Subject: [PATCH 07/17] Handled root token use case --- api/sys_capabilities.go | 15 ++++++++------- command/capabilities.go | 6 ++++-- http/sys_capabilities.go | 25 +++++++++++++++++-------- vault/capabilities.go | 40 ++++++++++++---------------------------- 4 files changed, 41 insertions(+), 45 deletions(-) diff --git a/api/sys_capabilities.go b/api/sys_capabilities.go index 7c23c56d43..7f215cf6dc 100644 --- a/api/sys_capabilities.go +++ b/api/sys_capabilities.go @@ -1,6 +1,6 @@ package api -func (c *Sys) CapabilitiesSelf(path string) ([]string, error) { +func (c *Sys) CapabilitiesSelf(path string) (*CapabilitiesResponse, error) { body := map[string]string{ "path": path, } @@ -16,12 +16,12 @@ func (c *Sys) CapabilitiesSelf(path string) ([]string, error) { } defer resp.Body.Close() - var result capabilitiesResp + var result CapabilitiesResponse err = resp.DecodeJSON(&result) - return result.Capabilities, err + return &result, err } -func (c *Sys) Capabilities(token, path string) ([]string, error) { +func (c *Sys) Capabilities(token, path string) (*CapabilitiesResponse, error) { body := map[string]string{ "token": token, "path": path, @@ -38,11 +38,12 @@ func (c *Sys) Capabilities(token, path string) ([]string, error) { } defer resp.Body.Close() - var result capabilitiesResp + var result CapabilitiesResponse err = resp.DecodeJSON(&result) - return result.Capabilities, err + return &result, err } -type capabilitiesResp struct { +type CapabilitiesResponse struct { + Message string `json:"message"` Capabilities []string `json:"capabilities"` } diff --git a/command/capabilities.go b/command/capabilities.go index 1293908de3..c40c42f120 100644 --- a/command/capabilities.go +++ b/command/capabilities.go @@ -3,6 +3,8 @@ package command import ( "fmt" "strings" + + "github.com/hashicorp/vault/api" ) // CapabilitiesCommand is a Command that enables a new endpoint. @@ -45,7 +47,7 @@ func (c *CapabilitiesCommand) Run(args []string) int { return 2 } - var capabilities []string + var capabilities *api.CapabilitiesResponse if token == "" { capabilities, err = client.Sys().CapabilitiesSelf(path) } else { @@ -57,7 +59,7 @@ func (c *CapabilitiesCommand) Run(args []string) int { return 1 } - c.Ui.Output(fmt.Sprintf("Capabilities: %s", capabilities)) + c.Ui.Output(fmt.Sprintf("Capabilities:%s\nMessage:%s\n", capabilities.Capabilities, capabilities.Message)) return 0 } diff --git a/http/sys_capabilities.go b/http/sys_capabilities.go index 2952a5368b..c42fe0cf34 100644 --- a/http/sys_capabilities.go +++ b/http/sys_capabilities.go @@ -1,8 +1,8 @@ package http import ( - "log" "net/http" + "strings" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/vault" @@ -18,10 +18,8 @@ func handleSysCapabilities(core *vault.Core) http.Handler { return } - log.Printf("r.URL.Path: %s\n", r.URL.Path) // Get the auth for the request so we can access the token directly req := requestAuth(r, &logical.Request{}) - log.Printf("handleSysCapabilities req:%#v\n", req) // Parse the request if we can var data capabilitiesRequest @@ -29,7 +27,8 @@ func handleSysCapabilities(core *vault.Core) http.Handler { respondError(w, http.StatusBadRequest, err) return } - if data.Token == "" { + + if strings.HasPrefix(r.URL.Path, "/v1/sys/capabilities-self") { data.Token = req.ClientToken } @@ -39,18 +38,28 @@ func handleSysCapabilities(core *vault.Core) http.Handler { return } if capabilities == nil { - respondOk(w, &capabilitiesResponse{Capabilities: nil}) + respondOk(w, &capabilitiesResponse{Message: "Token has no capabilities on the given path"}) return } - respondOk(w, &capabilitiesResponse{ - Capabilities: capabilities.Capabilities, - }) + var response capabilitiesResponse + switch capabilities.Root { + case true: + response.Message = `Thij is a 'root' token. It has all the capabilities on all the paths. +This token can be used on any valid path.` + response.Capabilities = nil + case false: + response.Message = "" + response.Capabilities = capabilities.Capabilities + } + + respondOk(w, response) }) } type capabilitiesResponse struct { + Message string `json:"message"` Capabilities []string `json:"capabilities"` } diff --git a/vault/capabilities.go b/vault/capabilities.go index 88d7ad0ad5..16935feb52 100644 --- a/vault/capabilities.go +++ b/vault/capabilities.go @@ -8,6 +8,7 @@ import ( // CapabilitiesResult holds the result of fetching the capabilities of token on a path type CapabilitiesResult struct { + Root bool Capabilities []string } @@ -33,46 +34,32 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResult, error) { return nil, nil } - maps := make(map[string]bool) + var result CapabilitiesResult + capabilities := make(map[string]bool) for _, tePolicy := range te.Policies { if tePolicy == "root" { - //TODO: check if the path is actually a valid path. Otherwise, there is no - // meaning in returning the capabilities - // Add all the capabilities - maps["create"] = true - maps["read"] = true - maps["update"] = true - maps["delete"] = true - maps["list"] = true - maps["sudo"] = true + result.Root = true break } policy, err := c.policyStore.GetPolicy(tePolicy) if err != nil { return nil, err } - if policy == nil { - return nil, fmt.Errorf("policy '%s' not found", tePolicy) - } - - if policy.Paths == nil { - return nil, fmt.Errorf("policy '%s' does not contain any paths", tePolicy) - } for _, pathCapability := range policy.Paths { switch pathCapability.Glob { case true: if strings.HasPrefix(path, pathCapability.Prefix) { for _, capability := range pathCapability.Capabilities { - if _, ok := maps[capability]; !ok { - maps[capability] = true + if _, ok := capabilities[capability]; !ok { + capabilities[capability] = true } } } case false: if path == pathCapability.Prefix { for _, capability := range pathCapability.Capabilities { - if _, ok := maps[capability]; !ok { - maps[capability] = true + if _, ok := capabilities[capability]; !ok { + capabilities[capability] = true } } } @@ -80,12 +67,9 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResult, error) { } } - var capabilities []string - for capability, _ := range maps { - capabilities = append(capabilities, capability) + for capability, _ := range capabilities { + result.Capabilities = append(result.Capabilities, capability) } - sort.Strings(capabilities) - return &CapabilitiesResult{ - Capabilities: capabilities, - }, nil + sort.Strings(result.Capabilities) + return &result, nil } From 894f2ccef1a484414b2f6bc3d5c0b0ab443d77e7 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Thu, 3 Mar 2016 11:54:14 -0500 Subject: [PATCH 08/17] self review rework --- command/capabilities.go | 17 ++++++++++------- http/sys_capabilities.go | 28 +++++++++++++++++----------- vault/capabilities.go | 16 ++++++++++++---- 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/command/capabilities.go b/command/capabilities.go index c40c42f120..ca6a2741e4 100644 --- a/command/capabilities.go +++ b/command/capabilities.go @@ -47,11 +47,11 @@ func (c *CapabilitiesCommand) Run(args []string) int { return 2 } - var capabilities *api.CapabilitiesResponse + var resp *api.CapabilitiesResponse if token == "" { - capabilities, err = client.Sys().CapabilitiesSelf(path) + resp, err = client.Sys().CapabilitiesSelf(path) } else { - capabilities, err = client.Sys().Capabilities(token, path) + resp, err = client.Sys().Capabilities(token, path) } if err != nil { c.Ui.Error(fmt.Sprintf( @@ -59,7 +59,10 @@ func (c *CapabilitiesCommand) Run(args []string) int { return 1 } - c.Ui.Output(fmt.Sprintf("Capabilities:%s\nMessage:%s\n", capabilities.Capabilities, capabilities.Message)) + c.Ui.Output(fmt.Sprintf("Capabilities: %s", resp.Capabilities)) + if resp.Message != "" { + c.Ui.Output(fmt.Sprintf("Message: %s", resp.Message)) + } return 0 } @@ -72,9 +75,9 @@ func (c *CapabilitiesCommand) Help() string { Usage: vault capabilities [options] [token] path Fetch the capabilities of a token on a given path. - If a token is given to the command '/sys/capabilities' will be called with - the given token; otherwise '/sys/capabilities-self' will be called with the - client token. + If a token is provided to the command, API '/sys/capabilities' will be invoked + with the given token; otherwise API '/sys/capabilities-self' will be invoked with + the client token. General Options: diff --git a/http/sys_capabilities.go b/http/sys_capabilities.go index c42fe0cf34..6f1eb46feb 100644 --- a/http/sys_capabilities.go +++ b/http/sys_capabilities.go @@ -32,28 +32,34 @@ func handleSysCapabilities(core *vault.Core) http.Handler { data.Token = req.ClientToken } - capabilities, err := core.Capabilities(data.Token, data.Path) + resp, err := core.Capabilities(data.Token, data.Path) if err != nil { respondError(w, http.StatusInternalServerError, err) return } - if capabilities == nil { - respondOk(w, &capabilitiesResponse{Message: "Token has no capabilities on the given path"}) + if resp == nil { + respondOk(w, &capabilitiesResponse{ + Message: "Token has no capabilities on the path", + Capabilities: nil, + }) return } - var response capabilitiesResponse - switch capabilities.Root { + var result capabilitiesResponse + switch resp.Root { case true: - response.Message = `Thij is a 'root' token. It has all the capabilities on all the paths. -This token can be used on any valid path.` - response.Capabilities = nil + result.Message = "This is a 'root' token. It has all the capabilities on all the 'valid' paths." + result.Capabilities = nil case false: - response.Message = "" - response.Capabilities = capabilities.Capabilities + if len(resp.Capabilities) == 0 { + result.Message = "Token has no capabilities on the path" + } else { + result.Message = "" + } + result.Capabilities = resp.Capabilities } - respondOk(w, response) + respondOk(w, result) }) } diff --git a/vault/capabilities.go b/vault/capabilities.go index 16935feb52..3730914248 100644 --- a/vault/capabilities.go +++ b/vault/capabilities.go @@ -6,14 +6,14 @@ import ( "strings" ) -// CapabilitiesResult holds the result of fetching the capabilities of token on a path -type CapabilitiesResult struct { +// CapabilitiesResponse holds the result of fetching the capabilities of token on a path +type CapabilitiesResponse struct { Root bool Capabilities []string } // Capabilities is used to fetch the capabilities of the given token on the given path -func (c *Core) Capabilities(token, path string) (*CapabilitiesResult, error) { +func (c *Core) Capabilities(token, path string) (*CapabilitiesResponse, error) { if path == "" { return nil, fmt.Errorf("missing path") } @@ -34,7 +34,7 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResult, error) { return nil, nil } - var result CapabilitiesResult + var result CapabilitiesResponse capabilities := make(map[string]bool) for _, tePolicy := range te.Policies { if tePolicy == "root" { @@ -45,6 +45,9 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResult, error) { if err != nil { return nil, err } + if policy == nil || policy.Paths == nil { + continue + } for _, pathCapability := range policy.Paths { switch pathCapability.Glob { case true: @@ -67,6 +70,11 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResult, error) { } } + if len(capabilities) == 0 { + result.Capabilities = nil + return &result, nil + } + for capability, _ := range capabilities { result.Capabilities = append(result.Capabilities, capability) } From 42a7bab69e00c128403fe7505618c2a4dff8f62d Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Thu, 3 Mar 2016 12:24:28 -0500 Subject: [PATCH 09/17] Test files for capabilities endpoint --- command/capabilities_test.go | 37 +++++++++++++++++++++++++++++++++++ http/sys_capabilities_test.go | 1 + vault/capabilities_test.go | 1 + 3 files changed, 39 insertions(+) create mode 100644 command/capabilities_test.go create mode 100644 http/sys_capabilities_test.go create mode 100644 vault/capabilities_test.go diff --git a/command/capabilities_test.go b/command/capabilities_test.go new file mode 100644 index 0000000000..312f45423a --- /dev/null +++ b/command/capabilities_test.go @@ -0,0 +1,37 @@ +package command + +import ( + "testing" + + "github.com/hashicorp/vault/http" + "github.com/hashicorp/vault/vault" + "github.com/mitchellh/cli" +) + +func TestCapabilities_Args(t *testing.T) { + core, key, _ := vault.TestCoreUnsealed(t) + ln, _ := http.TestServer(t, core) + defer ln.Close() + + ui := new(cli.MockUi) + c := &CapabilitiesCommand{ + Meta: Meta{ + Ui: ui, + }, + } + + args := []string{} + if code := c.Run(args); code == 0 { + t.Fatalf("expected failure due to no args") + } + + args = []string{"test"} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + args = []string{string(key), "test"} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } +} diff --git a/http/sys_capabilities_test.go b/http/sys_capabilities_test.go new file mode 100644 index 0000000000..d02cfda642 --- /dev/null +++ b/http/sys_capabilities_test.go @@ -0,0 +1 @@ +package http diff --git a/vault/capabilities_test.go b/vault/capabilities_test.go new file mode 100644 index 0000000000..f7e34c8a37 --- /dev/null +++ b/vault/capabilities_test.go @@ -0,0 +1 @@ +package vault From 5b9ef4ec2e15fc916e29697438f7fe7aa87cf95e Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Thu, 3 Mar 2016 14:00:36 -0500 Subject: [PATCH 10/17] testcase changes --- command/capabilities_test.go | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/command/capabilities_test.go b/command/capabilities_test.go index 312f45423a..198e0820dd 100644 --- a/command/capabilities_test.go +++ b/command/capabilities_test.go @@ -9,7 +9,7 @@ import ( ) func TestCapabilities_Args(t *testing.T) { - core, key, _ := vault.TestCoreUnsealed(t) + core, _, _ := vault.TestCoreUnsealed(t) ln, _ := http.TestServer(t, core) defer ln.Close() @@ -25,13 +25,30 @@ func TestCapabilities_Args(t *testing.T) { t.Fatalf("expected failure due to no args") } - args = []string{"test"} - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + args = []string{"invalidtoken", "test"} + if code := c.Run(args); code == 0 { + t.Fatalf("expected failure due to no invalid token") } - args = []string{string(key), "test"} - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } + /* + args = []string{"test"} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + log.Printf("result1: %s\n", string(ui.OutputWriter.Bytes())) + if !strings.Contains(string(ui.OutputWriter.Bytes()), "This is a 'root' token.") { + t.Fatalf("bad: %s", ui.OutputWriter.String()) + } + + args = []string{string(key), "test"} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + log.Printf("result2: %s\n", string(ui.OutputWriter.Bytes())) + if !strings.Contains(string(ui.OutputWriter.Bytes()), "This is a 'root' token.") { + t.Fatalf("bad: %s", ui.OutputWriter.String()) + } + */ } From a7cfc9cc7acb7e6b43ee00ce2f28cd5bf1d5bea8 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 4 Mar 2016 08:40:13 -0500 Subject: [PATCH 11/17] Removing the 'Message' field --- api/sys_capabilities.go | 1 - command/capabilities.go | 3 --- http/sys_capabilities.go | 8 -------- vault/capabilities.go | 12 +++++++----- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/api/sys_capabilities.go b/api/sys_capabilities.go index 7f215cf6dc..5001986749 100644 --- a/api/sys_capabilities.go +++ b/api/sys_capabilities.go @@ -44,6 +44,5 @@ func (c *Sys) Capabilities(token, path string) (*CapabilitiesResponse, error) { } type CapabilitiesResponse struct { - Message string `json:"message"` Capabilities []string `json:"capabilities"` } diff --git a/command/capabilities.go b/command/capabilities.go index ca6a2741e4..9f60d6854f 100644 --- a/command/capabilities.go +++ b/command/capabilities.go @@ -60,9 +60,6 @@ func (c *CapabilitiesCommand) Run(args []string) int { } c.Ui.Output(fmt.Sprintf("Capabilities: %s", resp.Capabilities)) - if resp.Message != "" { - c.Ui.Output(fmt.Sprintf("Message: %s", resp.Message)) - } return 0 } diff --git a/http/sys_capabilities.go b/http/sys_capabilities.go index 6f1eb46feb..d6d638b90f 100644 --- a/http/sys_capabilities.go +++ b/http/sys_capabilities.go @@ -39,7 +39,6 @@ func handleSysCapabilities(core *vault.Core) http.Handler { } if resp == nil { respondOk(w, &capabilitiesResponse{ - Message: "Token has no capabilities on the path", Capabilities: nil, }) return @@ -48,14 +47,8 @@ func handleSysCapabilities(core *vault.Core) http.Handler { var result capabilitiesResponse switch resp.Root { case true: - result.Message = "This is a 'root' token. It has all the capabilities on all the 'valid' paths." result.Capabilities = nil case false: - if len(resp.Capabilities) == 0 { - result.Message = "Token has no capabilities on the path" - } else { - result.Message = "" - } result.Capabilities = resp.Capabilities } @@ -65,7 +58,6 @@ func handleSysCapabilities(core *vault.Core) http.Handler { } type capabilitiesResponse struct { - Message string `json:"message"` Capabilities []string `json:"capabilities"` } diff --git a/vault/capabilities.go b/vault/capabilities.go index 3730914248..f5a7797aa3 100644 --- a/vault/capabilities.go +++ b/vault/capabilities.go @@ -38,7 +38,9 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResponse, error) { capabilities := make(map[string]bool) for _, tePolicy := range te.Policies { if tePolicy == "root" { - result.Root = true + capabilities = map[string]bool{ + "root": true, + } break } policy, err := c.policyStore.GetPolicy(tePolicy) @@ -49,8 +51,8 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResponse, error) { continue } for _, pathCapability := range policy.Paths { - switch pathCapability.Glob { - case true: + switch { + case pathCapability.Glob: if strings.HasPrefix(path, pathCapability.Prefix) { for _, capability := range pathCapability.Capabilities { if _, ok := capabilities[capability]; !ok { @@ -58,7 +60,7 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResponse, error) { } } } - case false: + default: if path == pathCapability.Prefix { for _, capability := range pathCapability.Capabilities { if _, ok := capabilities[capability]; !ok { @@ -71,7 +73,7 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResponse, error) { } if len(capabilities) == 0 { - result.Capabilities = nil + result.Capabilities = []string{"deny"} return &result, nil } From 0792b147366901c04080500f8a8ac3a797de4488 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 4 Mar 2016 12:04:26 -0500 Subject: [PATCH 12/17] Adding acl.Capabilities to do the path matching --- vault/acl.go | 50 +++++++++++++++++++++++ vault/capabilities.go | 95 ++++++++++++++++++++++++++----------------- 2 files changed, 107 insertions(+), 38 deletions(-) diff --git a/vault/acl.go b/vault/acl.go index ac4922407b..6dc4e9bc67 100644 --- a/vault/acl.go +++ b/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{"root"} + } + + // 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 nil + } 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" capability is explicitly set, then ignore all other capabilities + if capabilities&DenyCapabilityInt > 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. diff --git a/vault/capabilities.go b/vault/capabilities.go index f5a7797aa3..c450684d46 100644 --- a/vault/capabilities.go +++ b/vault/capabilities.go @@ -1,10 +1,6 @@ package vault -import ( - "fmt" - "sort" - "strings" -) +import "fmt" // CapabilitiesResponse holds the result of fetching the capabilities of token on a path type CapabilitiesResponse struct { @@ -34,52 +30,75 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResponse, error) { return nil, nil } - var result CapabilitiesResponse - capabilities := make(map[string]bool) + var policies []*Policy for _, tePolicy := range te.Policies { - if tePolicy == "root" { - capabilities = map[string]bool{ - "root": true, - } - break - } policy, err := c.policyStore.GetPolicy(tePolicy) if err != nil { return nil, err } - if policy == nil || policy.Paths == nil { - continue - } - for _, pathCapability := range policy.Paths { - switch { - case pathCapability.Glob: - if strings.HasPrefix(path, pathCapability.Prefix) { - for _, capability := range pathCapability.Capabilities { - if _, ok := capabilities[capability]; !ok { - capabilities[capability] = true + policies = append(policies, policy) + } + + if len(policies) == 0 { + return nil, nil + } + + acl, err := NewACL(policies) + if err != nil { + return nil, err + } + + caps := acl.Capabilities(path) + /* + log.Printf("vishal: caps:%#v\n", caps) + + var result CapabilitiesResponse + capabilities := make(map[string]bool) + for _, tePolicy := range te.Policies { + if tePolicy == "root" { + capabilities = map[string]bool{ + "root": true, + } + break + } + policy, err := c.policyStore.GetPolicy(tePolicy) + if err != nil { + return nil, err + } + if policy == nil || policy.Paths == nil { + continue + } + for _, pathCapability := range policy.Paths { + switch { + case pathCapability.Glob: + if strings.HasPrefix(path, pathCapability.Prefix) { + for _, capability := range pathCapability.Capabilities { + if _, ok := capabilities[capability]; !ok { + capabilities[capability] = true + } } } - } - default: - if path == pathCapability.Prefix { - for _, capability := range pathCapability.Capabilities { - if _, ok := capabilities[capability]; !ok { - capabilities[capability] = true + default: + if path == pathCapability.Prefix { + for _, capability := range pathCapability.Capabilities { + if _, ok := capabilities[capability]; !ok { + capabilities[capability] = true + } } } } } } - } - if len(capabilities) == 0 { - result.Capabilities = []string{"deny"} - return &result, nil - } + if len(capabilities) == 0 { + result.Capabilities = []string{"deny"} + return &result, nil + } - for capability, _ := range capabilities { - result.Capabilities = append(result.Capabilities, capability) - } - sort.Strings(result.Capabilities) + for capability, _ := range capabilities { + result.Capabilities = append(result.Capabilities, capability) + } + sort.Strings(result.Capabilities) + */ return &result, nil } From 7f832f22aafe4d18407b2dee79991edcd4e32e60 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 4 Mar 2016 13:21:07 -0500 Subject: [PATCH 13/17] refactoring changes due to acl.Capabilities --- api/sys_capabilities.go | 8 ++--- command/capabilities.go | 24 +++++++-------- http/sys_capabilities.go | 20 +++--------- vault/acl.go | 12 ++++---- vault/capabilities.go | 66 +++------------------------------------- vault/policy.go | 1 + 6 files changed, 30 insertions(+), 101 deletions(-) diff --git a/api/sys_capabilities.go b/api/sys_capabilities.go index 5001986749..1640a2fc9b 100644 --- a/api/sys_capabilities.go +++ b/api/sys_capabilities.go @@ -1,6 +1,6 @@ package api -func (c *Sys) CapabilitiesSelf(path string) (*CapabilitiesResponse, error) { +func (c *Sys) CapabilitiesSelf(path string) ([]string, error) { body := map[string]string{ "path": path, } @@ -18,10 +18,10 @@ func (c *Sys) CapabilitiesSelf(path string) (*CapabilitiesResponse, error) { var result CapabilitiesResponse err = resp.DecodeJSON(&result) - return &result, err + return result.Capabilities, err } -func (c *Sys) Capabilities(token, path string) (*CapabilitiesResponse, error) { +func (c *Sys) Capabilities(token, path string) ([]string, error) { body := map[string]string{ "token": token, "path": path, @@ -40,7 +40,7 @@ func (c *Sys) Capabilities(token, path string) (*CapabilitiesResponse, error) { var result CapabilitiesResponse err = resp.DecodeJSON(&result) - return &result, err + return result.Capabilities, err } type CapabilitiesResponse struct { diff --git a/command/capabilities.go b/command/capabilities.go index 9f60d6854f..0c637c37b6 100644 --- a/command/capabilities.go +++ b/command/capabilities.go @@ -3,8 +3,6 @@ package command import ( "fmt" "strings" - - "github.com/hashicorp/vault/api" ) // CapabilitiesCommand is a Command that enables a new endpoint. @@ -29,15 +27,12 @@ func (c *CapabilitiesCommand) Run(args []string) int { var token string var path string - switch len(args) { - case 1: - // only path is provided + switch { + case len(args) == 1: path = args[0] - case 2: - // both token and path are provided + case len(args) == 2: token = args[0] path = args[1] - default: } client, err := c.Client() @@ -47,11 +42,11 @@ func (c *CapabilitiesCommand) Run(args []string) int { return 2 } - var resp *api.CapabilitiesResponse + var capabilities []string if token == "" { - resp, err = client.Sys().CapabilitiesSelf(path) + capabilities, err = client.Sys().CapabilitiesSelf(path) } else { - resp, err = client.Sys().Capabilities(token, path) + capabilities, err = client.Sys().Capabilities(token, path) } if err != nil { c.Ui.Error(fmt.Sprintf( @@ -59,12 +54,12 @@ func (c *CapabilitiesCommand) Run(args []string) int { return 1 } - c.Ui.Output(fmt.Sprintf("Capabilities: %s", resp.Capabilities)) + c.Ui.Output(fmt.Sprintf("Capabilities: %s", capabilities)) return 0 } func (c *CapabilitiesCommand) Synopsis() string { - return "Fetch the capabilities of a given token on a given path" + return "Fetch the capabilities of a token on a given path" } func (c *CapabilitiesCommand) Help() string { @@ -76,6 +71,9 @@ Usage: vault capabilities [options] [token] path with the given token; otherwise API '/sys/capabilities-self' will be invoked with the client token. + Note that this command will respond with a ["deny"] capability if the given path + is invalid. + General Options: ` + generalOptionsUsage() diff --git a/http/sys_capabilities.go b/http/sys_capabilities.go index d6d638b90f..22f85ea7f7 100644 --- a/http/sys_capabilities.go +++ b/http/sys_capabilities.go @@ -32,27 +32,15 @@ func handleSysCapabilities(core *vault.Core) http.Handler { data.Token = req.ClientToken } - resp, err := core.Capabilities(data.Token, data.Path) + capabilities, err := core.Capabilities(data.Token, data.Path) if err != nil { respondError(w, http.StatusInternalServerError, err) return } - if resp == nil { - respondOk(w, &capabilitiesResponse{ - Capabilities: nil, - }) - return - } - var result capabilitiesResponse - switch resp.Root { - case true: - result.Capabilities = nil - case false: - result.Capabilities = resp.Capabilities - } - - respondOk(w, result) + respondOk(w, &capabilitiesResponse{ + Capabilities: capabilities, + }) }) } diff --git a/vault/acl.go b/vault/acl.go index 6dc4e9bc67..ead38089d4 100644 --- a/vault/acl.go +++ b/vault/acl.go @@ -74,7 +74,7 @@ func NewACL(policies []*Policy) (*ACL, error) { func (a *ACL) Capabilities(path string) (pathCapabilities []string) { // Fast-path root if a.root { - return []string{"root"} + return []string{RootCapability} } // Find an exact matching rule, look for glob if no match @@ -88,13 +88,12 @@ func (a *ACL) Capabilities(path string) (pathCapabilities []string) { // Find a glob rule, default deny if no match _, raw, ok = a.globRules.LongestPrefix(path) if !ok { - return nil + return []string{DenyCapability} } else { capabilities = raw.(uint32) } CHECK: - if capabilities&SudoCapabilityInt > 0 { pathCapabilities = append(pathCapabilities, SudoCapability) } @@ -113,11 +112,12 @@ CHECK: if capabilities&CreateCapabilityInt > 0 { pathCapabilities = append(pathCapabilities, CreateCapability) } - // If "deny" capability is explicitly set, then ignore all other capabilities - if capabilities&DenyCapabilityInt > 0 { + + // 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 } diff --git a/vault/capabilities.go b/vault/capabilities.go index c450684d46..fcbdcd88f5 100644 --- a/vault/capabilities.go +++ b/vault/capabilities.go @@ -2,14 +2,8 @@ package vault import "fmt" -// CapabilitiesResponse holds the result of fetching the capabilities of token on a path -type CapabilitiesResponse struct { - Root bool - Capabilities []string -} - // Capabilities is used to fetch the capabilities of the given token on the given path -func (c *Core) Capabilities(token, path string) (*CapabilitiesResponse, error) { +func (c *Core) Capabilities(token, path string) ([]string, error) { if path == "" { return nil, fmt.Errorf("missing path") } @@ -27,7 +21,7 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResponse, error) { } if te.Policies == nil { - return nil, nil + return []string{DenyCapability}, nil } var policies []*Policy @@ -40,7 +34,7 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResponse, error) { } if len(policies) == 0 { - return nil, nil + return []string{DenyCapability}, nil } acl, err := NewACL(policies) @@ -48,57 +42,5 @@ func (c *Core) Capabilities(token, path string) (*CapabilitiesResponse, error) { return nil, err } - caps := acl.Capabilities(path) - /* - log.Printf("vishal: caps:%#v\n", caps) - - var result CapabilitiesResponse - capabilities := make(map[string]bool) - for _, tePolicy := range te.Policies { - if tePolicy == "root" { - capabilities = map[string]bool{ - "root": true, - } - break - } - policy, err := c.policyStore.GetPolicy(tePolicy) - if err != nil { - return nil, err - } - if policy == nil || policy.Paths == nil { - continue - } - for _, pathCapability := range policy.Paths { - switch { - case pathCapability.Glob: - if strings.HasPrefix(path, pathCapability.Prefix) { - for _, capability := range pathCapability.Capabilities { - if _, ok := capabilities[capability]; !ok { - capabilities[capability] = true - } - } - } - default: - if path == pathCapability.Prefix { - for _, capability := range pathCapability.Capabilities { - if _, ok := capabilities[capability]; !ok { - capabilities[capability] = true - } - } - } - } - } - } - - if len(capabilities) == 0 { - result.Capabilities = []string{"deny"} - return &result, nil - } - - for capability, _ := range capabilities { - result.Capabilities = append(result.Capabilities, capability) - } - sort.Strings(result.Capabilities) - */ - return &result, nil + return acl.Capabilities(path), nil } diff --git a/vault/policy.go b/vault/policy.go index 6f856e7b72..7e459f59a9 100644 --- a/vault/policy.go +++ b/vault/policy.go @@ -15,6 +15,7 @@ const ( DeleteCapability = "delete" ListCapability = "list" SudoCapability = "sudo" + RootCapability = "root" // Backwards compatibility OldDenyPathPolicy = "deny" From 0f82724f1f618a445a3c3fd7349408df5d6150d4 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Sat, 5 Mar 2016 00:03:55 -0500 Subject: [PATCH 14/17] test cases for capabilities endpoint --- command/capabilities_test.go | 48 ++++++++------------- http/sys_capabilities_test.go | 81 +++++++++++++++++++++++++++++++++++ vault/acl_test.go | 45 +++++++++++++++++++ vault/capabilities_test.go | 44 +++++++++++++++++++ 4 files changed, 189 insertions(+), 29 deletions(-) diff --git a/command/capabilities_test.go b/command/capabilities_test.go index 198e0820dd..9c71fcf8a7 100644 --- a/command/capabilities_test.go +++ b/command/capabilities_test.go @@ -8,47 +8,37 @@ import ( "github.com/mitchellh/cli" ) -func TestCapabilities_Args(t *testing.T) { - core, _, _ := vault.TestCoreUnsealed(t) - ln, _ := http.TestServer(t, core) +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{ - Ui: ui, + ClientToken: token, + Ui: ui, }, } - args := []string{} + var args []string + + args = []string{"-address", addr} if code := c.Run(args); code == 0 { t.Fatalf("expected failure due to no args") } - args = []string{"invalidtoken", "test"} + 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 no invalid token") } - - /* - args = []string{"test"} - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - log.Printf("result1: %s\n", string(ui.OutputWriter.Bytes())) - if !strings.Contains(string(ui.OutputWriter.Bytes()), "This is a 'root' token.") { - t.Fatalf("bad: %s", ui.OutputWriter.String()) - } - - args = []string{string(key), "test"} - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - log.Printf("result2: %s\n", string(ui.OutputWriter.Bytes())) - if !strings.Contains(string(ui.OutputWriter.Bytes()), "This is a 'root' token.") { - t.Fatalf("bad: %s", ui.OutputWriter.String()) - } - */ } diff --git a/http/sys_capabilities_test.go b/http/sys_capabilities_test.go index d02cfda642..f192e2b0a6 100644 --- a/http/sys_capabilities_test.go +++ b/http/sys_capabilities_test.go @@ -1 +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) + } +} diff --git a/vault/acl_test.go b/vault/acl_test.go index 41df6a2071..ac1ab4f7f9 100644 --- a/vault/acl_test.go +++ b/vault/acl_test.go @@ -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"}} diff --git a/vault/capabilities_test.go b/vault/capabilities_test.go index f7e34c8a37..8367dc90bd 100644 --- a/vault/capabilities_test.go +++ b/vault/capabilities_test.go @@ -1 +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) + } +} From b5ca8ba5bcd2ba5f61665d71ded70d52cf170e02 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Sat, 5 Mar 2016 00:54:48 -0500 Subject: [PATCH 15/17] Documentation for capabilities and capabilities-self APIs --- command/capabilities.go | 15 ++++-- .../docs/http/sys-capabilities-self.html.md | 44 +++++++++++++++++ .../source/docs/http/sys-capabilities.html.md | 48 +++++++++++++++++++ website/source/layouts/http.erb | 8 ++++ 4 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 website/source/docs/http/sys-capabilities-self.html.md create mode 100644 website/source/docs/http/sys-capabilities.html.md diff --git a/command/capabilities.go b/command/capabilities.go index 0c637c37b6..17e8b59712 100644 --- a/command/capabilities.go +++ b/command/capabilities.go @@ -33,6 +33,10 @@ func (c *CapabilitiesCommand) Run(args []string) int { 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() @@ -67,12 +71,13 @@ func (c *CapabilitiesCommand) Help() string { Usage: vault capabilities [options] [token] path Fetch the capabilities of a token on a given path. - If a token is provided to the command, API '/sys/capabilities' will be invoked - with the given token; otherwise API '/sys/capabilities-self' will be invoked with - the client token. + If a token is provided as an argument, '/sys/capabilities' endpoint will be invoked + with the given token; otherwise '/sys/capabilities-self' endpoing will be invoked + with the client token. - Note that this command will respond with a ["deny"] capability if the given path - is invalid. + If a token does not have any capability on a given path, or if any of the policies + belonging to the token explicitly had ["deny"] capability, or if the argument path + is invalid, this command will respond with a ["deny"]. General Options: diff --git a/website/source/docs/http/sys-capabilities-self.html.md b/website/source/docs/http/sys-capabilities-self.html.md new file mode 100644 index 0000000000..df51ecfb97 --- /dev/null +++ b/website/source/docs/http/sys-capabilities-self.html.md @@ -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 + +
+
Description
+
+ Returns the capabilities of client token on the given path. + Client token is the Vault token with which this API call is made. +
+ +
Method
+
POST
+ +
Parameters
+
+
    +
  • + path + required + Path on which the client token's capabilities will be checked. +
  • +
+
+ +
Returns
+
+ + ```javascript + { + "capabilities": ["read", "list"] + } + ``` + +
+
diff --git a/website/source/docs/http/sys-capabilities.html.md b/website/source/docs/http/sys-capabilities.html.md new file mode 100644 index 0000000000..fc2e7c5e63 --- /dev/null +++ b/website/source/docs/http/sys-capabilities.html.md @@ -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 + +
+
Description
+
+ Returns the capabilities of the token on the given path. +
+ +
Method
+
POST
+ +
Parameters
+
+
    +
  • + token + required + Token for which capabilities are being queried. +
  • +
  • + path + required + Path on which the token's capabilities will be checked. +
  • +
+
+ +
Returns
+
+ + ```javascript + { + "capabilities": ["read", "list"] + } + ``` + +
+
diff --git a/website/source/layouts/http.erb b/website/source/layouts/http.erb index ee75b37f9c..b8ea3443e1 100644 --- a/website/source/layouts/http.erb +++ b/website/source/layouts/http.erb @@ -69,6 +69,14 @@ > /sys/policy + + > + /sys/capabilities + + + > + /sys/capabilities-self + From 1b9e486bfd428e84334bbf094270ca5b5e1b4f39 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Mon, 7 Mar 2016 18:36:26 -0500 Subject: [PATCH 16/17] use errwrap to check the type of error message, fix typos --- command/capabilities.go | 6 +++--- command/capabilities_test.go | 2 +- http/sys_capabilities.go | 12 ++++++++++-- vault/capabilities.go | 12 ++++++++---- .../source/docs/http/sys-capabilities-self.html.md | 8 ++++---- website/source/docs/http/sys-capabilities.html.md | 10 +++++----- 6 files changed, 31 insertions(+), 19 deletions(-) diff --git a/command/capabilities.go b/command/capabilities.go index 17e8b59712..b33a6914a2 100644 --- a/command/capabilities.go +++ b/command/capabilities.go @@ -71,12 +71,12 @@ func (c *CapabilitiesCommand) Help() string { Usage: vault capabilities [options] [token] path Fetch the capabilities of a token on a given path. - If a token is provided as an argument, '/sys/capabilities' endpoint will be invoked - with the given token; otherwise '/sys/capabilities-self' endpoing will be invoked + 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 had ["deny"] capability, or if the argument path + belonging to the token explicitly have ["deny"] capability, or if the argument path is invalid, this command will respond with a ["deny"]. General Options: diff --git a/command/capabilities_test.go b/command/capabilities_test.go index 9c71fcf8a7..d300ff6b67 100644 --- a/command/capabilities_test.go +++ b/command/capabilities_test.go @@ -39,6 +39,6 @@ func TestCapabilities_Basic(t *testing.T) { args = []string{"-address", addr, "invalidtoken", "test"} if code := c.Run(args); code == 0 { - t.Fatalf("expected failure due to no invalid token") + t.Fatalf("expected failure due to invalid token") } } diff --git a/http/sys_capabilities.go b/http/sys_capabilities.go index 22f85ea7f7..a17ab34df4 100644 --- a/http/sys_capabilities.go +++ b/http/sys_capabilities.go @@ -4,6 +4,7 @@ import ( "net/http" "strings" + "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/vault" ) @@ -34,8 +35,15 @@ func handleSysCapabilities(core *vault.Core) http.Handler { capabilities, err := core.Capabilities(data.Token, data.Path) if err != nil { - respondError(w, http.StatusInternalServerError, err) - return + if errwrap.Contains(err, "invalid token") || + errwrap.Contains(err, "missing path") || + errwrap.Contains(err, "missing token") { + respondError(w, http.StatusBadRequest, err) + return + } else { + respondError(w, http.StatusInternalServerError, err) + return + } } respondOk(w, &capabilitiesResponse{ diff --git a/vault/capabilities.go b/vault/capabilities.go index fcbdcd88f5..5772884e7e 100644 --- a/vault/capabilities.go +++ b/vault/capabilities.go @@ -1,15 +1,19 @@ package vault -import "fmt" +import ( + "fmt" + + "github.com/hashicorp/errwrap" +) // 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, fmt.Errorf("missing path") + return nil, errwrap.Wrapf("{{err}}", fmt.Errorf("missing path")) } if token == "" { - return nil, fmt.Errorf("missing token") + return nil, errwrap.Wrapf("{{err}}", fmt.Errorf("missing token")) } te, err := c.tokenStore.Lookup(token) @@ -17,7 +21,7 @@ func (c *Core) Capabilities(token, path string) ([]string, error) { return nil, err } if te == nil { - return nil, fmt.Errorf("invalid token") + return nil, errwrap.Wrapf("{{err}}", fmt.Errorf("invalid token")) } if te.Policies == nil { diff --git a/website/source/docs/http/sys-capabilities-self.html.md b/website/source/docs/http/sys-capabilities-self.html.md index df51ecfb97..f4fdbffba0 100644 --- a/website/source/docs/http/sys-capabilities-self.html.md +++ b/website/source/docs/http/sys-capabilities-self.html.md @@ -26,7 +26,7 @@ description: |-
  • path required - Path on which the client token's capabilities will be checked. + Path on which the client token's capabilities will be checked.
  • @@ -35,9 +35,9 @@ description: |-
    ```javascript - { - "capabilities": ["read", "list"] - } + { + "capabilities": ["read", "list"] + } ```
    diff --git a/website/source/docs/http/sys-capabilities.html.md b/website/source/docs/http/sys-capabilities.html.md index fc2e7c5e63..4776bfc06a 100644 --- a/website/source/docs/http/sys-capabilities.html.md +++ b/website/source/docs/http/sys-capabilities.html.md @@ -25,12 +25,12 @@ description: |-
  • token required - Token for which capabilities are being queried. + Token for which capabilities are being queried.
  • path required - Path on which the token's capabilities will be checked. + Path on which the token's capabilities will be checked.
  • @@ -39,9 +39,9 @@ description: |-
    ```javascript - { - "capabilities": ["read", "list"] - } + { + "capabilities": ["read", "list"] + } ```
    From 4e05730e7b3db471a6da1992600917c24be9d9de Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Mon, 7 Mar 2016 22:16:09 -0500 Subject: [PATCH 17/17] Introduced ErrUserInput to distinguish user error from server error --- http/sys_capabilities.go | 4 +--- vault/capabilities.go | 26 +++++++++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/http/sys_capabilities.go b/http/sys_capabilities.go index a17ab34df4..48f41c778c 100644 --- a/http/sys_capabilities.go +++ b/http/sys_capabilities.go @@ -35,9 +35,7 @@ func handleSysCapabilities(core *vault.Core) http.Handler { capabilities, err := core.Capabilities(data.Token, data.Path) if err != nil { - if errwrap.Contains(err, "invalid token") || - errwrap.Contains(err, "missing path") || - errwrap.Contains(err, "missing token") { + if errwrap.ContainsType(err, new(vault.ErrUserInput)) { respondError(w, http.StatusBadRequest, err) return } else { diff --git a/vault/capabilities.go b/vault/capabilities.go index 5772884e7e..fb9c1ad934 100644 --- a/vault/capabilities.go +++ b/vault/capabilities.go @@ -1,19 +1,29 @@ package vault -import ( - "fmt" +// 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 +} - "github.com/hashicorp/errwrap" -) +// 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, errwrap.Wrapf("{{err}}", fmt.Errorf("missing path")) + return nil, &ErrUserInput{ + Message: "missing path", + } } if token == "" { - return nil, errwrap.Wrapf("{{err}}", fmt.Errorf("missing token")) + return nil, &ErrUserInput{ + Message: "missing token", + } } te, err := c.tokenStore.Lookup(token) @@ -21,7 +31,9 @@ func (c *Core) Capabilities(token, path string) ([]string, error) { return nil, err } if te == nil { - return nil, errwrap.Wrapf("{{err}}", fmt.Errorf("invalid token")) + return nil, &ErrUserInput{ + Message: "invalid token", + } } if te.Policies == nil {