Merge pull request #762 from hashicorp/issue-732

Create a "default" policy with sensible rules.
This commit is contained in:
Jeff Mitchell 2015-11-09 17:44:09 -05:00
commit 201adad4ae
11 changed files with 142 additions and 45 deletions

View File

@ -8,6 +8,9 @@ DEPRECATIONS/BREAKING CHANGES:
* Default etcd port number: the default connection string for the `etcd`
physical store uses port 2379 instead of port 4001, which is the port used
by the supported version 2.x of etcd. [GH-753]
* As noted below in the FEATURES section, if your Vault installation contains
a policy called `default`, new tokens created will inherit this policy
automatically.
FEATURES:
@ -15,6 +18,16 @@ FEATURES:
supports pushing CRLs into the mount and using the contained serial numbers
for revocation checking. See the documentation for the `cert` backend for
more info. [GH-330]
* **Default Policy**: Vault now ensures that a policy named `default` is added
to every token. This policy cannot be deleted, but it can be modified
(including to an empty policy). There are three endpoints allowed in the
default `default` policy, related to token self-management: `lookup-self`,
which allows a token to retrieve its own information, and `revoke-self` and
`renew-self`, which are self-explanatory. If your existing Vault
installation contains a policy called `default`, it will not be overridden,
but it will be added to each new token created. You can override this
behavior when using manual token creation (i.e. not via an authentication
backend) by setting the "no_default_policy" flag to true. [GH-732]
IMPROVEMENTS:

View File

@ -123,6 +123,7 @@ type TokenCreateRequest struct {
Lease string `json:"lease,omitempty"`
TTL string `json:"ttl,omitempty"`
NoParent bool `json:"no_parent,omitempty"`
NoDefaultPolicy bool `json:"no_default_policy,omitempty"`
DisplayName string `json:"display_name"`
NumUses int `json:"num_uses"`
}

View File

