updating azure auth plugin and docs (#4975)

This commit is contained in:
Chris Hoffman 2018-07-23 10:00:44 -04:00 committed by GitHub
parent ecc622ac43
commit 3ba265cf6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 26 deletions

View file

@ -24,6 +24,10 @@ type computeClient interface {
Get(ctx context.Context, resourceGroup, vmName string, instanceView compute.InstanceViewTypes) (compute.VirtualMachine, error)
}
type vmssClient interface {
Get(ctx context.Context, resourceGroup, vmssName string) (compute.VirtualMachineScaleSet, error)
}
type tokenVerifier interface {
Verify(ctx context.Context, token string) (*oidc.IDToken, error)
}
@ -31,6 +35,7 @@ type tokenVerifier interface {
type provider interface {
Verifier() tokenVerifier
ComputeClient(subscriptionID string) computeClient
VMSSClient(subscriptionID string) vmssClient
}
type azureProvider struct {
@ -134,6 +139,14 @@ func (p *azureProvider) ComputeClient(subscriptionID string) computeClient {
return client
}
func (p *azureProvider) VMSSClient(subscriptionID string) vmssClient {
client := compute.NewVirtualMachineScaleSetsClient(subscriptionID)
client.Authorizer = p.authorizer
client.Sender = p.httpClient
client.AddToUserAgent(userAgent())
return client
}
type azureSettings struct {
TenantID string
ClientID string

View file

@ -59,15 +59,15 @@ type azureConfig struct {
}
func (b *azureAuthBackend) config(ctx context.Context, s logical.Storage) (*azureConfig, error) {
config := new(azureConfig)
entry, err := s.Get(ctx, "config")
if err != nil {
return nil, err
}
if entry == nil {
return config, nil
return nil, nil
}
config := new(azureConfig)
if err := entry.DecodeJSON(config); err != nil {
return nil, err
}

View file

@ -35,7 +35,11 @@ func pathLogin(b *azureAuthBackend) *framework.Path {
},
"vm_name": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The name of the virtual machine.`,
Description: `The name of the virtual machine. This value is ignored if vmss_name is specified.`,
},
"vmss_name": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The name of the virtual machine scale set the instance is in.`,
},
},
@ -60,12 +64,16 @@ func (b *azureAuthBackend) pathLogin(ctx context.Context, req *logical.Request,
}
subscriptionID := data.Get("subscription_id").(string)
resourceGroupName := data.Get("resource_group_name").(string)
vmssName := data.Get("vmss_name").(string)
vmName := data.Get("vm_name").(string)
config, err := b.config(ctx, req.Storage)
if err != nil {
return nil, errwrap.Wrapf("unable to retrieve backend configuration: {{err}}", err)
}
if config == nil {
config = new(azureConfig)
}
role, err := b.role(ctx, req.Storage, roleName)
if err != nil {
@ -75,7 +83,6 @@ func (b *azureAuthBackend) pathLogin(ctx context.Context, req *logical.Request,
return logical.ErrorResponse(fmt.Sprintf("invalid role name %q", roleName)), nil
}
// Set the client id for 'aud' claim verification
provider, err := b.getProvider(config)
if err != nil {
return nil, err
@ -98,7 +105,7 @@ func (b *azureAuthBackend) pathLogin(ctx context.Context, req *logical.Request,
return nil, err
}
if err := b.verifyResource(ctx, subscriptionID, resourceGroupName, vmName, claims, role); err != nil {
if err := b.verifyResource(ctx, subscriptionID, resourceGroupName, vmName, vmssName, claims, role); err != nil {
return nil, err
}
@ -168,30 +175,72 @@ func (b *azureAuthBackend) verifyClaims(claims *additionalClaims, role *azureRol
return nil
}
func (b *azureAuthBackend) verifyResource(ctx context.Context, subscriptionID, resourceGroupName, vmName string, claims *additionalClaims, role *azureRole) error {
func (b *azureAuthBackend) verifyResource(ctx context.Context, subscriptionID, resourceGroupName, vmName string, vmssName string, claims *additionalClaims, role *azureRole) error {
// If not checking anything with the resource id, exit early
if len(role.BoundResourceGroups) == 0 && len(role.BoundSubscriptionsIDs) == 0 && len(role.BoundLocations) == 0 {
if len(role.BoundResourceGroups) == 0 && len(role.BoundSubscriptionsIDs) == 0 && len(role.BoundLocations) == 0 && len(role.BoundScaleSets) == 0 {
return nil
}
if subscriptionID == "" || resourceGroupName == "" || vmName == "" {
return errors.New("subscription_id, resource_group_name, and vm_name are required")
if subscriptionID == "" || resourceGroupName == "" {
return errors.New("subscription_id and resource_group_name are required")
}
client := b.provider.ComputeClient(subscriptionID)
vm, err := client.Get(ctx, resourceGroupName, vmName, compute.InstanceView)
if err != nil {
return errwrap.Wrapf("unable to retrieve virtual machine metadata: {{err}}", err)
var principalID, location *string
switch {
// If vmss name is specified, the vm name will be ignored and only the scale set
// will be verified since vm names are generated automatically for scale sets
case vmssName != "":
client := b.provider.VMSSClient(subscriptionID)
vmss, err := client.Get(ctx, resourceGroupName, vmssName)
if err != nil {
return errwrap.Wrapf("unable to retrieve virtual machine scale set metadata: {{err}}", err)
}
if vmss.Identity == nil {
return errors.New("vmss client did not return identity information")
}
if vmss.Identity.PrincipalID == nil {
return errors.New("vmss principal id is empty")
}
// Check bound scale sets
if len(role.BoundScaleSets) > 0 && !strListContains(role.BoundScaleSets, vmssName) {
return errors.New("scale set not authorized")
}
principalID = vmss.Identity.PrincipalID
location = vmss.Location
case vmName != "":
client := b.provider.ComputeClient(subscriptionID)
vm, err := client.Get(ctx, resourceGroupName, vmName, compute.InstanceView)
if err != nil {
return errwrap.Wrapf("unable to retrieve virtual machine metadata: {{err}}", err)
}
if vm.Identity == nil {
return errors.New("vm client did not return identity information")
}
if vm.Identity.PrincipalID == nil {
return errors.New("vm principal id is empty")
}
// Check bound scale sets
if len(role.BoundScaleSets) > 0 {
return errors.New("bound scale set defined but this vm isn't in a scale set")
}
principalID = vm.Identity.PrincipalID
location = vm.Location
default:
return errors.New("either vm_name or vmss_name is required")
}
// Ensure the principal id for the VM matches the verified token OID
if vm.Identity == nil {
return errors.New("vm client did not return identity information")
}
if vm.Identity.PrincipalID == nil {
return errors.New("vm principal id is empty")
}
if to.String(vm.Identity.PrincipalID) != claims.ObjectID {
if to.String(principalID) != claims.ObjectID {
return errors.New("token object id does not match virtual machine principal id")
}
@ -207,10 +256,10 @@ func (b *azureAuthBackend) verifyResource(ctx context.Context, subscriptionID, r
// Check bound locations
if len(role.BoundLocations) > 0 {
if vm.Location == nil {
if location == nil {
return errors.New("vm location is empty")
}
if !strListContains(role.BoundLocations, to.String(vm.Location)) {
if !strListContains(role.BoundLocations, to.String(location)) {
return errors.New("location not authorized")
}
}

View file

@ -79,6 +79,11 @@ is restricted to.`,
"bound_locations": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `Comma-separated list of locations that login
is restricted to.`,
},
"bound_scale_sets": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `Comma-separated list of scale sets that login
is restricted to.`,
},
},
@ -121,6 +126,7 @@ type azureRole struct {
BoundResourceGroups []string `json:"bound_resource_groups"`
BoundSubscriptionsIDs []string `json:"bound_subscription_ids"`
BoundLocations []string `json:"bound_locations"`
BoundScaleSets []string `json:"bound_scale_sets"`
}
// role takes a storage backend and the name and returns the role's storage
@ -193,6 +199,7 @@ func (b *azureAuthBackend) pathRoleRead(ctx context.Context, req *logical.Reques
"bound_subscription_ids": role.BoundSubscriptionsIDs,
"bound_resource_groups": role.BoundResourceGroups,
"bound_locations": role.BoundLocations,
"bound_scale_sets": role.BoundScaleSets,
},
}
@ -291,11 +298,16 @@ func (b *azureAuthBackend) pathRoleCreateUpdate(ctx context.Context, req *logica
role.BoundLocations = boundLocations.([]string)
}
if boundScaleSets, ok := data.GetOk("bound_scale_sets"); ok {
role.BoundScaleSets = boundScaleSets.([]string)
}
if len(role.BoundServicePrincipalIDs) == 0 &&
len(role.BoundGroupIDs) == 0 &&
len(role.BoundSubscriptionsIDs) == 0 &&
len(role.BoundResourceGroups) == 0 &&
len(role.BoundLocations) == 0 {
len(role.BoundLocations) == 0 &&
len(role.BoundScaleSets) == 0 {
return logical.ErrorResponse("must have at least one bound constraint when creating/updating a role"), nil
}

6
vendor/vendor.json vendored
View file

@ -1297,10 +1297,10 @@
"revisionTime": "2018-05-30T15:59:58Z"
},
{
"checksumSHA1": "SQ1DvDihitOt2BxCOXn9mrGXnSA=",
"checksumSHA1": "bgitTutpYaZwaGQ5VTZjSbPHlmc=",
"path": "github.com/hashicorp/vault-plugin-auth-azure",
"revision": "5bbc3c8a4d777d9655b6009ace708f725d9679f7",
"revisionTime": "2018-06-26T21:25:21Z"
"revision": "075d5da053e049e818fdea6ab4225e7d43020a6a",
"revisionTime": "2018-07-23T12:29:58Z"
},
{
"checksumSHA1": "LI3ZsYvX/mU/o5EL3gE0qwKcSPs=",

View file

@ -135,6 +135,8 @@ entities attempting to login.
is restricted to.
- `bound_resource_group_names` `(array: [])` - The list of resource groups that
login is restricted to.
- `bound_scale_sets` `(array: [])` - The list of scale set names that the
login is restricted to.
### Sample Payload
@ -283,6 +285,9 @@ entity and then authorizes the entity for the given role.
metadata.
- `vm_name` `(string: "")` - The virtual machine name for the machine that
generated the MSI token. This information can be obtained through instance
metadata. If vmss_name is provided, this value is ignored.
- `vmss_name` `(string: "")` - The virtual machine scale set name for the machine
that generated the MSI token. This information can be obtained through instance
metadata.
### Sample Payload