Added sys/capabililties endpoint

This commit is contained in:
vishalnayak 2016-03-02 13:42:32 -05:00
parent 0998e1cdf9
commit 5749a6718c
6 changed files with 392 additions and 0 deletions

48
api/sys_capabilities.go Normal file
View 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 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"`
}

View File

@ -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()

85
command/capabilities.go Normal file
View File

@ -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)
}

99
http/sys_capabilities.go Normal file
View File

@ -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"`
}

View File

@ -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<path>.+)",
@ -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/<name>" endpoint to set a policy
func (b *SystemBackend) handlePolicySet(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {

View File

@ -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.`
)