@ -17,7 +17,7 @@ type TokenCreateCommand struct {
func (c *TokenCreateCommand) Run(args []string) int {
var format string
var id, displayName, lease, ttl string
var orphan bool
var orphan, noDefaultPolicy bool
var metadata map[string]string
var numUses int
var policies []string
@ -28,6 +28,7 @@ func (c *TokenCreateCommand) Run(args []string) int {
flags.StringVar(&lease, "lease", "", "")
flags.StringVar(&ttl, "ttl", "", "")
flags.BoolVar(&orphan, "orphan", false, "")
flags.BoolVar(&noDefaultPolicy, "no-default-policy", false, "")
flags.IntVar(&numUses, "use-limit", 0, "")
flags.Var((*kvFlag.Flag)(&metadata), "metadata", "")
flags.Var((*sliceflag.StringFlag)(&policies), "policy", "")
@ -60,6 +61,7 @@ func (c *TokenCreateCommand) Run(args []string) int {
Metadata: metadata,
TTL: ttl,
NoParent: orphan,
NoDefaultPolicy: noDefaultPolicy,
DisplayName: displayName,
NumUses: numUses,
})
@ -122,6 +124,9 @@ Token Options:
root tokens can create orphan tokens. This prevents
the new token from being revoked with your token.
-no-default-policy If specified, the token will not have the "default"
policy included in its policy set.
-policy="name" Policy to associate with this token. This can be
specified multiple times.

View File

@ -17,7 +17,7 @@ func TestSysPolicies(t *testing.T) {
var actual map[string]interface{}
expected := map[string]interface{}{
"policies": []interface{}{"root"},
"policies": []interface{}{"default", "root"},
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
@ -61,7 +61,7 @@ func TestSysWritePolicy(t *testing.T) {
var actual map[string]interface{}
expected := map[string]interface{}{
"policies": []interface{}{"foo", "root"},
"policies": []interface{}{"default", "foo", "root"},
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
@ -88,7 +88,7 @@ func TestSysDeletePolicy(t *testing.T) {
var actual map[string]interface{}
expected := map[string]interface{}{
"policies": []interface{}{"root"},
"policies": []interface{}{"default", "root"},
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)

View File

@ -648,6 +648,10 @@ func (c *Core) handleLoginRequest(req *logical.Request) (*logical.Response, *log
TTL: auth.TTL,
}
if !strListSubset(te.Policies, []string{"root"}) {
te.Policies = append(te.Policies, "default")
}
if err := c.tokenStore.create(&te); err != nil {
c.logger.Printf("[ERR] core: failed to create token: %v", err)
return nil, auth, ErrInternalError

View File

@ -5,8 +5,8 @@ import (
"testing"
"time"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/uuid"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/physical"
)
@ -821,7 +821,7 @@ func TestCore_HandleLogin_Token(t *testing.T) {
expect := &TokenEntry{
ID: clientToken,
Parent: "",
Policies: []string{"foo", "bar"},
Policies: []string{"foo", "bar", "default"},
Path: "auth/foo/login",
Meta: map[string]string{
"user": "armon",
@ -1020,7 +1020,7 @@ func TestCore_HandleRequest_CreateToken_Lease(t *testing.T) {
expect := &TokenEntry{
ID: clientToken,
Parent: root,
Policies: []string{"foo"},
Policies: []string{"foo", "default"},
Path: "auth/token/create",
DisplayName: "token",
CreationTime: te.CreationTime,
@ -1036,6 +1036,45 @@ func TestCore_HandleRequest_CreateToken_Lease(t *testing.T) {
}
}
// Check that we handle excluding the default policy
func TestCore_HandleRequest_CreateToken_NoDefaultPolicy(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
// Create a new credential
req := logical.TestRequest(t, logical.WriteOperation, "auth/token/create")
req.ClientToken = root
req.Data["policies"] = []string{"foo"}
req.Data["no_default_policy"] = true
resp, err := c.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
// Ensure we got a new client token back
clientToken := resp.Auth.ClientToken
if clientToken == "" {
t.Fatalf("bad: %#v", resp)
}
// Check the policy and metadata
te, err := c.tokenStore.Lookup(clientToken)
if err != nil {
t.Fatalf("err: %v", err)
}
expect := &TokenEntry{
ID: clientToken,
Parent: root,
Policies: []string{"foo"},
Path: "auth/token/create",
DisplayName: "token",
CreationTime: te.CreationTime,
TTL: time.Hour * 24 * 30,
}
if !reflect.DeepEqual(te, expect) {
t.Fatalf("Bad: %#v expect: %#v", te, expect)
}
}
func TestCore_LimitedUseToken(t *testing.T) {
c, _, root := TestCoreUnsealed(t)

View File

@ -430,7 +430,7 @@ func TestSystemBackend_policyList(t *testing.T) {
}
exp := map[string]interface{}{
"keys": []string{"root"},
"keys": []string{"default", "root"},
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
@ -482,7 +482,7 @@ func TestSystemBackend_policyCRUD(t *testing.T) {
}
exp = map[string]interface{}{
"keys": []string{"foo", "root"},
"keys": []string{"default", "foo", "root"},
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
@ -516,7 +516,7 @@ func TestSystemBackend_policyCRUD(t *testing.T) {
}
exp = map[string]interface{}{
"keys": []string{"root"},
"keys": []string{"default", "root"},
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)

View File

@ -5,6 +5,7 @@ import (
"time"
"github.com/armon/go-metrics"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/golang-lru"
"github.com/hashicorp/vault/logical"
)
@ -51,16 +52,17 @@ func (c *Core) setupPolicyStore() error {
// Create the policy store
c.policyStore = NewPolicyStore(view)
/*
// Ensure that the default policy exists, and if not, create it
policy, err := c.policyStore.GetPolicy("default")
if err != nil {
return errwrap.Wrapf("error fetching default policy from store: {{err}}", err)
}
if policy == nil {
c.policyStore.createDefaultPolicy()
err := c.policyStore.createDefaultPolicy()
if err != nil {
return err
}
}
*/
return nil
}
@ -169,6 +171,9 @@ func (ps *PolicyStore) DeletePolicy(name string) error {
if name == "root" {
return fmt.Errorf("cannot delete root policy")
}
if name == "default" {
return fmt.Errorf("cannot delete default policy")
}
if err := ps.view.Delete(name); err != nil {
return fmt.Errorf("failed to delete policy: %v", err)
}
@ -199,8 +204,28 @@ func (ps *PolicyStore) ACL(names ...string) (*ACL, error) {
return acl, nil
}
/*
func (ps *PolicyStore) createDefaultPolicy() error {
return nil
policy, err := Parse(`
path "auth/token/lookup-self" {
policy = "read"
}
path "auth/token/renew-self" {
policy = "write"
}
path "auth/token/revoke-self" {
policy = "write"
}
`)
if err != nil {
return errwrap.Wrapf("error parsing default policy: {{err}}", err)
}
if policy == nil {
return fmt.Errorf("parsing default policy resulted in nil policy")
}
policy.Name = "default"
return ps.SetPolicy(policy)
}
*/

View File

@ -551,6 +551,7 @@ func (ts *TokenStore) handleCreateCommon(
Policies []string
Metadata map[string]string `mapstructure:"meta"`
NoParent bool `mapstructure:"no_parent"`
NoDefaultPolicy bool `mapstructure:"no_default_policy"`
Lease string
TTL string
DisplayName string `mapstructure:"display_name"`
@ -602,6 +603,9 @@ func (ts *TokenStore) handleCreateCommon(
return logical.ErrorResponse("child policies must be subset of parent"), logical.ErrInvalidRequest
}
te.Policies = data.Policies
if !strListSubset(te.Policies, []string{"root"}) && !data.NoDefaultPolicy {
te.Policies = append(te.Policies, "default")
}
// Only allow an orphan token if the client has sudo policy
if data.NoParent {

View File

@ -888,7 +888,7 @@ func TestTokenStore_HandleRequest_Lookup(t *testing.T) {
exp = map[string]interface{}{
"id": "client",
"policies": []string{"foo"},
"policies": []string{"foo", "default"},
"path": "auth/token/create",
"meta": map[string]string(nil),
"display_name": "token",

View File

@ -87,6 +87,12 @@ of the header should be "X-Vault-Token" and the value should be the token.
If true and set by a root caller, the token will not have the
parent token of the caller. This creates a token with no parent.
</li>
<li>
<span class="param">no_default_profile</span>
<span class="param-flags">optional</span>
If true the `default` profile will not be a part of this token's
policy set.
</li>
<li>
<span class="param">lease</span>
<span class="param-flags">optional</span>