mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
Add bound cidrs to tokens in AppRole (#4680)
This commit is contained in:
committed by
Jeff Mitchell
parent
bd99c43e9c
commit
b3a711d717
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/vault/helper/cidrutil"
|
||||
"github.com/hashicorp/vault/helper/parseutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
@@ -160,7 +161,7 @@ func (b *backend) pathLoginUpdate(ctx context.Context, req *logical.Request, dat
|
||||
|
||||
// Ensure that the CIDRs on the secret ID are still a subset of that of
|
||||
// role's
|
||||
err = verifyCIDRRoleSecretIDSubset(entry.CIDRList, role.BoundCIDRList)
|
||||
err = verifyCIDRRoleSecretIDSubset(entry.CIDRList, role.SecretIDBoundCIDRs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -228,7 +229,7 @@ func (b *backend) pathLoginUpdate(ctx context.Context, req *logical.Request, dat
|
||||
|
||||
// Ensure that the CIDRs on the secret ID are still a subset of that of
|
||||
// role's
|
||||
err = verifyCIDRRoleSecretIDSubset(entry.CIDRList, role.BoundCIDRList)
|
||||
err = verifyCIDRRoleSecretIDSubset(entry.CIDRList, role.SecretIDBoundCIDRs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -250,17 +251,22 @@ func (b *backend) pathLoginUpdate(ctx context.Context, req *logical.Request, dat
|
||||
metadata = entry.Metadata
|
||||
}
|
||||
|
||||
if len(role.BoundCIDRList) != 0 {
|
||||
if len(role.SecretIDBoundCIDRs) != 0 {
|
||||
if req.Connection == nil || req.Connection.RemoteAddr == "" {
|
||||
return nil, fmt.Errorf("failed to get connection information")
|
||||
}
|
||||
|
||||
belongs, err := cidrutil.IPBelongsToCIDRBlocksSlice(req.Connection.RemoteAddr, role.BoundCIDRList)
|
||||
belongs, err := cidrutil.IPBelongsToCIDRBlocksSlice(req.Connection.RemoteAddr, role.SecretIDBoundCIDRs)
|
||||
if err != nil || !belongs {
|
||||
return logical.ErrorResponse(errwrap.Wrapf(fmt.Sprintf("source address %q unauthorized by CIDR restrictions on the role: {{err}}", req.Connection.RemoteAddr), err).Error()), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the CIDRs we should be binding the token to.
|
||||
tokenBoundCIDRs, err := parseutil.ParseAddrs(role.TokenBoundCIDRs)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
// For some reason, if metadata was set to nil while processing secret ID
|
||||
// binding, ensure that it is initialized again to avoid a panic.
|
||||
if metadata == nil {
|
||||
@@ -286,6 +292,7 @@ func (b *backend) pathLoginUpdate(ctx context.Context, req *logical.Request, dat
|
||||
Alias: &logical.Alias{
|
||||
Name: role.RoleID,
|
||||
},
|
||||
BoundCIDRs: tokenBoundCIDRs,
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
|
||||
@@ -2,6 +2,7 @@ package approle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -55,12 +56,20 @@ type roleStorageEntry struct {
|
||||
// A constraint, if set, requires 'secret_id' credential to be presented during login
|
||||
BindSecretID bool `json:"bind_secret_id" mapstructure:"bind_secret_id"`
|
||||
|
||||
// A constraint, if set, specifies the CIDR blocks from which logins should be allowed
|
||||
// Deprecated: A constraint, if set, specifies the CIDR blocks from which logins should be allowed,
|
||||
// please use SecretIDBoundCIDRs instead.
|
||||
BoundCIDRListOld string `json:"bound_cidr_list,omitempty"`
|
||||
|
||||
// A constraint, if set, specifies the CIDR blocks from which logins should be allowed
|
||||
// Deprecated: A constraint, if set, specifies the CIDR blocks from which logins should be allowed,
|
||||
// please use SecretIDBoundCIDRs instead.
|
||||
BoundCIDRList []string `json:"bound_cidr_list_list" mapstructure:"bound_cidr_list"`
|
||||
|
||||
// A constraint, if set, specifies the CIDR blocks from which logins should be allowed
|
||||
SecretIDBoundCIDRs []string `json:"secret_id_bound_cidrs" mapstructure:"secret_id_bound_cidrs"`
|
||||
|
||||
// A constraint, if set, specifies the CIDR blocks from which token use should be allowed
|
||||
TokenBoundCIDRs []string `json:"token_bound_cidrs" mapstructure:"token_bound_cidrs"`
|
||||
|
||||
// Period, if set, indicates that the token generated using this role
|
||||
// should never expire. The token should be renewed within the duration
|
||||
// specified by this value. The renewal duration will be fixed if the
|
||||
@@ -125,10 +134,21 @@ func rolePaths(b *backend) []*framework.Path {
|
||||
Default: true,
|
||||
Description: "Impose secret_id to be presented when logging in using this role. Defaults to 'true'.",
|
||||
},
|
||||
// Deprecated
|
||||
"bound_cidr_list": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `Deprecated: Please use "secret_id_bound_cidrs" instead. Comma separated string or list
|
||||
of CIDR blocks. If set, specifies the blocks of IP addresses which can perform the login operation.`,
|
||||
},
|
||||
"secret_id_bound_cidrs": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `Comma separated string or list of CIDR blocks. If set, specifies the blocks of
|
||||
IP addresses which can perform the login operation.`,
|
||||
},
|
||||
"token_bound_cidrs": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `Comma separated string or list of CIDR blocks. If set, specifies the blocks of
|
||||
IP addresses which can use the returned token.`,
|
||||
},
|
||||
"policies": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
@@ -231,18 +251,60 @@ can only be set during role creation and once set, it can't be reset later.`,
|
||||
},
|
||||
"bound_cidr_list": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `Comma separated string or list of CIDR blocks. If set, specifies the blocks of
|
||||
IP addresses which can perform the login operation.`,
|
||||
Description: `Deprecated: Please use "secret_id_bound_cidrs" instead. Comma separated string or list
|
||||
of CIDR blocks. If set, specifies the blocks of IP addresses which can perform the login operation.`,
|
||||
},
|
||||
},
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathRoleBoundCIDRListUpdate,
|
||||
logical.UpdateOperation: b.pathRoleBoundCIDRUpdate,
|
||||
logical.ReadOperation: b.pathRoleBoundCIDRListRead,
|
||||
logical.DeleteOperation: b.pathRoleBoundCIDRListDelete,
|
||||
},
|
||||
HelpSynopsis: strings.TrimSpace(roleHelp["role-bound-cidr-list"][0]),
|
||||
HelpDescription: strings.TrimSpace(roleHelp["role-bound-cidr-list"][1]),
|
||||
},
|
||||
&framework.Path{
|
||||
Pattern: "role/" + framework.GenericNameRegex("role_name") + "/secret-id-bound-cidrs$",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"role_name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the role.",
|
||||
},
|
||||
"secret_id_bound_cidrs": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `Comma separated string or list of CIDR blocks. If set, specifies the blocks of
|
||||
IP addresses which can perform the login operation.`,
|
||||
},
|
||||
},
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathRoleBoundCIDRUpdate,
|
||||
logical.ReadOperation: b.pathRoleSecretIDBoundCIDRRead,
|
||||
logical.DeleteOperation: b.pathRoleSecretIDBoundCIDRDelete,
|
||||
},
|
||||
HelpSynopsis: strings.TrimSpace(roleHelp["secret-id-bound-cidrs"][0]),
|
||||
HelpDescription: strings.TrimSpace(roleHelp["secret-id-bound-cidrs"][1]),
|
||||
},
|
||||
&framework.Path{
|
||||
Pattern: "role/" + framework.GenericNameRegex("role_name") + "/token-bound-cidrs$",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"role_name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the role.",
|
||||
},
|
||||
"token_bound_cidrs": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `Comma separated string or list of CIDR blocks. If set, specifies the blocks of
|
||||
IP addresses which can use the returned token.`,
|
||||
},
|
||||
},
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathRoleBoundCIDRUpdate,
|
||||
logical.ReadOperation: b.pathRoleTokenBoundCIDRRead,
|
||||
logical.DeleteOperation: b.pathRoleTokenBoundCIDRDelete,
|
||||
},
|
||||
HelpSynopsis: strings.TrimSpace(roleHelp["token-bound-cidrs"][0]),
|
||||
HelpDescription: strings.TrimSpace(roleHelp["token-bound-cidrs"][1]),
|
||||
},
|
||||
&framework.Path{
|
||||
Pattern: "role/" + framework.GenericNameRegex("role_name") + "/bind-secret-id$",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
@@ -662,6 +724,8 @@ func validateRoleConstraints(role *roleStorageEntry) error {
|
||||
switch {
|
||||
case role.BindSecretID:
|
||||
case len(role.BoundCIDRList) != 0:
|
||||
case len(role.SecretIDBoundCIDRs) != 0:
|
||||
case len(role.TokenBoundCIDRs) != 0:
|
||||
default:
|
||||
return fmt.Errorf("at least one constraint should be enabled on the role")
|
||||
}
|
||||
@@ -749,11 +813,17 @@ func (b *backend) roleEntry(ctx context.Context, s logical.Storage, roleName str
|
||||
needsUpgrade := false
|
||||
|
||||
if role.BoundCIDRListOld != "" {
|
||||
role.BoundCIDRList = strings.Split(role.BoundCIDRListOld, ",")
|
||||
role.SecretIDBoundCIDRs = strutil.ParseDedupAndSortStrings(role.BoundCIDRListOld, ",")
|
||||
role.BoundCIDRListOld = ""
|
||||
needsUpgrade = true
|
||||
}
|
||||
|
||||
if len(role.BoundCIDRList) != 0 {
|
||||
role.SecretIDBoundCIDRs = role.BoundCIDRList
|
||||
role.BoundCIDRList = nil
|
||||
needsUpgrade = true
|
||||
}
|
||||
|
||||
if role.SecretIDPrefix == "" {
|
||||
role.SecretIDPrefix = secretIDPrefix
|
||||
needsUpgrade = true
|
||||
@@ -847,14 +917,26 @@ func (b *backend) pathRoleCreateUpdate(ctx context.Context, req *logical.Request
|
||||
role.BindSecretID = data.Get("bind_secret_id").(bool)
|
||||
}
|
||||
|
||||
if boundCIDRListRaw, ok := data.GetOk("bound_cidr_list"); ok {
|
||||
role.BoundCIDRList = boundCIDRListRaw.([]string)
|
||||
} else if req.Operation == logical.CreateOperation {
|
||||
role.BoundCIDRList = data.Get("bound_cidr_list").([]string)
|
||||
if boundCIDRListRaw, ok := data.GetFirst("secret_id_bound_cidrs", "bound_cidr_list"); ok {
|
||||
role.SecretIDBoundCIDRs = boundCIDRListRaw.([]string)
|
||||
}
|
||||
|
||||
if len(role.BoundCIDRList) != 0 {
|
||||
valid, err := cidrutil.ValidateCIDRListSlice(role.BoundCIDRList)
|
||||
if len(role.SecretIDBoundCIDRs) != 0 {
|
||||
valid, err := cidrutil.ValidateCIDRListSlice(role.SecretIDBoundCIDRs)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("failed to validate CIDR blocks: {{err}}", err)
|
||||
}
|
||||
if !valid {
|
||||
return logical.ErrorResponse("invalid CIDR blocks"), nil
|
||||
}
|
||||
}
|
||||
|
||||
if boundCIDRListRaw, ok := data.GetOk("token_bound_cidrs"); ok {
|
||||
role.TokenBoundCIDRs = boundCIDRListRaw.([]string)
|
||||
}
|
||||
|
||||
if len(role.TokenBoundCIDRs) != 0 {
|
||||
valid, err := cidrutil.ValidateCIDRListSlice(role.TokenBoundCIDRs)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("failed to validate CIDR blocks: {{err}}", err)
|
||||
}
|
||||
@@ -955,16 +1037,20 @@ func (b *backend) pathRoleRead(ctx context.Context, req *logical.Request, data *
|
||||
}
|
||||
|
||||
respData := map[string]interface{}{
|
||||
"bind_secret_id": role.BindSecretID,
|
||||
"bound_cidr_list": role.BoundCIDRList,
|
||||
"period": role.Period / time.Second,
|
||||
"policies": role.Policies,
|
||||
"secret_id_num_uses": role.SecretIDNumUses,
|
||||
"secret_id_ttl": role.SecretIDTTL / time.Second,
|
||||
"token_max_ttl": role.TokenMaxTTL / time.Second,
|
||||
"token_num_uses": role.TokenNumUses,
|
||||
"token_ttl": role.TokenTTL / time.Second,
|
||||
"local_secret_ids": false,
|
||||
"bind_secret_id": role.BindSecretID,
|
||||
// TODO - remove this deprecated field in future versions,
|
||||
// and its associated warning below.
|
||||
"bound_cidr_list": role.SecretIDBoundCIDRs,
|
||||
"secret_id_bound_cidrs": role.SecretIDBoundCIDRs,
|
||||
"token_bound_cidrs": role.TokenBoundCIDRs,
|
||||
"period": role.Period / time.Second,
|
||||
"policies": role.Policies,
|
||||
"secret_id_num_uses": role.SecretIDNumUses,
|
||||
"secret_id_ttl": role.SecretIDTTL / time.Second,
|
||||
"token_max_ttl": role.TokenMaxTTL / time.Second,
|
||||
"token_num_uses": role.TokenNumUses,
|
||||
"token_ttl": role.TokenTTL / time.Second,
|
||||
"local_secret_ids": false,
|
||||
}
|
||||
|
||||
if role.SecretIDPrefix == secretIDLocalPrefix {
|
||||
@@ -978,6 +1064,7 @@ func (b *backend) pathRoleRead(ctx context.Context, req *logical.Request, data *
|
||||
if err := validateRoleConstraints(role); err != nil {
|
||||
resp.AddWarning("Role does not have any constraints set on it. Updates to this role will require a constraint to be set")
|
||||
}
|
||||
resp.AddWarning(`The "bound_cidr_list" parameter is deprecated and will be removed in favor of "secret_id_bound_cidrs".`)
|
||||
|
||||
// For sanity, verify that the index still exists. If the index is missing,
|
||||
// add one and return a warning so it can be reported.
|
||||
@@ -1312,7 +1399,7 @@ func (b *backend) pathRoleSecretIDAccessorDestroyUpdateDelete(ctx context.Contex
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleBoundCIDRListUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
func (b *backend) pathRoleBoundCIDRUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
roleName := data.Get("role_name").(string)
|
||||
if roleName == "" {
|
||||
return logical.ErrorResponse("missing role_name"), nil
|
||||
@@ -1331,12 +1418,18 @@ func (b *backend) pathRoleBoundCIDRListUpdate(ctx context.Context, req *logical.
|
||||
return nil, logical.ErrUnsupportedPath
|
||||
}
|
||||
|
||||
role.BoundCIDRList = data.Get("bound_cidr_list").([]string)
|
||||
if len(role.BoundCIDRList) == 0 {
|
||||
var cidrs []string
|
||||
if cidrsIfc, ok := data.GetFirst("secret_id_bound_cidrs", "bound_cidr_list"); ok {
|
||||
cidrs = cidrsIfc.([]string)
|
||||
role.SecretIDBoundCIDRs = cidrs
|
||||
} else if cidrsIfc, ok := data.GetOk("token_bound_cidrs"); ok {
|
||||
cidrs = cidrsIfc.([]string)
|
||||
role.TokenBoundCIDRs = cidrs
|
||||
}
|
||||
if len(cidrs) == 0 {
|
||||
return logical.ErrorResponse("missing bound_cidr_list"), nil
|
||||
}
|
||||
|
||||
valid, err := cidrutil.ValidateCIDRListSlice(role.BoundCIDRList)
|
||||
valid, err := cidrutil.ValidateCIDRListSlice(cidrs)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("failed to validate CIDR blocks: {{err}}", err)
|
||||
}
|
||||
@@ -1347,7 +1440,19 @@ func (b *backend) pathRoleBoundCIDRListUpdate(ctx context.Context, req *logical.
|
||||
return nil, b.setRoleEntry(ctx, req.Storage, role.name, role, "")
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleSecretIDBoundCIDRRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
return b.pathRoleFieldRead(ctx, req, data, "secret_id_bound_cidrs")
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleTokenBoundCIDRRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
return b.pathRoleFieldRead(ctx, req, data, "token_bound_cidrs")
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleBoundCIDRListRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
return b.pathRoleFieldRead(ctx, req, data, "bound_cidr_list")
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleFieldRead(ctx context.Context, req *logical.Request, data *framework.FieldData, fieldName string) (*logical.Response, error) {
|
||||
roleName := data.Get("role_name").(string)
|
||||
if roleName == "" {
|
||||
return logical.ErrorResponse("missing role_name"), nil
|
||||
@@ -1363,16 +1468,36 @@ func (b *backend) pathRoleBoundCIDRListRead(ctx context.Context, req *logical.Re
|
||||
}
|
||||
if role == nil {
|
||||
return nil, nil
|
||||
} else {
|
||||
switch fieldName {
|
||||
case "secret_id_bound_cidrs":
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"secret_id_bound_cidrs": role.SecretIDBoundCIDRs,
|
||||
},
|
||||
}, nil
|
||||
case "token_bound_cidrs":
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"token_bound_cidrs": role.TokenBoundCIDRs,
|
||||
},
|
||||
}, nil
|
||||
case "bound_cidr_list":
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"bound_cidr_list": role.BoundCIDRList,
|
||||
},
|
||||
}
|
||||
resp.AddWarning(`The "bound_cidr_list" parameter is deprecated and will be removed. Please use "secret_id_bound_cidrs" instead.`)
|
||||
return resp, nil
|
||||
default:
|
||||
// shouldn't occur IRL
|
||||
return nil, errors.New("unrecognized field provided: " + fieldName)
|
||||
}
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"bound_cidr_list": role.BoundCIDRList,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleBoundCIDRListDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
func (b *backend) pathRoleBoundCIDRDelete(ctx context.Context, req *logical.Request, data *framework.FieldData, fieldName string) (*logical.Response, error) {
|
||||
roleName := data.Get("role_name").(string)
|
||||
if roleName == "" {
|
||||
return logical.ErrorResponse("missing role_name"), nil
|
||||
@@ -1391,9 +1516,27 @@ func (b *backend) pathRoleBoundCIDRListDelete(ctx context.Context, req *logical.
|
||||
}
|
||||
|
||||
// Deleting a field implies setting the value to it's default value.
|
||||
role.BoundCIDRList = data.GetDefaultOrZero("bound_cidr_list").([]string)
|
||||
switch fieldName {
|
||||
case "bound_cidr_list":
|
||||
role.BoundCIDRList = data.GetDefaultOrZero("bound_cidr_list").([]string)
|
||||
case "secret_id_bound_cidrs":
|
||||
role.SecretIDBoundCIDRs = data.GetDefaultOrZero("secret_id_bound_cidrs").([]string)
|
||||
case "token_bound_cidrs":
|
||||
role.TokenBoundCIDRs = data.GetDefaultOrZero("token_bound_cidrs").([]string)
|
||||
}
|
||||
return nil, b.setRoleEntry(ctx, req.Storage, roleName, role, "")
|
||||
}
|
||||
|
||||
return nil, b.setRoleEntry(ctx, req.Storage, role.name, role, "")
|
||||
func (b *backend) pathRoleBoundCIDRListDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
return b.pathRoleBoundCIDRDelete(ctx, req, data, "bound_cidr_list")
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleSecretIDBoundCIDRDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
return b.pathRoleBoundCIDRDelete(ctx, req, data, "secret_id_bound_cidrs")
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleTokenBoundCIDRDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
return b.pathRoleBoundCIDRDelete(ctx, req, data, "token_bound_cidrs")
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleBindSecretIDUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
@@ -2137,7 +2280,7 @@ func (b *backend) handleRoleSecretIDCommon(ctx context.Context, req *logical.Req
|
||||
}
|
||||
|
||||
// Ensure that the CIDRs on the secret ID are a subset of that of role's
|
||||
if err := verifyCIDRRoleSecretIDSubset(secretIDCIDRs, role.BoundCIDRList); err != nil {
|
||||
if err := verifyCIDRRoleSecretIDSubset(secretIDCIDRs, role.SecretIDBoundCIDRs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2266,11 +2409,25 @@ configured using the parameters of this endpoint.`,
|
||||
The value of 'secret_id' can be retrieved using 'role/<role_name>/secret-id' endpoint.`,
|
||||
},
|
||||
"role-bound-cidr-list": {
|
||||
`Deprecated: Comma separated list of CIDR blocks, if set, specifies blocks of IP
|
||||
addresses which can perform the login operation`,
|
||||
`During login, the IP address of the client will be checked to see if it
|
||||
belongs to the CIDR blocks specified. If CIDR blocks were set and if the
|
||||
IP is not encompassed by it, login fails`,
|
||||
},
|
||||
"secret-id-bound-cidrs": {
|
||||
`Comma separated list of CIDR blocks, if set, specifies blocks of IP
|
||||
addresses which can perform the login operation`,
|
||||
`During login, the IP address of the client will be checked to see if it
|
||||
belongs to the CIDR blocks specified. If CIDR blocks were set and if the
|
||||
IP is not encompassed by it, login fails`,
|
||||
},
|
||||
"token-bound-cidrs": {
|
||||
`Comma separated string or list of CIDR blocks. If set, specifies the blocks of
|
||||
IP addresses which can use the returned token.`,
|
||||
`During use of the returned token, the IP address of the client will be checked to see if it
|
||||
belongs to the CIDR blocks specified. If CIDR blocks were set and if the
|
||||
IP is not encompassed by it, token use fails`,
|
||||
},
|
||||
"role-policies": {
|
||||
"Policies of the role.",
|
||||
|
||||
@@ -243,10 +243,10 @@ func TestAppRole_UpgradeBoundCIDRList(t *testing.T) {
|
||||
}
|
||||
|
||||
expected := []string{"127.0.0.1/18", "192.178.1.2/24"}
|
||||
actual := resp.Data["bound_cidr_list"].([]string)
|
||||
actual := resp.Data["secret_id_bound_cidrs"].([]string)
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("bad: bound_cidr_list; expected: %#v\nactual: %#v\n", expected, actual)
|
||||
t.Fatalf("bad: secret_id_bound_cidrs; expected: %#v\nactual: %#v\n", expected, actual)
|
||||
}
|
||||
|
||||
// Modify the storage entry of the role to hold the old style string typed bound_cidr_list
|
||||
@@ -519,7 +519,7 @@ func TestAppRole_RoleReadSetIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check if the warning is being returned
|
||||
if !strings.Contains(resp.Warnings[0], "Role identifier was missing an index back to role name.") {
|
||||
if !strings.Contains(resp.Warnings[1], "Role identifier was missing an index back to role name.") {
|
||||
t.Fatalf("bad: expected a warning in the response")
|
||||
}
|
||||
|
||||
@@ -1122,13 +1122,13 @@ func TestAppRole_RoleCRUD(t *testing.T) {
|
||||
b, storage := createBackendWithStorage(t)
|
||||
|
||||
roleData := map[string]interface{}{
|
||||
"policies": "p,q,r,s",
|
||||
"secret_id_num_uses": 10,
|
||||
"secret_id_ttl": 300,
|
||||
"token_ttl": 400,
|
||||
"token_max_ttl": 500,
|
||||
"token_num_uses": 600,
|
||||
"bound_cidr_list": "127.0.0.1/32,127.0.0.1/16",
|
||||
"policies": "p,q,r,s",
|
||||
"secret_id_num_uses": 10,
|
||||
"secret_id_ttl": 300,
|
||||
"token_ttl": 400,
|
||||
"token_max_ttl": 500,
|
||||
"token_num_uses": 600,
|
||||
"secret_id_bound_cidrs": "127.0.0.1/32,127.0.0.1/16",
|
||||
}
|
||||
roleReq := &logical.Request{
|
||||
Operation: logical.CreateOperation,
|
||||
@@ -1149,14 +1149,16 @@ func TestAppRole_RoleCRUD(t *testing.T) {
|
||||
}
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"bind_secret_id": true,
|
||||
"policies": []string{"p", "q", "r", "s"},
|
||||
"secret_id_num_uses": 10,
|
||||
"secret_id_ttl": 300,
|
||||
"token_ttl": 400,
|
||||
"token_max_ttl": 500,
|
||||
"token_num_uses": 600,
|
||||
"bound_cidr_list": []string{"127.0.0.1/32", "127.0.0.1/16"},
|
||||
"bind_secret_id": true,
|
||||
"policies": []string{"p", "q", "r", "s"},
|
||||
"secret_id_num_uses": 10,
|
||||
"secret_id_ttl": 300,
|
||||
"token_ttl": 400,
|
||||
"token_max_ttl": 500,
|
||||
"token_num_uses": 600,
|
||||
"secret_id_bound_cidrs": []string{"127.0.0.1/32", "127.0.0.1/16"},
|
||||
"bound_cidr_list": []string{"127.0.0.1/32", "127.0.0.1/16"}, // returned for backwards compatibility
|
||||
"token_bound_cidrs": []string{},
|
||||
}
|
||||
|
||||
var expectedStruct roleStorageEntry
|
||||
@@ -1591,6 +1593,221 @@ func TestAppRole_RoleCRUD(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppRole_RoleWithTokenBoundCIDRsCRUD(t *testing.T) {
|
||||
var resp *logical.Response
|
||||
var err error
|
||||
b, storage := createBackendWithStorage(t)
|
||||
|
||||
roleData := map[string]interface{}{
|
||||
"policies": "p,q,r,s",
|
||||
"secret_id_num_uses": 10,
|
||||
"secret_id_ttl": 300,
|
||||
"token_ttl": 400,
|
||||
"token_max_ttl": 500,
|
||||
"token_num_uses": 600,
|
||||
"secret_id_bound_cidrs": "127.0.0.1/32,127.0.0.1/16",
|
||||
"token_bound_cidrs": "127.0.0.1/32,127.0.0.1/16",
|
||||
}
|
||||
roleReq := &logical.Request{
|
||||
Operation: logical.CreateOperation,
|
||||
Path: "role/role1",
|
||||
Storage: storage,
|
||||
Data: roleData,
|
||||
}
|
||||
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
roleReq.Operation = logical.ReadOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"bind_secret_id": true,
|
||||
"policies": []string{"p", "q", "r", "s"},
|
||||
"secret_id_num_uses": 10,
|
||||
"secret_id_ttl": 300,
|
||||
"token_ttl": 400,
|
||||
"token_max_ttl": 500,
|
||||
"token_num_uses": 600,
|
||||
"token_bound_cidrs": []string{"127.0.0.1/32", "127.0.0.1/16"},
|
||||
"secret_id_bound_cidrs": []string{"127.0.0.1/32", "127.0.0.1/16"},
|
||||
"bound_cidr_list": []string{"127.0.0.1/32", "127.0.0.1/16"}, // provided for backwards compatibility
|
||||
}
|
||||
|
||||
var expectedStruct roleStorageEntry
|
||||
err = mapstructure.Decode(expected, &expectedStruct)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var actualStruct roleStorageEntry
|
||||
err = mapstructure.Decode(resp.Data, &actualStruct)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedStruct.RoleID = actualStruct.RoleID
|
||||
if !reflect.DeepEqual(expectedStruct, actualStruct) {
|
||||
t.Fatalf("bad:\nexpected:%#v\nactual:%#v\n", expectedStruct, actualStruct)
|
||||
}
|
||||
|
||||
roleData = map[string]interface{}{
|
||||
"role_id": "test_role_id",
|
||||
"policies": "a,b,c,d",
|
||||
"secret_id_num_uses": 100,
|
||||
"secret_id_ttl": 3000,
|
||||
"token_ttl": 4000,
|
||||
"token_max_ttl": 5000,
|
||||
}
|
||||
roleReq.Data = roleData
|
||||
roleReq.Operation = logical.UpdateOperation
|
||||
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
roleReq.Operation = logical.ReadOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
expected = map[string]interface{}{
|
||||
"policies": []string{"a", "b", "c", "d"},
|
||||
"secret_id_num_uses": 100,
|
||||
"secret_id_ttl": 3000,
|
||||
"token_ttl": 4000,
|
||||
"token_max_ttl": 5000,
|
||||
}
|
||||
err = mapstructure.Decode(expected, &expectedStruct)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = mapstructure.Decode(resp.Data, &actualStruct)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expectedStruct, actualStruct) {
|
||||
t.Fatalf("bad:\nexpected:%#v\nactual:%#v\n", expectedStruct, actualStruct)
|
||||
}
|
||||
|
||||
// RUD for secret-id-bound-cidrs field
|
||||
roleReq.Path = "role/role1/secret-id-bound-cidrs"
|
||||
roleReq.Operation = logical.ReadOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
if resp.Data["secret_id_bound_cidrs"].([]string)[0] != "127.0.0.1/32" ||
|
||||
resp.Data["secret_id_bound_cidrs"].([]string)[1] != "127.0.0.1/16" {
|
||||
t.Fatalf("bad: secret_id_bound_cidrs: expected:127.0.0.1/32,127.0.0.1/16 actual:%d\n", resp.Data["secret_id_bound_cidrs"].(int))
|
||||
}
|
||||
|
||||
roleReq.Data = map[string]interface{}{"secret_id_bound_cidrs": []string{"127.0.0.1/20"}}
|
||||
roleReq.Operation = logical.UpdateOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
roleReq.Operation = logical.ReadOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
if resp.Data["secret_id_bound_cidrs"].([]string)[0] != "127.0.0.1/20" {
|
||||
t.Fatalf("bad: secret_id_bound_cidrs: expected:127.0.0.1/20 actual:%s\n", resp.Data["secret_id_bound_cidrs"].([]string)[0])
|
||||
}
|
||||
|
||||
roleReq.Operation = logical.DeleteOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
roleReq.Operation = logical.ReadOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
if len(resp.Data["secret_id_bound_cidrs"].([]string)) != 0 {
|
||||
t.Fatalf("expected value to be reset")
|
||||
}
|
||||
|
||||
// RUD for token-bound-cidrs field
|
||||
roleReq.Path = "role/role1/token-bound-cidrs"
|
||||
roleReq.Operation = logical.ReadOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
if resp.Data["token_bound_cidrs"].([]string)[0] != "127.0.0.1/32" ||
|
||||
resp.Data["token_bound_cidrs"].([]string)[1] != "127.0.0.1/16" {
|
||||
t.Fatalf("bad: token_bound_cidrs: expected:127.0.0.1/32,127.0.0.1/16 actual:%d\n", resp.Data["token_bound_cidrs"].(int))
|
||||
}
|
||||
|
||||
roleReq.Data = map[string]interface{}{"token_bound_cidrs": []string{"127.0.0.1/20"}}
|
||||
roleReq.Operation = logical.UpdateOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
roleReq.Operation = logical.ReadOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
if resp.Data["token_bound_cidrs"].([]string)[0] != "127.0.0.1/20" {
|
||||
t.Fatalf("bad: token_bound_cidrs: expected:127.0.0.1/20 actual:%s\n", resp.Data["token_bound_cidrs"].([]string)[0])
|
||||
}
|
||||
|
||||
roleReq.Operation = logical.DeleteOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
roleReq.Operation = logical.ReadOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
if len(resp.Data["token_bound_cidrs"].([]string)) != 0 {
|
||||
t.Fatalf("expected value to be reset")
|
||||
}
|
||||
|
||||
// Delete test for role
|
||||
roleReq.Path = "role/role1"
|
||||
roleReq.Operation = logical.DeleteOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
roleReq.Operation = logical.ReadOperation
|
||||
resp, err = b.HandleRequest(context.Background(), roleReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
t.Fatalf("expected a nil response")
|
||||
}
|
||||
}
|
||||
|
||||
func createRole(t *testing.T, b *backend, s logical.Storage, roleName, policies string) {
|
||||
roleData := map[string]interface{}{
|
||||
"policies": policies,
|
||||
|
||||
@@ -80,6 +80,19 @@ func (d *FieldData) GetDefaultOrZero(k string) interface{} {
|
||||
return schema.DefaultOrZero()
|
||||
}
|
||||
|
||||
// GetFirst gets the value for the given field names, in order from first
|
||||
// to last. This can be useful for fields with a current name, and one or
|
||||
// more deprecated names. The second return value will be false if the keys
|
||||
// are invalid or the keys are not set at all.
|
||||
func (d *FieldData) GetFirst(k ...string) (interface{}, bool) {
|
||||
for _, v := range k {
|
||||
if result, ok := d.GetOk(v); ok {
|
||||
return result, ok
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetOk gets the value for the given field. The second return value
|
||||
// will be false if the key is invalid or the key is not set at all.
|
||||
func (d *FieldData) GetOk(k string) (interface{}, bool) {
|
||||
|
||||
@@ -642,3 +642,37 @@ func TestFieldDataGet_Error(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldDataGetFirst(t *testing.T) {
|
||||
data := &FieldData{
|
||||
Raw: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"fizz": "buzz",
|
||||
},
|
||||
Schema: map[string]*FieldSchema{
|
||||
"foo": {Type: TypeNameString},
|
||||
"fizz": {Type: TypeNameString},
|
||||
},
|
||||
}
|
||||
|
||||
result, ok := data.GetFirst("foo", "fizz")
|
||||
if !ok {
|
||||
t.Fatal("should have found value for foo")
|
||||
}
|
||||
if result.(string) != "bar" {
|
||||
t.Fatal("should have gotten bar for foo")
|
||||
}
|
||||
|
||||
result, ok = data.GetFirst("fizz", "foo")
|
||||
if !ok {
|
||||
t.Fatal("should have found value for fizz")
|
||||
}
|
||||
if result.(string) != "buzz" {
|
||||
t.Fatal("should have gotten buzz for fizz")
|
||||
}
|
||||
|
||||
result, ok = data.GetFirst("cats")
|
||||
if ok {
|
||||
t.Fatal("shouldn't have gotten anything for cats")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,9 +69,12 @@ enabled while creating or updating a role.
|
||||
- `role_name` `(string: <required>)` - Name of the AppRole.
|
||||
- `bind_secret_id` `(bool: true)` - Require `secret_id` to be presented when
|
||||
logging in using this AppRole.
|
||||
- `bound_cidr_list` `(array: [])` - Comma-separated string or list of CIDR
|
||||
- `secret_id_bound_cidrs` `(array: [])` - Comma-separated string or list of CIDR
|
||||
blocks; if set, specifies blocks of IP addresses which can perform the login
|
||||
operation.
|
||||
- `token_bound_cidrs` `(array: [])` - Comma-separated string or list of CIDR
|
||||
blocks; if set, specifies blocks of IP addresses which can use the auth tokens
|
||||
generated by this role.
|
||||
- `policies` `(array: [])` - Comma-separated list of policies set on tokens
|
||||
issued via this AppRole.
|
||||
- `secret_id_num_uses` `(integer: 0)` - Number of times any particular SecretID
|
||||
|
||||
@@ -226,7 +226,7 @@ credentials for login. The `bind_secret_id` constraint requires `secret_id` to
|
||||
be presented at the login endpoint. Going forward, this auth method can support
|
||||
more constraint parameters to support varied set of Apps. Some constraints will
|
||||
not require a credential, but still enforce constraints for login. For
|
||||
example, `bound_cidr_list` will only allow requests coming from IP addresses
|
||||
example, `secret_id_bound_cidrs` will only allow logins coming from IP addresses
|
||||
belonging to configured CIDR blocks on the AppRole.
|
||||
|
||||
## API
|
||||
|
||||
Reference in New Issue
Block a user