diff --git a/go.mod b/go.mod index ec0b872ea..b3fd3ef8b 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/Azure/go-autorest/autorest/to v0.4.0 github.com/Azure/go-autorest/autorest/validation v0.3.1 github.com/Azure/go-autorest/tracing v0.6.0 - github.com/Azure/msi-dataplane v0.0.6 + github.com/Azure/msi-dataplane v0.0.8 github.com/apparentlymart/go-cidr v1.1.0 github.com/codahale/etm v0.0.0-20141003032925-c00c9e6fb4c9 github.com/containers/image/v5 v5.30.1 diff --git a/go.sum b/go.sum index 41b39be91..7fb171b5c 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/Azure/msi-dataplane v0.0.6 h1:+IhGETRF9lLNlbs6793xWFKMcbborBtaR2ops1XWlPo= -github.com/Azure/msi-dataplane v0.0.6/go.mod h1:/fXvAsxSogxoT7If0xfaeyaIQ7Q/0xAY9ISn7lOpA4o= +github.com/Azure/msi-dataplane v0.0.8 h1:vIopp85cLy1kWdZUaMnNlu1ssvdRLOxU8KRdTahWrwg= +github.com/Azure/msi-dataplane v0.0.8/go.mod h1:/fXvAsxSogxoT7If0xfaeyaIQ7Q/0xAY9ISn7lOpA4o= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index b120f4d29..4ac54f4f1 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -149,7 +149,7 @@ func New(ctx context.Context, log *logrus.Entry, _env env.Interface, db database return nil, err } - localFPAuthorizer, err := _env.FPAuthorizer(_env.TenantID(), _env.Environment().ResourceManagerScope) + localFPAuthorizer, err := _env.FPAuthorizer(_env.TenantID(), nil, _env.Environment().ResourceManagerScope) if err != nil { return nil, err } @@ -160,7 +160,7 @@ func New(ctx context.Context, log *logrus.Entry, _env env.Interface, db database return nil, err } - fpCredClusterTenant, err := _env.FPNewClientCertificateCredential(subscriptionDoc.Subscription.Properties.TenantID) + fpCredClusterTenant, err := _env.FPNewClientCertificateCredential(subscriptionDoc.Subscription.Properties.TenantID, nil) if err != nil { return nil, err } @@ -175,7 +175,7 @@ func New(ctx context.Context, log *logrus.Entry, _env env.Interface, db database } fpspID := tokenClaims.ObjectId - fpCredRPTenant, err := _env.FPNewClientCertificateCredential(_env.TenantID()) + fpCredRPTenant, err := _env.FPNewClientCertificateCredential(_env.TenantID(), nil) if err != nil { return nil, err } @@ -325,7 +325,12 @@ func New(ctx context.Context, log *logrus.Entry, _env env.Interface, db database return nil, err } - authenticatorPolicy := dataplane.NewAuthenticatorPolicy(fpCredRPTenant, _env.MsiRpEndpoint()) + // MSI dataplane client receives tenant from the bearer challenge, so we can't limit the allowed tenants in the credential + fpMSICred, err := _env.FPNewClientCertificateCredential(_env.TenantID(), []string{"*"}) + if err != nil { + return nil, err + } + authenticatorPolicy := dataplane.NewAuthenticatorPolicy(fpMSICred, _env.MsiRpEndpoint()) msiDataplane, err := dataplane.NewClient(cloud, authenticatorPolicy, msiDataplaneClientOptions) if err != nil { return nil, err diff --git a/pkg/env/armhelper.go b/pkg/env/armhelper.go index baf3b548a..8f76d14f0 100644 --- a/pkg/env/armhelper.go +++ b/pkg/env/armhelper.go @@ -79,7 +79,7 @@ func newARMHelper(ctx context.Context, log *logrus.Entry, env Interface) (ARMHel return nil, err } - options := env.Environment().ClientCertificateCredentialOptions() + options := env.Environment().ClientCertificateCredentialOptions(nil) armHelperTokenCredential, err := azidentity.NewClientCertificateCredential(env.TenantID(), os.Getenv("AZURE_ARM_CLIENT_ID"), certs, key, options) if err != nil { return nil, err @@ -89,7 +89,7 @@ func newARMHelper(ctx context.Context, log *logrus.Entry, env Interface) (ARMHel armHelperAuthorizer := azidext.NewTokenCredentialAdapter(armHelperTokenCredential, scopes) // Graph service client uses the first party service principal. - fpTokenCredential, err := env.FPNewClientCertificateCredential(env.TenantID()) + fpTokenCredential, err := env.FPNewClientCertificateCredential(env.TenantID(), nil) if err != nil { return nil, err } diff --git a/pkg/env/dev.go b/pkg/env/dev.go index 1dee4514c..f090d9dbe 100644 --- a/pkg/env/dev.go +++ b/pkg/env/dev.go @@ -81,8 +81,8 @@ func (d *dev) Listen() (net.Listener, error) { } // TODO: Delete FPAuthorizer once the replace from track1 to track2 is done. -func (d *dev) FPAuthorizer(tenantID string, scopes ...string) (autorest.Authorizer, error) { - fpTokenCredential, err := d.FPNewClientCertificateCredential(tenantID) +func (d *dev) FPAuthorizer(tenantID string, additionalTenants []string, scopes ...string) (autorest.Authorizer, error) { + fpTokenCredential, err := d.FPNewClientCertificateCredential(tenantID, additionalTenants) if err != nil { return nil, err } @@ -90,10 +90,10 @@ func (d *dev) FPAuthorizer(tenantID string, scopes ...string) (autorest.Authoriz return azidext.NewTokenCredentialAdapter(fpTokenCredential, scopes), nil } -func (d *dev) FPNewClientCertificateCredential(tenantID string) (*azidentity.ClientCertificateCredential, error) { +func (d *dev) FPNewClientCertificateCredential(tenantID string, additionalTenants []string) (*azidentity.ClientCertificateCredential, error) { fpPrivateKey, fpCertificates := d.fpCertificateRefresher.GetCertificates() - options := d.Environment().ClientCertificateCredentialOptions() + options := d.Environment().ClientCertificateCredentialOptions(additionalTenants) credential, err := azidentity.NewClientCertificateCredential(tenantID, d.fpClientID, fpCertificates, fpPrivateKey, options) if err != nil { return nil, err diff --git a/pkg/env/env.go b/pkg/env/env.go index 4a8724c01..46601fa2c 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -89,8 +89,8 @@ type Interface interface { Domain() string FeatureIsSet(Feature) bool // TODO: Delete FPAuthorizer once the replace from track1 to track2 is done. - FPAuthorizer(string, ...string) (autorest.Authorizer, error) - FPNewClientCertificateCredential(string) (*azidentity.ClientCertificateCredential, error) + FPAuthorizer(string, []string, ...string) (autorest.Authorizer, error) + FPNewClientCertificateCredential(string, []string) (*azidentity.ClientCertificateCredential, error) FPClientID() string Listen() (net.Listener, error) GatewayDomains() []string diff --git a/pkg/env/prod.go b/pkg/env/prod.go index 72877d0af..4fd76f60e 100644 --- a/pkg/env/prod.go +++ b/pkg/env/prod.go @@ -160,7 +160,7 @@ func newProd(ctx context.Context, log *logrus.Entry, component ServiceComponent) return nil, err } - localFPKVAuthorizer, err := p.FPAuthorizer(p.TenantID(), p.Environment().KeyVaultScope) + localFPKVAuthorizer, err := p.FPAuthorizer(p.TenantID(), nil, p.Environment().KeyVaultScope) if err != nil { return nil, err } @@ -338,8 +338,8 @@ func (p *prod) FeatureIsSet(f Feature) bool { } // TODO: Delete FPAuthorizer once the replace from track1 to track2 is done. -func (p *prod) FPAuthorizer(tenantID string, scopes ...string) (autorest.Authorizer, error) { - fpTokenCredential, err := p.FPNewClientCertificateCredential(tenantID) +func (p *prod) FPAuthorizer(tenantID string, additionalTenants []string, scopes ...string) (autorest.Authorizer, error) { + fpTokenCredential, err := p.FPNewClientCertificateCredential(tenantID, additionalTenants) if err != nil { return nil, err } @@ -383,10 +383,10 @@ func (p *prod) LiveConfig() liveconfig.Manager { return p.liveConfig } -func (p *prod) FPNewClientCertificateCredential(tenantID string) (*azidentity.ClientCertificateCredential, error) { +func (p *prod) FPNewClientCertificateCredential(tenantID string, additionalTenants []string) (*azidentity.ClientCertificateCredential, error) { fpPrivateKey, fpCertificates := p.fpCertificateRefresher.GetCertificates() - options := p.Environment().ClientCertificateCredentialOptions() + options := p.Environment().ClientCertificateCredentialOptions(additionalTenants) credential, err := azidentity.NewClientCertificateCredential(tenantID, p.fpClientID, fpCertificates, fpPrivateKey, options) if err != nil { return nil, err diff --git a/pkg/frontend/adminactions/applens.go b/pkg/frontend/adminactions/applens.go index 6773214a4..8cdce2b9f 100644 --- a/pkg/frontend/adminactions/applens.go +++ b/pkg/frontend/adminactions/applens.go @@ -29,7 +29,7 @@ type appLensActions struct { func NewAppLensActions(log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster, subscriptionDoc *api.SubscriptionDocument) (AppLensActions, error) { - fpClientCertCred, err := env.FPNewClientCertificateCredential(env.Environment().AppLensTenantID) + fpClientCertCred, err := env.FPNewClientCertificateCredential(env.Environment().AppLensTenantID, nil) if err != nil { return nil, err } diff --git a/pkg/frontend/adminactions/azureactions.go b/pkg/frontend/adminactions/azureactions.go index 042834d6b..9884e8caa 100644 --- a/pkg/frontend/adminactions/azureactions.go +++ b/pkg/frontend/adminactions/azureactions.go @@ -58,7 +58,7 @@ type azureActions struct { // NewAzureActions returns an azureActions func NewAzureActions(log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster, subscriptionDoc *api.SubscriptionDocument) (AzureActions, error) { - fpAuth, err := env.FPAuthorizer(subscriptionDoc.Subscription.Properties.TenantID, + fpAuth, err := env.FPAuthorizer(subscriptionDoc.Subscription.Properties.TenantID, nil, env.Environment().ResourceManagerScope) if err != nil { return nil, err diff --git a/pkg/frontend/providers_validation.go b/pkg/frontend/providers_validation.go index 7bd1596bf..cb70894e1 100644 --- a/pkg/frontend/providers_validation.go +++ b/pkg/frontend/providers_validation.go @@ -29,7 +29,7 @@ var requiredResourceProviders = []string{ } func (p providersValidator) ValidateProviders(ctx context.Context, azEnv *azureclient.AROEnvironment, environment env.Interface, subscriptionID, tenantID string) error { - fpAuthorizer, err := environment.FPAuthorizer(tenantID, environment.Environment().ResourceManagerScope) + fpAuthorizer, err := environment.FPAuthorizer(tenantID, nil, environment.Environment().ResourceManagerScope) if err != nil { return err } diff --git a/pkg/frontend/quota_validation.go b/pkg/frontend/quota_validation.go index a8cd8657c..1b9c0fbf4 100644 --- a/pkg/frontend/quota_validation.go +++ b/pkg/frontend/quota_validation.go @@ -39,7 +39,7 @@ func addRequiredResources(requiredResources map[string]int, vmSize api.VMSize, c // creation // It is a method on struct so we can make use of interfaces. func (q quotaValidator) ValidateQuota(ctx context.Context, azEnv *azureclient.AROEnvironment, environment env.Interface, subscriptionID, tenantID string, oc *api.OpenShiftCluster) error { - fpAuthorizer, err := environment.FPAuthorizer(tenantID, environment.Environment().ResourceManagerScope) + fpAuthorizer, err := environment.FPAuthorizer(tenantID, nil, environment.Environment().ResourceManagerScope) if err != nil { return err } diff --git a/pkg/frontend/sku_validation.go b/pkg/frontend/sku_validation.go index 61dbcb9a2..5edee0e12 100644 --- a/pkg/frontend/sku_validation.go +++ b/pkg/frontend/sku_validation.go @@ -24,7 +24,7 @@ type SkuValidator interface { type skuValidator struct{} func (s skuValidator) ValidateVMSku(ctx context.Context, azEnv *azureclient.AROEnvironment, environment env.Interface, subscriptionID, tenantID string, oc *api.OpenShiftCluster) error { - fpAuthorizer, err := environment.FPAuthorizer(tenantID, environment.Environment().ResourceManagerScope) + fpAuthorizer, err := environment.FPAuthorizer(tenantID, nil, environment.Environment().ResourceManagerScope) if err != nil { return err } diff --git a/pkg/monitor/azure/nsg/nsg.go b/pkg/monitor/azure/nsg/nsg.go index 93af0be58..ac55d9abb 100644 --- a/pkg/monitor/azure/nsg/nsg.go +++ b/pkg/monitor/azure/nsg/nsg.go @@ -68,7 +68,7 @@ func NewMonitor(log *logrus.Entry, oc *api.OpenShiftCluster, e env.Interface, su return &monitoring.NoOpMonitor{Wg: wg} } - token, err := e.FPNewClientCertificateCredential(tenantID) + token, err := e.FPNewClientCertificateCredential(tenantID, nil) if err != nil { log.Error("Unable to create FP Authorizer for NSG monitoring.", err) emitter.EmitGauge(MetricFailedNSGMonitorCreation, int64(1), dims) diff --git a/pkg/monitor/azure/nsg/nsg_test.go b/pkg/monitor/azure/nsg/nsg_test.go index 40e55bc0f..9e1a76bec 100644 --- a/pkg/monitor/azure/nsg/nsg_test.go +++ b/pkg/monitor/azure/nsg/nsg_test.go @@ -613,7 +613,7 @@ func TestNewMonitor(t *testing.T) { emitter.EXPECT().EmitGauge(MetricFailedNSGMonitorCreation, int64(1), dims) }, mockInterface: func(mi *mock_env.MockInterface) { - mi.EXPECT().FPNewClientCertificateCredential(gomock.Any()).Return(nil, errors.New("Unknown Error")) + mi.EXPECT().FPNewClientCertificateCredential(gomock.Any(), gomock.Any()).Return(nil, errors.New("Unknown Error")) }, tick: true, valid: isOfType[*monitoring.NoOpMonitor], @@ -627,7 +627,7 @@ func TestNewMonitor(t *testing.T) { emitter.EXPECT().EmitGauge(MetricPreconfiguredNSGEnabled, int64(1), dims) }, mockInterface: func(mi *mock_env.MockInterface) { - mi.EXPECT().FPNewClientCertificateCredential(gomock.Any()).Return(&azidentity.ClientCertificateCredential{}, nil) + mi.EXPECT().FPNewClientCertificateCredential(gomock.Any(), gomock.Any()).Return(&azidentity.ClientCertificateCredential{}, nil) mi.EXPECT().Environment().Return(&azureclient.AROEnvironment{}) }, tick: true, diff --git a/pkg/util/azureclient/environments.go b/pkg/util/azureclient/environments.go index 1b9d48b39..c85a1f342 100644 --- a/pkg/util/azureclient/environments.go +++ b/pkg/util/azureclient/environments.go @@ -121,8 +121,9 @@ func (e *AROEnvironment) ArmClientOptions() *arm.ClientOptions { } } -func (e *AROEnvironment) ClientCertificateCredentialOptions() *azidentity.ClientCertificateCredentialOptions { +func (e *AROEnvironment) ClientCertificateCredentialOptions(additionalTenants []string) *azidentity.ClientCertificateCredentialOptions { return &azidentity.ClientCertificateCredentialOptions{ + AdditionallyAllowedTenants: additionalTenants, ClientOptions: azcore.ClientOptions{ Cloud: e.Cloud, }, diff --git a/pkg/util/mocks/env/env.go b/pkg/util/mocks/env/env.go index e19883b7c..787e59351 100644 --- a/pkg/util/mocks/env/env.go +++ b/pkg/util/mocks/env/env.go @@ -296,10 +296,10 @@ func (mr *MockInterfaceMockRecorder) Environment() *gomock.Call { } // FPAuthorizer mocks base method. -func (m *MockInterface) FPAuthorizer(arg0 string, arg1 ...string) (autorest.Authorizer, error) { +func (m *MockInterface) FPAuthorizer(arg0 string, arg1 []string, arg2 ...string) (autorest.Authorizer, error) { m.ctrl.T.Helper() - varargs := []any{arg0} - for _, a := range arg1 { + varargs := []any{arg0, arg1} + for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "FPAuthorizer", varargs...) @@ -309,9 +309,9 @@ func (m *MockInterface) FPAuthorizer(arg0 string, arg1 ...string) (autorest.Auth } // FPAuthorizer indicates an expected call of FPAuthorizer. -func (mr *MockInterfaceMockRecorder) FPAuthorizer(arg0 any, arg1 ...any) *gomock.Call { +func (mr *MockInterfaceMockRecorder) FPAuthorizer(arg0, arg1 any, arg2 ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{arg0}, arg1...) + varargs := append([]any{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FPAuthorizer", reflect.TypeOf((*MockInterface)(nil).FPAuthorizer), varargs...) } @@ -330,18 +330,18 @@ func (mr *MockInterfaceMockRecorder) FPClientID() *gomock.Call { } // FPNewClientCertificateCredential mocks base method. -func (m *MockInterface) FPNewClientCertificateCredential(arg0 string) (*azidentity.ClientCertificateCredential, error) { +func (m *MockInterface) FPNewClientCertificateCredential(arg0 string, arg1 []string) (*azidentity.ClientCertificateCredential, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FPNewClientCertificateCredential", arg0) + ret := m.ctrl.Call(m, "FPNewClientCertificateCredential", arg0, arg1) ret0, _ := ret[0].(*azidentity.ClientCertificateCredential) ret1, _ := ret[1].(error) return ret0, ret1 } // FPNewClientCertificateCredential indicates an expected call of FPNewClientCertificateCredential. -func (mr *MockInterfaceMockRecorder) FPNewClientCertificateCredential(arg0 any) *gomock.Call { +func (mr *MockInterfaceMockRecorder) FPNewClientCertificateCredential(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FPNewClientCertificateCredential", reflect.TypeOf((*MockInterface)(nil).FPNewClientCertificateCredential), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FPNewClientCertificateCredential", reflect.TypeOf((*MockInterface)(nil).FPNewClientCertificateCredential), arg0, arg1) } // FeatureIsSet mocks base method. diff --git a/pkg/util/refreshable/refreshable.go b/pkg/util/refreshable/refreshable.go index d821963bb..883773370 100644 --- a/pkg/util/refreshable/refreshable.go +++ b/pkg/util/refreshable/refreshable.go @@ -21,7 +21,7 @@ type authorizer struct { } func (a *authorizer) Rebuild() error { - auth, err := a.env.FPAuthorizer(a.tenantID, a.env.Environment().ResourceManagerScope) + auth, err := a.env.FPAuthorizer(a.tenantID, nil, a.env.Environment().ResourceManagerScope) if err != nil { return err } diff --git a/pkg/validate/openshiftcluster_validatedynamic.go b/pkg/validate/openshiftcluster_validatedynamic.go index 9dec04ff9..b6c97298e 100644 --- a/pkg/validate/openshiftcluster_validatedynamic.go +++ b/pkg/validate/openshiftcluster_validatedynamic.go @@ -125,7 +125,7 @@ func (dv *openShiftClusterDynamicValidator) Dynamic(ctx context.Context) error { } tenantID := dv.subscriptionDoc.Subscription.Properties.TenantID - fpClientCred, err := dv.env.FPNewClientCertificateCredential(tenantID) + fpClientCred, err := dv.env.FPNewClientCertificateCredential(tenantID, nil) if err != nil { return err } diff --git a/vendor/github.com/Azure/msi-dataplane/pkg/dataplane/identity.go b/vendor/github.com/Azure/msi-dataplane/pkg/dataplane/identity.go index ae9351198..3c57dec01 100644 --- a/vendor/github.com/Azure/msi-dataplane/pkg/dataplane/identity.go +++ b/vendor/github.com/Azure/msi-dataplane/pkg/dataplane/identity.go @@ -4,10 +4,10 @@ import ( "encoding/base64" "errors" "fmt" - "reflect" "strings" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" azcloud "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/msi-dataplane/pkg/dataplane/swagger" @@ -17,6 +17,7 @@ var ( // Errors returned when processing idenities errDecodeClientSecret = errors.New("failed to decode client secret") errParseCertificate = errors.New("failed to parse certificate") + errParseResourceID = errors.New("failed to parse resource ID") errNilField = errors.New("expected non nil field in identity") errNoUserAssignedMSIs = errors.New("credentials object does not contain user-assigned managed identities") errResourceIDNotFound = errors.New("resource ID not found in user-assigned managed identity") @@ -49,10 +50,20 @@ func (c CredentialsObject) IsUserAssigned() bool { // Get an AzIdentity credential for the given user-assigned identity resource ID // Clients can use the credential to get a token for the user-assigned identity -func (u UserAssignedIdentities) GetCredential(resourceID string) (*azidentity.ClientCertificateCredential, error) { +func (u UserAssignedIdentities) GetCredential(requestedResourceID string) (*azidentity.ClientCertificateCredential, error) { + requestedARMResourceID, err := arm.ParseResourceID(requestedResourceID) + if err != nil { + return nil, fmt.Errorf("%w for requested resource ID %s: %w", errParseResourceID, requestedResourceID, err) + } + requestedResourceID = requestedARMResourceID.String() + for _, id := range u.ExplicitIdentities { if id != nil && id.ResourceID != nil { - if *id.ResourceID == resourceID { + idARMResourceID, err := arm.ParseResourceID(*id.ResourceID) + if err != nil { + return nil, fmt.Errorf("%w for identity resource ID %s: %w", errParseResourceID, *id.ResourceID, err) + } + if requestedResourceID == idARMResourceID.String() { return getClientCertificateCredential(*id, u.cloud) } } @@ -61,6 +72,15 @@ func (u UserAssignedIdentities) GetCredential(resourceID string) (*azidentity.Cl return nil, errResourceIDNotFound } +func getAzCoreCloud(cloud string) azcloud.Configuration { + switch cloud { + case AzureUSGovCloud: + return azcloud.AzureGovernment + default: + return azcloud.AzurePublic + } +} + func getClientCertificateCredential(identity swagger.NestedCredentialsObject, cloud string) (*azidentity.ClientCertificateCredential, error) { // Double check nil pointers so we don't panic fieldsToCheck := map[string]*string{ @@ -86,6 +106,10 @@ func getClientCertificateCredential(identity swagger.NestedCredentialsObject, cl // x5c header required: https://eng.ms/docs/products/arm/rbac/managed_identities/msionboardingrequestingatoken SendCertificateChain: true, + + // Disable instance discovery because MSI credential may have regional AAD endpoint that instance discovery endpoint doesn't support + // e.g. when MSI credential has westus2.login.microsoft.com, it will cause instance discovery to fail with HTTP 400 + DisableInstanceDiscovery: true, } // Set the regional AAD endpoint @@ -116,30 +140,26 @@ func validateUserAssignedMSIs(identities []*swagger.NestedCredentialsObject, res if identity == nil { return errNilMSI } - - v := reflect.ValueOf(*identity) - for i := 0; i < v.NumField(); i++ { - if v.Field(i).IsNil() { - return fmt.Errorf("%w, field %s", errNilField, v.Type().Field(i).Name) - } + if identity.ResourceID == nil { + return fmt.Errorf("%w, resource ID", errNilField) } - resourceIDMap[*identity.ResourceID] = true + armResourceID, err := arm.ParseResourceID(*identity.ResourceID) + if err != nil { + return fmt.Errorf("%w for received resource ID %s: %w", errParseResourceID, *identity.ResourceID, err) + } + + resourceIDMap[armResourceID.String()] = true } for _, resourceID := range resourceIDs { - if _, ok := resourceIDMap[resourceID]; !ok { + armResourceID, err := arm.ParseResourceID(resourceID) + if err != nil { + return fmt.Errorf("%w for requested resource ID %s: %w", errParseResourceID, resourceID, err) + } + if _, ok := resourceIDMap[armResourceID.String()]; !ok { return fmt.Errorf("%w, resource ID %s", errResourceIDNotFound, resourceID) } } return nil } - -func getAzCoreCloud(cloud string) azcloud.Configuration { - switch cloud { - case AzureUSGovCloud: - return azcloud.AzureGovernment - default: - return azcloud.AzurePublic - } -} diff --git a/vendor/github.com/Azure/msi-dataplane/pkg/store/kvclient.go b/vendor/github.com/Azure/msi-dataplane/pkg/store/kvclient.go index 292acff33..5fbe61273 100644 --- a/vendor/github.com/Azure/msi-dataplane/pkg/store/kvclient.go +++ b/vendor/github.com/Azure/msi-dataplane/pkg/store/kvclient.go @@ -11,6 +11,7 @@ import ( type KeyVaultClient interface { DeleteSecret(ctx context.Context, name string, options *azsecrets.DeleteSecretOptions) (azsecrets.DeleteSecretResponse, error) + GetDeletedSecret(ctx context.Context, name string, options *azsecrets.GetDeletedSecretOptions) (azsecrets.GetDeletedSecretResponse, error) GetSecret(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) NewListDeletedSecretPropertiesPager(options *azsecrets.ListDeletedSecretPropertiesOptions) *runtime.Pager[azsecrets.ListDeletedSecretPropertiesResponse] NewListSecretPropertiesPager(options *azsecrets.ListSecretPropertiesOptions) *runtime.Pager[azsecrets.ListSecretPropertiesResponse] diff --git a/vendor/github.com/Azure/msi-dataplane/pkg/store/mock_kvclient/zz_generated_mocks.go b/vendor/github.com/Azure/msi-dataplane/pkg/store/mock_kvclient/zz_generated_mocks.go index 227384718..8e3cac24d 100644 --- a/vendor/github.com/Azure/msi-dataplane/pkg/store/mock_kvclient/zz_generated_mocks.go +++ b/vendor/github.com/Azure/msi-dataplane/pkg/store/mock_kvclient/zz_generated_mocks.go @@ -56,6 +56,21 @@ func (mr *MockKeyVaultClientMockRecorder) DeleteSecret(ctx, name, options any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSecret", reflect.TypeOf((*MockKeyVaultClient)(nil).DeleteSecret), ctx, name, options) } +// GetDeletedSecret mocks base method. +func (m *MockKeyVaultClient) GetDeletedSecret(ctx context.Context, name string, options *azsecrets.GetDeletedSecretOptions) (azsecrets.GetDeletedSecretResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDeletedSecret", ctx, name, options) + ret0, _ := ret[0].(azsecrets.GetDeletedSecretResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDeletedSecret indicates an expected call of GetDeletedSecret. +func (mr *MockKeyVaultClientMockRecorder) GetDeletedSecret(ctx, name, options any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDeletedSecret", reflect.TypeOf((*MockKeyVaultClient)(nil).GetDeletedSecret), ctx, name, options) +} + // GetSecret mocks base method. func (m *MockKeyVaultClient) GetSecret(ctx context.Context, name, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) { m.ctrl.T.Helper() diff --git a/vendor/github.com/Azure/msi-dataplane/pkg/store/store.go b/vendor/github.com/Azure/msi-dataplane/pkg/store/store.go index 7f1a31cca..abd11580a 100644 --- a/vendor/github.com/Azure/msi-dataplane/pkg/store/store.go +++ b/vendor/github.com/Azure/msi-dataplane/pkg/store/store.go @@ -14,6 +14,17 @@ var ( errNilSecretValue = errors.New("secret value is nil") ) +type DeletedSecretProperties struct { + Name string + RecoveryLevel string + DeletedDate time.Time +} + +type DeletedSecretResponse struct { + CredentialsObject dataplane.CredentialsObject + Properties DeletedSecretProperties +} + type MsiKeyVaultStore struct { kvClient KeyVaultClient } @@ -85,6 +96,42 @@ func (s *MsiKeyVaultStore) GetCredentialsObject(ctx context.Context, secretName return &SecretResponse{CredentialsObject: credentialsObject, Properties: secretProperties}, nil } +// Get a deleted credentials object from the key vault using the specified secret name. +func (s *MsiKeyVaultStore) GetDeletedCredentialsObject(ctx context.Context, secretName string) (*DeletedSecretResponse, error) { + response, err := s.kvClient.GetDeletedSecret(ctx, secretName, nil) + if err != nil { + return nil, err + } + + if response.Value == nil { + return nil, errNilSecretValue + } + + var credentialsObject dataplane.CredentialsObject + if err := credentialsObject.UnmarshalJSON([]byte(*response.Value)); err != nil { + return nil, err + } + + deletedSecretProperties := DeletedSecretProperties{ + Name: secretName, + RecoveryLevel: "", + DeletedDate: time.Time{}, + } + + if response.DeletedDate != nil { + deletedSecretProperties.DeletedDate = *response.DeletedDate + } + + if response.Attributes != nil { + // Override defaults if values are present + if response.Attributes.RecoveryLevel != nil { + deletedSecretProperties.RecoveryLevel = *response.Attributes.RecoveryLevel + } + } + + return &DeletedSecretResponse{CredentialsObject: credentialsObject, Properties: deletedSecretProperties}, nil +} + // Get a pager for listing credentials objects from the key vault. func (s *MsiKeyVaultStore) GetCredentialsObjectPager() *runtime.Pager[azsecrets.ListSecretPropertiesResponse] { return s.kvClient.NewListSecretPropertiesPager(nil) diff --git a/vendor/modules.txt b/vendor/modules.txt index a10ba8494..680f83cfc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -139,7 +139,7 @@ github.com/Azure/go-autorest/logger # github.com/Azure/go-autorest/tracing v0.6.0 ## explicit; go 1.12 github.com/Azure/go-autorest/tracing -# github.com/Azure/msi-dataplane v0.0.6 +# github.com/Azure/msi-dataplane v0.0.8 ## explicit; go 1.21 github.com/Azure/msi-dataplane/pkg/dataplane github.com/Azure/msi-dataplane/pkg/dataplane/swagger