Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support VMSS Flex Authentications #63

Merged
merged 5 commits into from
Oct 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute"
"github.com/Azure/azure-sdk-for-go/services/msi/mgmt/2018-11-30/msi"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/azure/auth"
Expand All @@ -32,6 +33,10 @@ type vmssClient interface {
Get(ctx context.Context, resourceGroup, vmssName string, expandTypes compute.ExpandTypesForGetVMScaleSets) (compute.VirtualMachineScaleSet, error)
}

type msiClient interface {
Get(ctx context.Context, resourceGroup, resourceName string) (result msi.Identity, err error)
}

type tokenVerifier interface {
Verify(ctx context.Context, token string) (*oidc.IDToken, error)
}
Expand All @@ -40,6 +45,7 @@ type provider interface {
Verifier() tokenVerifier
ComputeClient(subscriptionID string) (computeClient, error)
VMSSClient(subscriptionID string) (vmssClient, error)
MSIClient(subscriptionID string) (msiClient, error)
}

type azureProvider struct {
Expand Down Expand Up @@ -139,6 +145,19 @@ func (p *azureProvider) VMSSClient(subscriptionID string) (vmssClient, error) {
return client, nil
}

func (p *azureProvider) MSIClient(subscriptionID string) (msiClient, error) {
authorizer, err := p.getAuthorizer()
if err != nil {
return nil, err
}

client := msi.NewUserAssignedIdentitiesClientWithBaseURI(p.settings.Environment.ResourceManagerEndpoint, subscriptionID)
client.Authorizer = authorizer
client.Sender = p.httpClient
client.AddToUserAgent(userAgent(p.settings.PluginEnv))
return client, nil
}

func (p *azureProvider) getAuthorizer() (autorest.Authorizer, error) {
p.lock.RLock()
unlockFunc := p.lock.RUnlock
Expand Down
24 changes: 23 additions & 1 deletion azure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"

"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute"
"github.com/Azure/azure-sdk-for-go/services/msi/mgmt/2018-11-30/msi"
"github.com/coreos/go-oidc"
)

Expand Down Expand Up @@ -44,6 +45,10 @@ type mockVMSSClient struct {
vmssClientFunc func(vmssName string) (compute.VirtualMachineScaleSet, error)
}

type mockMSIClient struct {
msiClientFunc func(resourceName string) (msi.Identity, error)
}

func (c *mockComputeClient) Get(_ context.Context, _, vmName string, _ compute.InstanceViewTypes) (compute.VirtualMachine, error) {
if c.computeClientFunc != nil {
return c.computeClientFunc(vmName)
Expand All @@ -58,19 +63,30 @@ func (c *mockVMSSClient) Get(_ context.Context, _, vmssName string, _ compute.Ex
return compute.VirtualMachineScaleSet{}, nil
}

func (c *mockMSIClient) Get(_ context.Context, _, resourceName string) (msi.Identity, error) {
if c.msiClientFunc != nil {
return c.msiClientFunc(resourceName)
}
return msi.Identity{}, nil
}

type computeClientFunc func(vmName string) (compute.VirtualMachine, error)

type vmssClientFunc func(vmssName string) (compute.VirtualMachineScaleSet, error)

type msiClientFunc func(resourceName string) (msi.Identity, error)

type mockProvider struct {
computeClientFunc
vmssClientFunc
msiClientFunc
}

func newMockProvider(c computeClientFunc, v vmssClientFunc) *mockProvider {
func newMockProvider(c computeClientFunc, v vmssClientFunc, m msiClientFunc) *mockProvider {
return &mockProvider{
computeClientFunc: c,
vmssClientFunc: v,
msiClientFunc: m,
}
}

Expand All @@ -89,3 +105,9 @@ func (p *mockProvider) VMSSClient(string) (vmssClient, error) {
vmssClientFunc: p.vmssClientFunc,
}, nil
}

func (p *mockProvider) MSIClient(string) (msiClient, error) {
return &mockMSIClient{
msiClientFunc: p.msiClientFunc,
}, nil
}
6 changes: 3 additions & 3 deletions backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import (
)

func getTestBackend(t *testing.T) (*azureAuthBackend, logical.Storage) {
return getTestBackendWithComputeClient(t, nil, nil)
return getTestBackendWithComputeClient(t, nil, nil, nil)
}

func getTestBackendWithComputeClient(t *testing.T, c computeClientFunc, v vmssClientFunc) (*azureAuthBackend, logical.Storage) {
func getTestBackendWithComputeClient(t *testing.T, c computeClientFunc, v vmssClientFunc, m msiClientFunc) (*azureAuthBackend, logical.Storage) {
t.Helper()
defaultLeaseTTLVal := time.Hour * 12
maxLeaseTTLVal := time.Hour * 24
Expand All @@ -30,6 +30,6 @@ func getTestBackendWithComputeClient(t *testing.T, c computeClientFunc, v vmssCl
if err != nil {
t.Fatalf("unable to create backend: %v", err)
}
b.provider = newMockProvider(c, v)
b.provider = newMockProvider(c, v, m)
return b, config.StorageView
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ module github.com/hashicorp/vault-plugin-auth-azure
go 1.17

require (
github.com/Azure/azure-sdk-for-go v61.4.0+incompatible
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible
github.com/Azure/go-autorest/autorest v0.11.24
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/gofrs/uuid v4.3.0+incompatible
github.com/hashicorp/errwrap v1.1.0
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-hclog v1.1.0
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go v61.4.0+incompatible h1:BF2Pm3aQWIa6q9KmxyF1JYKYXtVw67vtvu2Wd54NGuY=
github.com/Azure/azure-sdk-for-go v61.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible h1:bmmC38SlE8/E81nNADlgmVGurPWMHDX2YNXVQMrBpEE=
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.24 h1:1fIGgHKqVm54KIPT+q8Zmd1QlVsmHqeUGso5qm2BqqE=
Expand Down Expand Up @@ -132,6 +132,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc=
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
Expand Down
32 changes: 30 additions & 2 deletions path_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute"
Expand Down Expand Up @@ -273,8 +274,35 @@ func (b *azureAuthBackend) verifyResource(ctx context.Context, subscriptionID, r
principalIDs[to.String(vmss.Identity.PrincipalID)] = struct{}{}
}
// if not, look for user-assigned identities
for _, userIdentity := range vmss.Identity.UserAssignedIdentities {
principalIDs[to.String(userIdentity.PrincipalID)] = struct{}{}
for userIdentityID, userIdentity := range vmss.Identity.UserAssignedIdentities {
// Principal ID is not nil for VMSS uniform orchestration mode
if userIdentity.PrincipalID != nil {
principalIDs[to.String(userIdentity.PrincipalID)] = struct{}{}
continue
}

elements := strings.Split(userIdentityID, "/")
if len(elements) < 9 {
return fmt.Errorf("unable to parse the user-assigned identity resource ID: %s", userIdentityID)
}
msiSubscriptionID := elements[2]
msiResourceGroupName := elements[4]
msiResourceName := elements[8]
zmyzheng marked this conversation as resolved.
Show resolved Hide resolved

zmyzheng marked this conversation as resolved.
Show resolved Hide resolved
// Principal ID is nil for VMSS flex orchestration mode, so we
// must look up the user-assigned identity using the MSI client
msiClient, err := b.provider.MSIClient(msiSubscriptionID)
if err != nil {
return fmt.Errorf("unable to create msi client: %w", err)
}
userIdentity, err := msiClient.Get(ctx, msiResourceGroupName, msiResourceName)
if err != nil {
return fmt.Errorf("unable to retrieve user assigned identity metadata: %w", err)
}

if userIdentity.PrincipalID != nil {
principalIDs[userIdentity.PrincipalID.String()] = struct{}{}
}
}
case vmName != "":
client, err := b.provider.ComputeClient(subscriptionID)
Expand Down
75 changes: 64 additions & 11 deletions path_login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import (
"time"

"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute"
"github.com/Azure/azure-sdk-for-go/services/msi/mgmt/2018-11-30/msi"
"github.com/coreos/go-oidc"
"github.com/gofrs/uuid"
"github.com/hashicorp/vault/sdk/helper/policyutil"
"github.com/hashicorp/vault/sdk/logical"
)
Expand Down Expand Up @@ -169,7 +171,8 @@ func TestLogin_BoundGroupID(t *testing.T) {
}

func TestLogin_BoundSubscriptionID(t *testing.T) {
principalID := "prinID"
principalID := "123e4567-e89b-12d3-a456-426655440000"
principalUUID := uuid.Must(uuid.FromString(principalID))
c := func(_ string) (compute.VirtualMachine, error) {
id := compute.VirtualMachineIdentity{
PrincipalID: &principalID,
Expand All @@ -187,7 +190,16 @@ func TestLogin_BoundSubscriptionID(t *testing.T) {
}, nil
}

b, s := getTestBackendWithComputeClient(t, c, v)
m := func(_ string) (msi.Identity, error) {
userAssignedIdentityProperties := msi.UserAssignedIdentityProperties{
PrincipalID: &principalUUID,
}
return msi.Identity{
UserAssignedIdentityProperties: &userAssignedIdentityProperties,
}, nil
}

b, s := getTestBackendWithComputeClient(t, c, v, m)

roleName := "testrole"
subID := "subID"
Expand Down Expand Up @@ -227,7 +239,8 @@ func TestLogin_BoundSubscriptionID(t *testing.T) {
}

func TestLogin_BoundResourceGroup(t *testing.T) {
principalID := "prinID"
principalID := "123e4567-e89b-12d3-a456-426655440000"
principalUUID := uuid.Must(uuid.FromString(principalID))
c := func(_ string) (compute.VirtualMachine, error) {
id := compute.VirtualMachineIdentity{
PrincipalID: &principalID,
Expand All @@ -244,7 +257,17 @@ func TestLogin_BoundResourceGroup(t *testing.T) {
Identity: &id,
}, nil
}
b, s := getTestBackendWithComputeClient(t, c, v)

m := func(_ string) (msi.Identity, error) {
userAssignedIdentityProperties := msi.UserAssignedIdentityProperties{
PrincipalID: &principalUUID,
}
return msi.Identity{
UserAssignedIdentityProperties: &userAssignedIdentityProperties,
}, nil
}

b, s := getTestBackendWithComputeClient(t, c, v, m)

roleName := "testrole"
rg := "rg"
Expand Down Expand Up @@ -284,7 +307,8 @@ func TestLogin_BoundResourceGroup(t *testing.T) {
}

func TestLogin_BoundResourceGroupWithUserAssignedID(t *testing.T) {
principalID := "prinID"
principalID := "123e4567-e89b-12d3-a456-426655440000"
principalUUID := uuid.Must(uuid.FromString(principalID))
badPrincipalID := "badID"
c := func(_ string) (compute.VirtualMachine, error) {
id := compute.VirtualMachineIdentity{
Expand All @@ -301,7 +325,7 @@ func TestLogin_BoundResourceGroupWithUserAssignedID(t *testing.T) {
v := func(_ string) (compute.VirtualMachineScaleSet, error) {
id := compute.VirtualMachineScaleSetIdentity{
UserAssignedIdentities: map[string]*compute.VirtualMachineScaleSetIdentityUserAssignedIdentitiesValue{
"mockuserassignedmsi": {
"/subscriptions/sub/resourceGroups/rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/123e4567-e89b-12d3-a456-426655440000": {
PrincipalID: &principalID,
},
},
Expand All @@ -310,7 +334,16 @@ func TestLogin_BoundResourceGroupWithUserAssignedID(t *testing.T) {
Identity: &id,
}, nil
}
b, s := getTestBackendWithComputeClient(t, c, v)
m := func(_ string) (msi.Identity, error) {
userAssignedIdentityProperties := msi.UserAssignedIdentityProperties{
PrincipalID: &principalUUID,
}
return msi.Identity{
UserAssignedIdentityProperties: &userAssignedIdentityProperties,
}, nil
}

b, s := getTestBackendWithComputeClient(t, c, v, m)

roleName := "testrole"
rg := "rg"
Expand Down Expand Up @@ -356,7 +389,8 @@ func TestLogin_BoundResourceGroupWithUserAssignedID(t *testing.T) {
}

func TestLogin_BoundLocation(t *testing.T) {
principalID := "prinID"
principalID := "123e4567-e89b-12d3-a456-426655440000"
principalUUID := uuid.Must(uuid.FromString(principalID))
location := "loc"
c := func(vmName string) (compute.VirtualMachine, error) {
id := compute.VirtualMachineIdentity{
Expand Down Expand Up @@ -397,7 +431,16 @@ func TestLogin_BoundLocation(t *testing.T) {
return compute.VirtualMachineScaleSet{}, nil
}

b, s := getTestBackendWithComputeClient(t, c, v)
m := func(_ string) (msi.Identity, error) {
userAssignedIdentityProperties := msi.UserAssignedIdentityProperties{
PrincipalID: &principalUUID,
}
return msi.Identity{
UserAssignedIdentityProperties: &userAssignedIdentityProperties,
}, nil
}

b, s := getTestBackendWithComputeClient(t, c, v, m)

roleName := "testrole"
roleData := map[string]interface{}{
Expand Down Expand Up @@ -437,7 +480,8 @@ func TestLogin_BoundLocation(t *testing.T) {
}

func TestLogin_BoundScaleSet(t *testing.T) {
principalID := "prinID"
principalID := "123e4567-e89b-12d3-a456-426655440000"
principalUUID := uuid.Must(uuid.FromString(principalID))
c := func(_ string) (compute.VirtualMachine, error) {
id := compute.VirtualMachineIdentity{
PrincipalID: &principalID,
Expand All @@ -455,7 +499,16 @@ func TestLogin_BoundScaleSet(t *testing.T) {
}, nil
}

b, s := getTestBackendWithComputeClient(t, c, v)
m := func(_ string) (msi.Identity, error) {
userAssignedIdentityProperties := msi.UserAssignedIdentityProperties{
PrincipalID: &principalUUID,
}
return msi.Identity{
UserAssignedIdentityProperties: &userAssignedIdentityProperties,
}, nil
}

b, s := getTestBackendWithComputeClient(t, c, v, m)

roleName := "testrole"
roleData := map[string]interface{}{
Expand Down