diff --git a/cloud/fallback-public-cloud.yaml b/cloud/fallback-public-cloud.yaml index d4cd2523f43..0dcd39055d7 100644 --- a/cloud/fallback-public-cloud.yaml +++ b/cloud/fallback-public-cloud.yaml @@ -133,6 +133,8 @@ clouds: southamerica-east1: endpoint: https://www.googleapis.com azure: + # Note: the storage endpoint definitions below are no longer used by + # recent Juju versions, and are retained for compatibility only. type: azure description: Microsoft Azure auth-types: [ interactive, service-principal-secret ] @@ -140,175 +142,175 @@ clouds: centralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com eastus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com eastus2: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com northcentralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southcentralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westcentralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westus2: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westus3: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com northeurope: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westeurope: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com eastasia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southeastasia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com japaneast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com japanwest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com brazilsouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiacentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiacentral2: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiaeast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiasoutheast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com centralindia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southindia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westindia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com canadacentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com canadaeast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com uksouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com ukwest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com koreacentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com koreasouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com francecentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com francesouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southafricanorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southafricawest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com germanynorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com germanywestcentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com uaecentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com uaenorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com norwayeast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com norwaywest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com switzerlandnorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com switzerlandwest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com swedencentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com qatarcentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com azure-china: type: azure description: Microsoft Azure China diff --git a/cloud/fallback_public_cloud.go b/cloud/fallback_public_cloud.go index 42b43f4806e..f35bd3c7402 100644 --- a/cloud/fallback_public_cloud.go +++ b/cloud/fallback_public_cloud.go @@ -140,6 +140,8 @@ clouds: southamerica-east1: endpoint: https://www.googleapis.com azure: + # Note: the storage endpoint definitions below are no longer used by + # recent Juju versions, and are retained for compatibility only. type: azure description: Microsoft Azure auth-types: [ interactive, service-principal-secret ] @@ -147,175 +149,175 @@ clouds: centralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com eastus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com eastus2: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com northcentralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southcentralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westcentralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westus2: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westus3: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com northeurope: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westeurope: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com eastasia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southeastasia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com japaneast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com japanwest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com brazilsouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiacentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiacentral2: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiaeast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiasoutheast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com centralindia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southindia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westindia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com canadacentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com canadaeast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com uksouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com ukwest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com koreacentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com koreasouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com francecentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com francesouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southafricanorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southafricawest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com germanynorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com germanywestcentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com uaecentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com uaenorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com norwayeast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com norwaywest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com switzerlandnorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com switzerlandwest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com swedencentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com qatarcentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com azure-china: type: azure description: Microsoft Azure China diff --git a/cmd/juju/cloud/addcredential.go b/cmd/juju/cloud/addcredential.go index 43efab750f6..5610abe043c 100644 --- a/cmd/juju/cloud/addcredential.go +++ b/cmd/juju/cloud/addcredential.go @@ -434,6 +434,7 @@ func finalizeProvider(ctxt *cmd.Context, cloud *jujucloud.Cloud, regionName, def newCredential, err := credentialsProvider.FinalizeCredential( ctxt, environs.FinalizeCredentialParams{ Credential: jujucloud.NewCredential(authType, attrs), + CloudName: cloud.Name, CloudEndpoint: cloudEndpoint, CloudStorageEndpoint: cloudStorageEndpoint, CloudIdentityEndpoint: cloudIdentityEndpoint, diff --git a/cmd/juju/common/cloudcredential.go b/cmd/juju/common/cloudcredential.go index 3738f23bb68..0f2cd36a12e 100644 --- a/cmd/juju/common/cloudcredential.go +++ b/cmd/juju/common/cloudcredential.go @@ -120,6 +120,7 @@ func GetOrDetectCredential( credential, err = provider.FinalizeCredential( ctx, environs.FinalizeCredentialParams{ Credential: *credential, + CloudName: args.Cloud.Name, CloudEndpoint: region.Endpoint, CloudStorageEndpoint: region.StorageEndpoint, CloudIdentityEndpoint: region.IdentityEndpoint, diff --git a/cmd/modelcmd/base.go b/cmd/modelcmd/base.go index cd7b4b5a731..98ad995b96c 100644 --- a/cmd/modelcmd/base.go +++ b/cmd/modelcmd/base.go @@ -695,6 +695,7 @@ func (g bootstrapConfigGetter) getBootstrapConfigParams(controllerName string) ( credential, err = provider.FinalizeCredential( g.ctx, environs.FinalizeCredentialParams{ Credential: *credential, + CloudName: bootstrapConfig.Cloud, CloudEndpoint: bootstrapConfig.CloudEndpoint, CloudStorageEndpoint: bootstrapConfig.CloudStorageEndpoint, CloudIdentityEndpoint: bootstrapConfig.CloudIdentityEndpoint, diff --git a/cmd/modelcmd/credentials.go b/cmd/modelcmd/credentials.go index 4222904d114..f7998e1d36c 100644 --- a/cmd/modelcmd/credentials.go +++ b/cmd/modelcmd/credentials.go @@ -94,6 +94,7 @@ func VerifyCredentials(ctx *cmd.Context, aCloud *cloud.Cloud, credential *cloud. credential, err = provider.FinalizeCredential( ctx, environs.FinalizeCredentialParams{ Credential: *credential, + CloudName: aCloud.Name, CloudEndpoint: cloudEndpoint, CloudStorageEndpoint: cloudStorageEndpoint, CloudIdentityEndpoint: cloudIdentityEndpoint, diff --git a/environs/interface.go b/environs/interface.go index e9b8615d03b..56bee888048 100644 --- a/environs/interface.go +++ b/environs/interface.go @@ -191,6 +191,9 @@ type FinalizeCredentialParams struct { // Credential is the credential that the provider should finalize. Credential cloud.Credential + // CloudName is the name of the cloud that the credentials are for. + CloudName string + // CloudEndpoint is the endpoint for the cloud that the credentials are // for. This may be used by the provider to communicate with the cloud // to finalize the credentials. diff --git a/go.mod b/go.mod index 111fffc91a4..bd952375253 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,15 @@ module github.com/juju/juju go 1.20 require ( - github.com/Azure/azure-sdk-for-go v65.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 - github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3 v3.0.0-beta.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2 v2.0.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.0.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.0.0 - github.com/Azure/go-autorest/autorest v0.11.18 - github.com/Azure/go-autorest/autorest/adal v0.9.13 - github.com/Azure/go-autorest/autorest/date v0.3.0 - github.com/Azure/go-autorest/autorest/mocks v0.4.1 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.2.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.2.0 + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 github.com/EvilSuperstars/go-cidrman v0.0.0-20170211231153-4e5a4a63d9b7 github.com/armon/go-metrics v0.4.0 github.com/aws/aws-sdk-go-v2 v1.9.1 @@ -33,10 +29,11 @@ require ( github.com/docker/distribution v2.8.2+incompatible github.com/dustin/go-humanize v1.0.1 github.com/go-goose/goose/v5 v5.0.0-20220707165353-781664254fe4 - github.com/go-logr/logr v1.2.2 + github.com/go-logr/logr v1.2.4 github.com/go-macaroon-bakery/macaroon-bakery/v3 v3.0.1 github.com/gofrs/uuid v4.2.0+incompatible github.com/google/go-querystring v1.1.0 + github.com/google/uuid v1.3.0 github.com/googleapis/gnostic v0.5.5 github.com/gorilla/schema v1.2.0 github.com/gorilla/websocket v1.5.0 @@ -92,6 +89,8 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kr/pretty v0.3.1 github.com/mattn/go-isatty v0.0.19 + github.com/microsoft/kiota-abstractions-go v1.2.0 + github.com/microsoftgraph/msgraph-sdk-go v1.14.0 github.com/mitchellh/go-linereader v0.0.0-20190213213312-1b945b3263eb github.com/mitchellh/mapstructure v1.5.0 github.com/mittwald/vaultgo v0.1.1 @@ -132,15 +131,10 @@ require ( require ( cloud.google.com/go/compute v1.20.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect - github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect - github.com/Azure/go-autorest/logger v0.2.1 // indirect - github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/adrg/xdg v0.3.3 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.1.1 // indirect @@ -153,28 +147,27 @@ require ( github.com/canonical/x-go v0.0.0-20230522092633-7947a7587f5b // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cjlapao/common-go v0.0.39 // indirect github.com/creack/pty v1.1.15 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dnaeon/go-vcr v1.2.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/fatih/color v1.13.0 // indirect github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 // indirect - github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gdamore/encoding v1.0.0 // indirect github.com/gdamore/tcell/v2 v2.5.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-macaroon-bakery/macaroonpb v1.0.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/godbus/dbus/v5 v5.0.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt v3.2.1+incompatible // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/s2a-go v0.1.4 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.11.0 // indirect github.com/gorilla/mux v1.8.0 // indirect @@ -222,6 +215,12 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/microsoft/kiota-authentication-azure-go v1.0.0 // indirect + github.com/microsoft/kiota-http-go v1.0.1 // indirect + github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect + github.com/microsoft/kiota-serialization-json-go v1.0.4 // indirect + github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect + github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect @@ -238,10 +237,11 @@ require ( github.com/pborman/uuid v1.2.1 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect - github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/term v1.1.0 // indirect github.com/pkg/xattr v0.4.9 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.11.0 // indirect github.com/rivo/tview v0.0.0-20220610163003-691f46d6f500 // indirect @@ -257,13 +257,18 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.16.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/zitadel/oidc/v2 v2.6.4 // indirect go.etcd.io/bbolt v1.3.5 // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/otel v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect + go.opentelemetry.io/otel/trace v1.16.0 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/term v0.11.0 // indirect diff --git a/go.sum b/go.sum index 95d540b71db..29e2ec64ce1 100644 --- a/go.sum +++ b/go.sum @@ -45,62 +45,53 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible h1:KnPIugL51v3N3WwvaSmZbxukD1WuWXOiE9fRdu32f2I= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= -github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0 h1:Ut0ZGdOwJDw0npYEg+TLlPls3Pq6JiZaP2/aGKir7Zw= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 h1:jp0dGvZ7ZK0mgqnTSClMxa5xuRL7NZgHameVYF6BurY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1 h1:AXFNQ6kLaPODEpGSMWjmbkt6iP7fa1DIEzjx6JRFC9U= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1/go.mod h1:yOYJv0tO0TTNcje8ahhBHQcdAiYqRIp5fsog5FPefr4= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 h1:9cn6ICCGiWFNA/slKnrkf+ENyvaCRKHtuoGtnLIAgao= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 h1:/iHxaJhsFr0+xVFfbMr5vxz848jyiWuIEDhYq3y5odY= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3 v3.0.0-beta.1 h1:kRt6idL93W/nYRkUPbZ81yxJeLFevvrLYkyJEVzLpYM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3 v3.0.0-beta.1/go.mod h1:nPsyC5G3IY+ljp+OHp8w/xa9UuLWe7ehFADNkqCSTaw= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0 h1:/Di3vB4sNeQ+7A8efjUVENvyB945Wruvstucqp7ZArg= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2 v2.0.0 h1:xxe4naFUPYEW1W6C8yWrfFNmyZLnEbO+CsbsSF83wDo= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2 v2.0.0/go.mod h1:aLFjumYDvv63tH1qnqkcmdjdZ6Sn+/viPv7H3jft0oY= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.0.0 h1:lMW1lD/17LUA5z1XTURo7LcVG2ICBPlyMHjIUrcFZNQ= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.0.0 h1:Jc2KcpCDMu7wJfkrzn7fs/53QMDXH78GuqnH4HOd7zs= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.0.0/go.mod h1:PFVgFsclKzPqYRT/BiwpfUN22cab0C7FlgXR3iWpwMo= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0 h1:nBy98uKOIfun5z6wx6jwWLrULcM0+cjBalBFZlEZ7CA= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0/go.mod h1:243D9iHbcQXoFUtgHJwL7gl2zx1aDuDMjvBZVGr2uW0= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.0.0 h1:xXmHA6JxGDHOY2anNQhpgIibZOiEaOvPLZOiAs07/4k= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.0.0/go.mod h1:qkZjuhvy20x2Ckq4BzopZ8UjZLhib6nRJbRQiC6EFXY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.2.0 h1:8d4U82r7ItT1Es91x3eUcAQweih36KWvUha8AZ9X0Rs= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.2.0/go.mod h1:/1bkGperHinQbAHMWivoec/Ucu6//iXo6jn5mhmqCVU= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0 h1:QM6sE5k2ZT/vI5BEe0r7mqjsUSnhVBFbOsVkEuaEfiA= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0/go.mod h1:243D9iHbcQXoFUtgHJwL7gl2zx1aDuDMjvBZVGr2uW0= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.2.0 h1:Pmy0+3ox1IC3sp6musv87BFPIdQbqyPFjn7I8I0o2Js= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.2.0/go.mod h1:ThfyMjs6auYrWPnYJjI3H4H++oVPrz01pizpu8lfl3A= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 h1:yfJe15aSwEQ6Oo6J+gdfdulPNoZ3TEhmbhLIoxZcA+U= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -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.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= -github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= -github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= 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/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7KY7zH2mqygeUD0t8hNFXe08p1Pb3/jKE= -github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -229,6 +220,8 @@ github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLI github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= +github.com/cjlapao/common-go v0.0.39/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -342,7 +335,6 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= @@ -388,7 +380,6 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 h1:fmFk0Wt3bBxxwZnu48jqMdaOR/IZ4vdtJFuaFV8MpIE= github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3/go.mod h1:bJWSKrZyQvfTnb2OudyUjurSG4/edverV7n82+K3JiM= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k= github.com/frankban/quicktest v1.1.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k= @@ -431,8 +422,11 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-macaroon-bakery/macaroon-bakery/v3 v3.0.1 h1:uvQJoKTHrFFu8zxoaopNKedRzwdy3+8H72we4T/5cGs= github.com/go-macaroon-bakery/macaroon-bakery/v3 v3.0.1/go.mod h1:H59IYeChwvD1po3dhGUPvq5na+4NVD7SJlbhGKvslr0= github.com/go-macaroon-bakery/macaroonpb v1.0.0 h1:It9exBaRMZ9iix1iJ6gwzfwsDE6ExNuwtAJ9e09v6XE= @@ -472,9 +466,8 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= -github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -971,6 +964,22 @@ github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vq github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/microsoft/kiota-abstractions-go v1.2.0 h1:lUriJgqdCY/QajwWQOgTCQE9Atywfe2NHhgoTCSXTRE= +github.com/microsoft/kiota-abstractions-go v1.2.0/go.mod h1:RkxyZ5x87Njik7iVeQY9M2wtrrL1MJZcXiI/BxD/82g= +github.com/microsoft/kiota-authentication-azure-go v1.0.0 h1:29FNZZ/4nnCOwFcGWlB/sxPvWz487HA2bXH8jR5k2Rk= +github.com/microsoft/kiota-authentication-azure-go v1.0.0/go.mod h1:rnx3PRlkGdXDcA/0lZQTbBwyYGmc+3POt7HpE/e4jGw= +github.com/microsoft/kiota-http-go v1.0.1 h1:818u3aiLpxj35hZgfUSqphQ18IUTK3gVdTE4cQ5vjLw= +github.com/microsoft/kiota-http-go v1.0.1/go.mod h1:H0cg+ly+5ZSR8z4swj5ea9O/GB5ll2YuYeQ0/pJs7AY= +github.com/microsoft/kiota-serialization-form-go v1.0.0 h1:UNdrkMnLFqUCccQZerKjblsyVgifS11b3WCx+eFEsAI= +github.com/microsoft/kiota-serialization-form-go v1.0.0/go.mod h1:h4mQOO6KVTNciMF6azi1J9QB19ujSw3ULKcSNyXXOMA= +github.com/microsoft/kiota-serialization-json-go v1.0.4 h1:5TaISWwd2Me8clrK7SqNATo0tv9seOq59y4I5953egQ= +github.com/microsoft/kiota-serialization-json-go v1.0.4/go.mod h1:rM4+FsAY+9AEpBsBzkFFis+b/LZLlNKKewuLwK9Q6Mg= +github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= +github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= +github.com/microsoftgraph/msgraph-sdk-go v1.14.0 h1:YdhMvzu8bXcfIQGRur6NkXnv4cPOsMBJ44XjfWLOt9Y= +github.com/microsoftgraph/msgraph-sdk-go v1.14.0/go.mod h1:ccLv84FJFtwdSzYWM/HlTes5FLzkzzBsYh9kg93/WS8= +github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0 h1:7NWTfyXvOjoizW7PmxNp3+8wCKPgpODs/D1cUZ3fkAY= +github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0/go.mod h1:tQb4q3YMIj2dWhhXhQSJ4ELpol931ANKzHSYK5kX1qE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= @@ -1018,10 +1027,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= @@ -1097,8 +1104,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= -github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1114,6 +1121,7 @@ github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -1245,6 +1253,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= @@ -1288,6 +1297,8 @@ github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yohcop/openid-go v1.0.0/go.mod h1:/408xiwkeItSPJZSTPF7+VtZxPkPrRRpRNK2vjGh6yI= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1315,6 +1326,12 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1575,6 +1592,7 @@ golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/provider/azure/config_test.go b/provider/azure/config_test.go index b0bd803d316..fa2f4b4885e 100644 --- a/provider/azure/config_test.go +++ b/provider/azure/config_test.go @@ -4,13 +4,13 @@ package azure_test import ( - "github.com/Azure/go-autorest/autorest/mocks" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" "github.com/juju/juju/provider/azure" + "github.com/juju/juju/provider/azure/internal/azuretesting" "github.com/juju/juju/testing" ) @@ -32,7 +32,7 @@ var _ = gc.Suite(&configSuite{}) func (s *configSuite) SetUpTest(c *gc.C) { s.BaseSuite.SetUpTest(c) s.provider = newProvider(c, azure.ProviderConfig{ - Sender: mocks.NewSender(), + Sender: &azuretesting.MockSender{}, }) } diff --git a/provider/azure/credentials.go b/provider/azure/credentials.go index f48c3019eb5..2209f85f014 100644 --- a/provider/azure/credentials.go +++ b/provider/azure/credentials.go @@ -7,11 +7,11 @@ import ( "context" "fmt" "io" - "strings" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/juju/errors" "github.com/juju/juju/cloud" @@ -46,8 +46,6 @@ type AzureCLI interface { ListAccounts() ([]azurecli.Account, error) FindAccountsWithCloudName(name string) ([]azurecli.Account, error) ShowAccount(subscription string) (*azurecli.Account, error) - GetAccessToken(tenant, resource string) (*azurecli.AccessToken, error) - FindCloudsWithResourceManagerEndpoint(url string) ([]azurecli.Cloud, error) ListClouds() ([]azurecli.Cloud, error) } @@ -135,7 +133,7 @@ func (c environProviderCredentials) DetectCredentials(cloudName string) (*cloud. if !ok { continue } - cred, err := c.accountCredential(acc, cloudInfo) + cred, err := c.accountCredential(acc) if err != nil { logger.Debugf("cannot get credential for %s: %s", acc.Name, err) continue @@ -164,16 +162,8 @@ func (c environProviderCredentials) FinalizeCredential( case deviceCodeAuthType: subscriptionId := args.Credential.Attributes()[credAttrSubscriptionId] if subscriptionId != "" { - // If a subscription ID was specified then fall - // back to the interactive device login. attempt - // to get subscription details from Azure CLI. - graphResourceId := azureauth.TokenResource(args.CloudIdentityEndpoint) - resourceManagerResourceId, err := azureauth.ResourceManagerResourceId(args.CloudStorageEndpoint) - if err != nil { - return nil, errors.Trace(err) - } opts := azcore.ClientOptions{ - Cloud: azureCloud(args.CloudEndpoint, args.CloudIdentityEndpoint), + Cloud: azureCloud(args.CloudName, args.CloudEndpoint, args.CloudIdentityEndpoint), Transport: c.transporter, } clientOpts := arm.ClientOptions{ClientOptions: opts} @@ -183,15 +173,22 @@ func (c environProviderCredentials) FinalizeCredential( return nil, errors.Trace(err) } return c.deviceCodeCredential(ctx, args, azureauth.ServicePrincipalParams{ - GraphEndpoint: args.CloudIdentityEndpoint, - GraphResourceId: graphResourceId, - ResourceManagerEndpoint: args.CloudEndpoint, - ResourceManagerResourceId: resourceManagerResourceId, - SubscriptionId: subscriptionId, - TenantId: tenantID, + SubscriptionId: subscriptionId, + TenantId: tenantID, }) } - params, err := c.getServicePrincipalParams(args.CloudEndpoint) + var azCloudName string + switch args.CloudName { + case "azure": + azCloudName = "AzureCloud" + case "azure-china": + azCloudName = "AzureChinaCloud" + case "azure-gov": + azCloudName = "AzureUSGovernment" + default: + return nil, errors.Errorf("unknown Azure cloud name %q", args.CloudName) + } + params, err := c.getServicePrincipalParams(azCloudName) if err != nil { return nil, errors.Trace(err) } @@ -228,26 +225,22 @@ func (c environProviderCredentials) azureCLICredential( args environs.FinalizeCredentialParams, params azureauth.ServicePrincipalParams, ) (*cloud.Credential, error) { - graphToken, err := c.azureCLI.GetAccessToken(params.TenantId, params.GraphResourceId) + cred, err := azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{ + TenantID: params.TenantId, + }) if err != nil { - // The version of Azure CLI may not support - // get-access-token so fallback to using device - // authentication. - logger.Debugf("error getting access token: %s", err) - return c.deviceCodeCredential(ctx, args, params) + return nil, errors.Trace(err) } - params.GraphTokenProvider = graphToken.Token() + params.Credential = cred - resourceManagerAuthorizer, err := c.azureCLI.GetAccessToken(params.TenantId, params.ResourceManagerResourceId) if err != nil { return nil, errors.Annotatef(err, "cannot get access token for %s", params.SubscriptionId) } - params.ResourceManagerTokenProvider = resourceManagerAuthorizer.Token() sdkCtx := context.Background() applicationId, spObjectId, password, err := c.servicePrincipalCreator.Create(sdkCtx, params) if err != nil { - return nil, errors.Annotatef(err, "cannot get service principal") + return nil, errors.Annotatef(err, "cannot create service principal") } out := cloud.NewCredential(clientCredentialsAuthType, map[string]string{ credAttrSubscriptionId: params.SubscriptionId, @@ -261,26 +254,19 @@ func (c environProviderCredentials) azureCLICredential( func (c environProviderCredentials) accountCredential( acc azurecli.Account, - cloudInfo azurecli.Cloud, ) (cloud.Credential, error) { - graphToken, err := c.azureCLI.GetAccessToken(acc.AuthTenantId(), cloudInfo.Endpoints.ActiveDirectoryGraphResourceID) - if err != nil { - return cloud.Credential{}, errors.Annotatef(err, "cannot get access token for %s", acc.ID) - } - armToken, err := c.azureCLI.GetAccessToken(acc.AuthTenantId(), cloudInfo.Endpoints.ResourceManager) + cred, err := azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{ + TenantID: acc.AuthTenantId(), + AdditionallyAllowedTenants: []string{"*"}, + }) if err != nil { - return cloud.Credential{}, errors.Annotatef(err, "cannot get access token for %s", acc.ID) + return cloud.Credential{}, errors.Annotate(err, "cannot get az cli credential") } sdkCtx := context.Background() applicationId, spObjectId, password, err := c.servicePrincipalCreator.Create(sdkCtx, azureauth.ServicePrincipalParams{ - GraphEndpoint: cloudInfo.Endpoints.ActiveDirectoryGraphResourceID, - GraphResourceId: cloudInfo.Endpoints.ActiveDirectoryGraphResourceID, - GraphTokenProvider: graphToken.Token(), - ResourceManagerEndpoint: cloudInfo.Endpoints.ResourceManager, - ResourceManagerResourceId: cloudInfo.Endpoints.ResourceManager, - ResourceManagerTokenProvider: armToken.Token(), - SubscriptionId: acc.ID, - TenantId: graphToken.Tenant, + Credential: cred, + SubscriptionId: acc.ID, + TenantId: acc.TenantId, }) if err != nil { return cloud.Credential{}, errors.Annotate(err, "cannot get service principal") @@ -294,23 +280,13 @@ func (c environProviderCredentials) accountCredential( }), nil } -func (c environProviderCredentials) getServicePrincipalParams(cloudEndpoint string) (azureauth.ServicePrincipalParams, error) { - if !strings.HasSuffix(cloudEndpoint, "/") { - cloudEndpoint += "/" - } - clouds, err := c.azureCLI.FindCloudsWithResourceManagerEndpoint(cloudEndpoint) - if err != nil { - return azureauth.ServicePrincipalParams{}, errors.Annotatef(err, "cannot list clouds") - } - if len(clouds) != 1 { - return azureauth.ServicePrincipalParams{}, errors.Errorf("cannot find cloud for %s", cloudEndpoint) - } - accounts, err := c.azureCLI.FindAccountsWithCloudName(clouds[0].Name) +func (c environProviderCredentials) getServicePrincipalParams(cloudName string) (azureauth.ServicePrincipalParams, error) { + accounts, err := c.azureCLI.FindAccountsWithCloudName(cloudName) if err != nil { return azureauth.ServicePrincipalParams{}, errors.Annotatef(err, "cannot get accounts") } if len(accounts) < 1 { - return azureauth.ServicePrincipalParams{}, errors.Errorf("no %s accounts found", clouds[0].Name) + return azureauth.ServicePrincipalParams{}, errors.Errorf("no %s accounts found", cloudName) } acc := accounts[0] for _, a := range accounts[1:] { @@ -319,12 +295,8 @@ func (c environProviderCredentials) getServicePrincipalParams(cloudEndpoint stri } } return azureauth.ServicePrincipalParams{ - GraphEndpoint: clouds[0].Endpoints.ActiveDirectoryGraphResourceID, - GraphResourceId: clouds[0].Endpoints.ActiveDirectoryGraphResourceID, - ResourceManagerEndpoint: clouds[0].Endpoints.ResourceManager, - ResourceManagerResourceId: clouds[0].Endpoints.ResourceManager, - SubscriptionId: acc.ID, - TenantId: acc.AuthTenantId(), + SubscriptionId: acc.ID, + TenantId: acc.AuthTenantId(), }, nil } diff --git a/provider/azure/credentials_test.go b/provider/azure/credentials_test.go index 48f1f2acb87..8d708d88bae 100644 --- a/provider/azure/credentials_test.go +++ b/provider/azure/credentials_test.go @@ -5,7 +5,6 @@ package azure_test import ( "context" - "fmt" "io" "github.com/juju/cmd/v3/cmdtesting" @@ -78,15 +77,6 @@ func (s *credentialsSuite) TestDetectCredentialsListError(c *gc.C) { c.Assert(err, jc.Satisfies, errors.IsNotFound) } -const ( - expectedToken = "tenant-id|https://graph.invalid/|access-token" - expectedToken2 = "tenant-id2|https://graph.invalid/|access-token" - expectedHomeToken = "home-tenant-id|https://graph.invalid/|access-token" - expectedArmToken = "tenant-id|https://arm.invalid/|access-token" - expectedArmToken2 = "tenant-id2|https://arm.invalid/|access-token" - expectedHomeArmToken = "home-tenant-id|https://arm.invalid/|access-token" -) - func (s *credentialsSuite) TestDetectCredentialsOneAccount(c *gc.C) { s.azureCLI.Accounts = []azurecli.Account{{ CloudName: "AzureCloud", @@ -98,10 +88,6 @@ func (s *credentialsSuite) TestDetectCredentialsOneAccount(c *gc.C) { HomeTenantId: "home-tenant-id", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} @@ -114,30 +100,19 @@ func (s *credentialsSuite) TestDetectCredentialsOneAccount(c *gc.C) { c.Assert(cred.AuthCredentials["test-account"].Label, gc.Equals, "AzureCloud subscription test-account") calls := s.azureCLI.Calls() - c.Assert(calls, gc.HasLen, 4) + c.Assert(calls, gc.HasLen, 2) c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") - c.Assert(calls[2].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{"home-tenant-id", "https://graph.invalid/"}) - c.Assert(calls[3].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[3].Args, jc.DeepEquals, []interface{}{"home-tenant-id", "https://arm.invalid/"}) calls = s.servicePrincipalCreator.Calls() c.Assert(calls, gc.HasLen, 1) c.Assert(calls[0].FuncName, gc.Equals, "Create") params, ok := calls[0].Args[1].(azureauth.ServicePrincipalParams) c.Assert(ok, jc.IsTrue) - c.Assert(params.GraphTokenProvider.OAuthToken(), gc.Equals, expectedHomeToken) - c.Assert(params.ResourceManagerTokenProvider.OAuthToken(), gc.Equals, expectedHomeArmToken) - params.GraphTokenProvider = nil - params.ResourceManagerTokenProvider = nil + params.Credential = nil c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid/", - GraphResourceId: "https://graph.invalid/", - ResourceManagerEndpoint: "https://arm.invalid/", - ResourceManagerResourceId: "https://arm.invalid/", - SubscriptionId: "test-account-id", - TenantId: "tenant-id", + SubscriptionId: "test-account-id", + TenantId: "tenant-id", }) } @@ -151,10 +126,6 @@ func (s *credentialsSuite) TestDetectCredentialsCloudError(c *gc.C) { TenantId: "tenant-id", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid", - ResourceManager: "https://arm.invalid", - }, IsActive: true, Name: "AzureCloud", }} @@ -189,10 +160,6 @@ func (s *credentialsSuite) TestDetectCredentialsTwoAccounts(c *gc.C) { TenantId: "tenant-id2", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} @@ -206,49 +173,27 @@ func (s *credentialsSuite) TestDetectCredentialsTwoAccounts(c *gc.C) { c.Assert(cred.AuthCredentials["test-account2"].Label, gc.Equals, "AzureCloud subscription test-account2") calls := s.azureCLI.Calls() - c.Assert(calls, gc.HasLen, 6) + c.Assert(calls, gc.HasLen, 2) c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") - c.Assert(calls[2].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{"tenant-id", "https://graph.invalid/"}) - c.Assert(calls[3].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[3].Args, jc.DeepEquals, []interface{}{"tenant-id", "https://arm.invalid/"}) - c.Assert(calls[4].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[4].Args, jc.DeepEquals, []interface{}{"tenant-id2", "https://graph.invalid/"}) - c.Assert(calls[5].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[5].Args, jc.DeepEquals, []interface{}{"tenant-id2", "https://arm.invalid/"}) calls = s.servicePrincipalCreator.Calls() c.Assert(calls, gc.HasLen, 2) c.Assert(calls[0].FuncName, gc.Equals, "Create") params, ok := calls[0].Args[1].(azureauth.ServicePrincipalParams) c.Assert(ok, jc.IsTrue) - c.Assert(params.GraphTokenProvider.OAuthToken(), gc.Equals, expectedToken) - c.Assert(params.ResourceManagerTokenProvider.OAuthToken(), gc.Equals, expectedArmToken) - params.GraphTokenProvider = nil - params.ResourceManagerTokenProvider = nil + params.Credential = nil c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid/", - GraphResourceId: "https://graph.invalid/", - ResourceManagerEndpoint: "https://arm.invalid/", - ResourceManagerResourceId: "https://arm.invalid/", - SubscriptionId: "test-account1-id", - TenantId: "tenant-id", + SubscriptionId: "test-account1-id", + TenantId: "tenant-id", }) c.Assert(calls[1].FuncName, gc.Equals, "Create") params, ok = calls[1].Args[1].(azureauth.ServicePrincipalParams) c.Assert(ok, jc.IsTrue) - c.Assert(params.GraphTokenProvider.OAuthToken(), gc.Equals, expectedToken2) - c.Assert(params.ResourceManagerTokenProvider.OAuthToken(), gc.Equals, expectedArmToken2) - params.GraphTokenProvider = nil - params.ResourceManagerTokenProvider = nil + params.Credential = nil c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid/", - GraphResourceId: "https://graph.invalid/", - ResourceManagerEndpoint: "https://arm.invalid/", - ResourceManagerResourceId: "https://arm.invalid/", - SubscriptionId: "test-account2-id", - TenantId: "tenant-id2", + SubscriptionId: "test-account2-id", + TenantId: "tenant-id2", }) } @@ -269,14 +214,10 @@ func (s *credentialsSuite) TestDetectCredentialsTwoAccountsOneError(c *gc.C) { TenantId: "tenant-id2", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} - s.azureCLI.SetErrors(nil, nil, nil, nil, errors.New("test error")) + s.servicePrincipalCreator.SetErrors(nil, errors.New("test error")) cred, err := s.provider.DetectCredentials("") c.Assert(err, jc.ErrorIsNil) c.Assert(cred, gc.Not(gc.IsNil)) @@ -286,32 +227,27 @@ func (s *credentialsSuite) TestDetectCredentialsTwoAccountsOneError(c *gc.C) { c.Assert(cred.AuthCredentials["test-account1"].Label, gc.Equals, "AzureCloud subscription test-account1") calls := s.azureCLI.Calls() - c.Assert(calls, gc.HasLen, 5) + c.Assert(calls, gc.HasLen, 2) c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") - c.Assert(calls[2].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{"tenant-id", "https://graph.invalid/"}) - c.Assert(calls[3].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[3].Args, jc.DeepEquals, []interface{}{"tenant-id", "https://arm.invalid/"}) - c.Assert(calls[4].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[4].Args, jc.DeepEquals, []interface{}{"tenant-id2", "https://graph.invalid/"}) calls = s.servicePrincipalCreator.Calls() - c.Assert(calls, gc.HasLen, 1) + c.Assert(calls, gc.HasLen, 2) c.Assert(calls[0].FuncName, gc.Equals, "Create") params, ok := calls[0].Args[1].(azureauth.ServicePrincipalParams) c.Assert(ok, jc.IsTrue) - c.Assert(params.GraphTokenProvider.OAuthToken(), gc.Equals, expectedToken) - c.Assert(params.ResourceManagerTokenProvider.OAuthToken(), gc.Equals, expectedArmToken) - params.GraphTokenProvider = nil - params.ResourceManagerTokenProvider = nil + params.Credential = nil c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid/", - GraphResourceId: "https://graph.invalid/", - ResourceManagerEndpoint: "https://arm.invalid/", - ResourceManagerResourceId: "https://arm.invalid/", - SubscriptionId: "test-account1-id", - TenantId: "tenant-id", + SubscriptionId: "test-account1-id", + TenantId: "tenant-id", + }) + c.Assert(calls[1].FuncName, gc.Equals, "Create") + params, ok = calls[1].Args[1].(azureauth.ServicePrincipalParams) + c.Assert(ok, jc.IsTrue) + params.Credential = nil + c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ + SubscriptionId: "test-account2-id", + TenantId: "tenant-id2", }) } @@ -338,12 +274,8 @@ func (s *credentialsSuite) TestFinalizeCredentialInteractive(c *gc.C) { s.servicePrincipalCreator.CheckCallNames(c, "InteractiveCreate") args := s.servicePrincipalCreator.Calls()[0].Args c.Assert(args[2], jc.DeepEquals, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid", - GraphResourceId: "https://graph.invalid/", - ResourceManagerEndpoint: "https://arm.invalid", - ResourceManagerResourceId: "https://management.core.invalid/", - SubscriptionId: fakeSubscriptionId, - TenantId: fakeTenantId, + SubscriptionId: fakeSubscriptionId, + TenantId: fakeTenantId, }) } @@ -377,19 +309,14 @@ func (s *credentialsSuite) TestFinalizeCredentialAzureCLI(c *gc.C) { TenantId: "tenant-id", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} in := cloud.NewCredential("interactive", nil) ctx := cmdtesting.Context(c) cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ - Credential: in, - CloudEndpoint: "https://arm.invalid", - CloudIdentityEndpoint: "https://graph.invalid", + Credential: in, + CloudName: "azure", }) c.Assert(err, jc.ErrorIsNil) c.Assert(cred, gc.Not(gc.IsNil)) @@ -418,20 +345,15 @@ func (s *credentialsSuite) TestFinalizeCredentialAzureCLIShowAccountError(c *gc. TenantId: "tenant-id", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} - s.azureCLI.SetErrors(nil, errors.New("test error")) + s.azureCLI.SetErrors(errors.New("test error")) in := cloud.NewCredential("interactive", nil) ctx := cmdtesting.Context(c) cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ - Credential: in, - CloudEndpoint: "https://arm.invalid", - CloudIdentityEndpoint: "https://graph.invalid", + Credential: in, + CloudName: "azure", }) c.Assert(err, gc.ErrorMatches, `cannot get accounts: test error`) c.Assert(cred, gc.IsNil) @@ -454,58 +376,17 @@ func (s *credentialsSuite) TestFinalizeCredentialAzureCLIGraphTokenError(c *gc.C TenantId: "tenant-id", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} - s.azureCLI.SetErrors(nil, nil, nil, errors.New("test error")) - in := cloud.NewCredential("interactive", nil) - ctx := cmdtesting.Context(c) - cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ - Credential: in, - CloudEndpoint: "https://arm.invalid", - CloudIdentityEndpoint: "https://graph.invalid", - }) - c.Assert(err, gc.ErrorMatches, `cannot get access token for test-account1-id: test error`) - c.Assert(cred, gc.IsNil) -} - -func (s *credentialsSuite) TestFinalizeCredentialAzureCLIResourceManagerTokenError(c *gc.C) { - s.azureCLI.Accounts = []azurecli.Account{{ - CloudName: "AzureCloud", - ID: "test-account1-id", - IsDefault: true, - Name: "test-account1", - State: "Enabled", - TenantId: "tenant-id", - }, { - CloudName: "AzureCloud", - ID: "test-account2-id", - IsDefault: false, - Name: "test-account2", - State: "Enabled", - TenantId: "tenant-id", - }} - s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, - IsActive: true, - Name: "AzureCloud", - }} - s.azureCLI.SetErrors(nil, nil, nil, errors.New("test error")) + s.servicePrincipalCreator.SetErrors(errors.New("test error")) in := cloud.NewCredential("interactive", nil) ctx := cmdtesting.Context(c) cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ - Credential: in, - CloudEndpoint: "https://arm.invalid", - CloudIdentityEndpoint: "https://graph.invalid", + Credential: in, + CloudName: "azure", }) - c.Assert(err, gc.ErrorMatches, `cannot get access token for test-account1-id: test error`) + c.Assert(err, gc.ErrorMatches, `cannot create service principal: test error`) c.Assert(cred, gc.IsNil) } @@ -526,10 +407,6 @@ func (s *credentialsSuite) TestFinalizeCredentialAzureCLIServicePrincipalError(c TenantId: "tenant-id", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} @@ -537,57 +414,13 @@ func (s *credentialsSuite) TestFinalizeCredentialAzureCLIServicePrincipalError(c in := cloud.NewCredential("interactive", nil) ctx := cmdtesting.Context(c) cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ - Credential: in, - CloudEndpoint: "https://arm.invalid", - CloudIdentityEndpoint: "https://graph.invalid", + Credential: in, + CloudName: "azure", }) - c.Assert(err, gc.ErrorMatches, `cannot get service principal: test error`) + c.Assert(err, gc.ErrorMatches, `cannot create service principal: test error`) c.Assert(cred, gc.IsNil) } -func (s *credentialsSuite) TestFinalizeCredentialAzureCLIDeviceFallback(c *gc.C) { - s.azureCLI.Accounts = []azurecli.Account{{ - CloudName: "AzureCloud", - ID: "test-account1-id", - IsDefault: true, - Name: "test-account1", - State: "Enabled", - TenantId: "tenant-id", - }, { - CloudName: "AzureCloud", - ID: "test-account2-id", - IsDefault: false, - Name: "test-account2", - State: "Enabled", - TenantId: "tenant-id", - }} - s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, - IsActive: true, - Name: "AzureCloud", - }} - s.azureCLI.SetErrors(nil, nil, errors.New("test error")) - in := cloud.NewCredential("interactive", nil) - ctx := cmdtesting.Context(c) - cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ - Credential: in, - CloudEndpoint: "https://arm.invalid", - CloudIdentityEndpoint: "https://graph.invalid", - }) - c.Assert(err, jc.ErrorIsNil) - c.Assert(cred, gc.Not(gc.IsNil)) - c.Assert(cred.AuthType(), gc.Equals, cloud.AuthType("service-principal-secret")) - attrs := cred.Attributes() - c.Assert(attrs["subscription-id"], gc.Equals, "test-account1-id") - c.Assert(attrs["application-id"], gc.Equals, "appid") - c.Assert(attrs["application-password"], gc.Equals, "service-principal-password") - c.Assert(attrs["application-object-id"], gc.Equals, "application-object-id") - s.servicePrincipalCreator.CheckCallNames(c, "InteractiveCreate") -} - type servicePrincipalCreator struct { testing.Stub } @@ -650,22 +483,6 @@ func (e *azureCLI) findAccount(tenant string) (*azurecli.Account, error) { return nil, errors.New("account not found") } -func (e *azureCLI) GetAccessToken(tenant, resource string) (*azurecli.AccessToken, error) { - e.MethodCall(e, "GetAccessToken", tenant, resource) - if err := e.NextErr(); err != nil { - return nil, err - } - acc, err := e.findAccount(tenant) - if err != nil { - return nil, err - } - return &azurecli.AccessToken{ - AccessToken: fmt.Sprintf("%s|%s|access-token", tenant, resource), - Tenant: acc.TenantId, - TokenType: "Bearer", - }, nil -} - func (e *azureCLI) ShowCloud(name string) (*azurecli.Cloud, error) { e.MethodCall(e, "ShowCloud", name) if err := e.NextErr(); err != nil { @@ -679,19 +496,6 @@ func (e *azureCLI) ShowCloud(name string) (*azurecli.Cloud, error) { return nil, errors.New("cloud not found") } -func (e *azureCLI) FindCloudsWithResourceManagerEndpoint(url string) ([]azurecli.Cloud, error) { - e.MethodCall(e, "FindCloudsWithResourceManagerEndpoint", url) - if err := e.NextErr(); err != nil { - return nil, err - } - for _, cloud := range e.Clouds { - if cloud.Endpoints.ResourceManager == url { - return []azurecli.Cloud{cloud}, nil - } - } - return nil, errors.New("cloud not found") -} - func (e *azureCLI) ListClouds() ([]azurecli.Cloud, error) { e.MethodCall(e, "ListClouds") if err := e.NextErr(); err != nil { diff --git a/provider/azure/disk.go b/provider/azure/disk.go index 8a6ac26de7e..77c2e87c9cd 100644 --- a/provider/azure/disk.go +++ b/provider/azure/disk.go @@ -7,12 +7,11 @@ import ( stdcontext "context" "fmt" "strconv" - "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" - "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" "github.com/gofrs/uuid" "github.com/juju/errors" @@ -326,35 +325,31 @@ func (env *azureEnviron) createVaultKey( resp, err := keyClient.CreateKey( ctx, - vaultName, - azkeys.KeyTypeRSA, - &azkeys.CreateKeyOptions{ + keyName, + azkeys.CreateKeyParameters{ + Kty: to.Ptr(azkeys.KeyTypeRSA), // TODO(wallyworld) - make these configurable via storage pool attributes - Size: to.Ptr(int32(4096)), - Operations: []*azkeys.Operation{ - to.Ptr(azkeys.OperationWrapKey), - to.Ptr(azkeys.OperationUnwrapKey), + KeySize: to.Ptr(int32(4096)), + KeyOps: []*azkeys.KeyOperation{ + to.Ptr(azkeys.KeyOperationWrapKey), + to.Ptr(azkeys.KeyOperationUnwrapKey), }, - Properties: &azkeys.Properties{ - Name: to.Ptr(keyName), + KeyAttributes: &azkeys.KeyAttributes{ Enabled: to.Ptr(true), }, - }) + }, + nil) if err == nil { - return resp.Key.ID, nil + return to.Ptr(string(toValue(resp.Key.KID))), nil } if !errorutils.IsConflictError(err) { return nil, errors.Trace(err) } // If the key was previously soft deleted, recover it. - poller, err := keyClient.BeginRecoverDeletedKey(ctx, keyName, nil) - var result azkeys.RecoverDeletedKeyResponse - if err == nil { - result, err = poller.PollUntilDone(ctx, 5*time.Second) - } + result, err := keyClient.RecoverDeletedKey(ctx, keyName, nil) if err != nil { return nil, errors.Annotatef(err, "restoring soft deleted vault key %q in %q", keyName, vaultName) } - return result.Key.ID, nil + return to.Ptr(string(toValue(result.Key.KID))), nil } diff --git a/provider/azure/environ.go b/provider/azure/environ.go index 27b89b52d35..7dcfcbeff6b 100644 --- a/provider/azure/environ.go +++ b/provider/azure/environ.go @@ -165,7 +165,7 @@ func (env *azureEnviron) initEnviron(ctx stdcontext.Context) error { } env.clientOptions = azcore.ClientOptions{ - Cloud: azureCloud(env.cloud.Endpoint, env.cloud.IdentityEndpoint), + Cloud: azureCloud(env.cloud.Name, env.cloud.Endpoint, env.cloud.IdentityEndpoint), PerCallPolicies: []policy.Policy{ &tracing.LoggingPolicy{ Logger: logger.Child("azureapi"), diff --git a/provider/azure/environ_test.go b/provider/azure/environ_test.go index a40d9c63399..c53437c623f 100644 --- a/provider/azure/environ_test.go +++ b/provider/azure/environ_test.go @@ -22,7 +22,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" - "github.com/Azure/go-autorest/autorest/mocks" "github.com/juju/clock/testclock" "github.com/juju/names/v4" gitjujutesting "github.com/juju/testing" @@ -322,19 +321,18 @@ func fakeCloudSpec() environscloudspec.CloudSpec { } func discoverAuthSender() *azuretesting.MockSender { - sender := mocks.NewSender() - resp := mocks.NewResponseWithStatus("", http.StatusUnauthorized) - mocks.SetResponseHeaderValues(resp, "WWW-Authenticate", []string{ + sender := &azuretesting.MockSender{ + PathPattern: ".*/subscriptions/(" + fakeSubscriptionId + "|" + fakeManagedSubscriptionId + ")", + } + resp := azuretesting.NewResponseWithStatus("", http.StatusUnauthorized) + azuretesting.SetResponseHeaderValues(resp, "WWW-Authenticate", []string{ fmt.Sprintf( `authorization_uri="https://testing.invalid/%s"`, fakeTenantId, ), }) sender.AppendResponse(resp) - return &azuretesting.MockSender{ - Sender: sender, - PathPattern: ".*/subscriptions/(" + fakeSubscriptionId + "|" + fakeManagedSubscriptionId + ")", - } + return sender } func (s *environSuite) initResourceGroupSenders(resourceGroupName string) azuretesting.Senders { @@ -347,6 +345,7 @@ type startInstanceSenderParams struct { subnets []*armnetwork.Subnet diskEncryptionSetName string vaultName string + vaultKeyName string existingNetwork string withQuotaRetry bool withConflictRetry bool @@ -381,9 +380,9 @@ func (s *environSuite) startInstanceSenders(args startInstanceSenderParams) azur }, })) vaultName := args.vaultName + "-deadbeef" - deletedVaultSender := azuretesting.MockSender{Sender: mocks.NewSender()} + deletedVaultSender := azuretesting.MockSender{} deletedVaultSender.PathPattern = ".*/locations/westus/deletedVaults/" + vaultName - deletedVaultSender.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + deletedVaultSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "vault not found", http.StatusNotFound, ), 1) senders = append(senders, &deletedVaultSender) @@ -394,12 +393,9 @@ func (s *environSuite) startInstanceSenders(args startInstanceSenderParams) azur VaultURI: to.Ptr("https://vault-uri"), }, })) - // Need to make 2 sends as the SDK sends a second time after token auth. - for i := 0; i < 2; i++ { - senders = append(senders, makeSender("keys/my-vault-deadbeef/create", &keyBundle{ - Key: &jsonWebKey{Kid: to.Ptr("https://key-url")}, - })) - } + senders = append(senders, makeSender(fmt.Sprintf("keys/%s/create", args.vaultKeyName), &keyBundle{ + Key: &jsonWebKey{Kid: to.Ptr("https://key-url")}, + })) } } @@ -462,9 +458,9 @@ func (s *environSuite) resourceSKUsSender() *azuretesting.MockSender { } func makeResourceGroupNotFoundSender(pattern string) *azuretesting.MockSender { - sender := azuretesting.MockSender{Sender: mocks.NewSender()} + sender := azuretesting.MockSender{} sender.PathPattern = pattern - sender.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + sender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "resource group not found", http.StatusNotFound, ), 1) return &sender @@ -477,9 +473,9 @@ func makeSender(pattern string, v interface{}) *azuretesting.MockSender { } func makeSenderWithStatus(pattern string, statusCode int) *azuretesting.MockSender { - sender := azuretesting.MockSender{Sender: mocks.NewSender()} + sender := azuretesting.MockSender{} sender.PathPattern = pattern - sender.AppendResponse(mocks.NewResponseWithStatus("", statusCode)) + sender.AppendResponse(azuretesting.NewResponseWithStatus("", statusCode)) return &sender } @@ -502,7 +498,7 @@ func newAzureResponseError(code int, status string) error { } func (s *environSuite) makeErrorSender(pattern string, err error, repeat int) *azuretesting.MockSender { - sender := &azuretesting.MockSender{Sender: mocks.NewSender()} + sender := &azuretesting.MockSender{} sender.PathPattern = pattern sender.SetAndRepeatError(err, repeat) return sender @@ -618,6 +614,7 @@ func (s *environSuite) assertStartInstance( } diskEncryptionSetName := "" vaultName := "" + vaultKeyName := "" if len(rootDiskSourceParams) > 0 { encrypted, _ := rootDiskSourceParams["encrypted"].(string) if encrypted == "true" { @@ -626,12 +623,14 @@ func (s *environSuite) assertStartInstance( } diskEncryptionSetName, _ = rootDiskSourceParams["disk-encryption-set-name"].(string) vaultName, _ = rootDiskSourceParams["vault-name-prefix"].(string) + vaultKeyName, _ = rootDiskSourceParams["vault-key-name"].(string) } } s.sender = s.startInstanceSenders(startInstanceSenderParams{ bootstrap: false, diskEncryptionSetName: diskEncryptionSetName, vaultName: vaultName, + vaultKeyName: vaultKeyName, withQuotaRetry: withQuotaRetry, withConflictRetry: withConflictRetry, }) @@ -642,6 +641,7 @@ func (s *environSuite) assertStartInstance( bootstrap: false, diskEncryptionSetName: diskEncryptionSetName, vaultName: vaultName, + vaultKeyName: vaultKeyName, withQuotaRetry: withQuotaRetry, existingAvailabilitySet: true, })...) @@ -654,6 +654,7 @@ func (s *environSuite) assertStartInstance( bootstrap: false, diskEncryptionSetName: diskEncryptionSetName, vaultName: vaultName, + vaultKeyName: vaultKeyName, existingCommon: true, })...) } @@ -734,8 +735,8 @@ func (s *environSuite) TestStartInstanceNoAuthorizedKeys(c *gc.C) { } func (s *environSuite) createSenderWithUnauthorisedStatusCode(c *gc.C) { - unauthSender := mocks.NewSender() - unauthSender.AppendAndRepeatResponse(mocks.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) + unauthSender := &azuretesting.MockSender{} + unauthSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) s.sender = azuretesting.Senders{unauthSender, unauthSender, unauthSender} } @@ -1711,8 +1712,8 @@ func (s *environSuite) TestBootstrapWithAutocert(c *gc.C) { func (s *environSuite) TestAllRunningInstancesResourceGroupNotFound(c *gc.C) { env := s.openEnviron(c) - sender := mocks.NewSender() - sender.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + sender := &azuretesting.MockSender{} + sender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "resource group not found", http.StatusNotFound, ), 2) s.sender = azuretesting.Senders{sender, sender} @@ -1746,12 +1747,12 @@ func (s *environSuite) TestAllRunningInstancesIgnoresCommonDeployment(c *gc.C) { func (s *environSuite) TestStopInstancesNotFound(c *gc.C) { env := s.openEnviron(c) - sender0 := mocks.NewSender() - sender0.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + sender0 := &azuretesting.MockSender{} + sender0.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "vm not found", http.StatusNotFound, ), 2) - sender1 := mocks.NewSender() - sender1.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + sender1 := &azuretesting.MockSender{} + sender1.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "vm not found", http.StatusNotFound, ), 2) s.sender = azuretesting.Senders{sender0, sender1} @@ -1860,8 +1861,8 @@ func (s *environSuite) TestStopInstancesMultiple(c *gc.C) { func (s *environSuite) TestStopInstancesDeploymentNotFound(c *gc.C) { env := s.openEnviron(c) - cancelSender := mocks.NewSender() - cancelSender.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + cancelSender := &azuretesting.MockSender{} + cancelSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "deployment not found", http.StatusNotFound, ), 2) s.sender = azuretesting.Senders{cancelSender} diff --git a/provider/azure/environprovider.go b/provider/azure/environprovider.go index 74c000120fb..76ed2084bd0 100644 --- a/provider/azure/environprovider.go +++ b/provider/azure/environprovider.go @@ -7,6 +7,7 @@ import ( stdcontext "context" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + azurecloud "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/juju/clock" "github.com/juju/errors" @@ -180,7 +181,7 @@ var verifyCredentials = func(e *azureEnviron, ctx context.ProviderCallContext) e // This is used at bootstrap - the ctx invalid credential callback will log // a suitable message. _, err := e.credential.GetToken(ctx, policy.TokenRequestOptions{ - Scopes: []string{"https://management.core.windows.net/.default"}, + Scopes: []string{e.clientOptions.Cloud.Services[azurecloud.ResourceManager].Audience + "/.default"}, }) return errorutils.HandleCredentialError(err, ctx) } diff --git a/provider/azure/environprovider_test.go b/provider/azure/environprovider_test.go index e0ce2295100..f2ecbaaf026 100644 --- a/provider/azure/environprovider_test.go +++ b/provider/azure/environprovider_test.go @@ -44,13 +44,11 @@ func (s *environProviderSuite) SetUpTest(c *gc.C) { }, }) s.spec = environscloudspec.CloudSpec{ - Type: "azure", - Name: "azure", - Region: "westus", - Endpoint: "https://api.azurestack.local", - IdentityEndpoint: "https://login.azurestack.local", - StorageEndpoint: "https://storage.azurestack.local", - Credential: fakeServicePrincipalCredential(), + Type: "azure", + Name: "azure", + Region: "westus", + StorageEndpoint: "https://storage.azurestack.local", + Credential: fakeServicePrincipalCredential(), } s.sender = nil } diff --git a/provider/azure/instance_test.go b/provider/azure/instance_test.go index 4343449c6b0..699afa2da6f 100644 --- a/provider/azure/instance_test.go +++ b/provider/azure/instance_test.go @@ -13,7 +13,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" - "github.com/Azure/go-autorest/autorest/mocks" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -463,9 +462,9 @@ func (s *instanceSuite) TestInstanceClosePorts(c *gc.C) { fwInst, ok := inst.(instances.InstanceFirewaller) c.Assert(ok, gc.Equals, true) - sender := mocks.NewSender() - notFoundSender := mocks.NewSender() - notFoundSender.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + sender := &azuretesting.MockSender{} + notFoundSender := &azuretesting.MockSender{} + notFoundSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "rule not found", http.StatusNotFound, ), 2) s.sender = azuretesting.Senders{nsgSender, sender, notFoundSender, notFoundSender, notFoundSender} @@ -496,8 +495,8 @@ func (s *instanceSuite) TestInstanceOpenPorts(c *gc.C) { fwInst, ok := inst.(instances.InstanceFirewaller) c.Assert(ok, gc.Equals, true) - okSender := mocks.NewSender() - okSender.AppendResponse(mocks.NewResponseWithContent("{}")) + okSender := &azuretesting.MockSender{} + okSender.AppendResponse(azuretesting.NewResponseWithContent("{}")) s.sender = azuretesting.Senders{nsgSender, okSender, okSender, okSender, okSender} err := fwInst.OpenPorts(s.callCtx, "0", firewall.IngressRules{ @@ -588,8 +587,8 @@ func (s *instanceSuite) TestInstanceOpenPortsAlreadyOpen(c *gc.C) { fwInst, ok := inst.(instances.InstanceFirewaller) c.Assert(ok, gc.Equals, true) - okSender := mocks.NewSender() - okSender.AppendResponse(mocks.NewResponseWithContent("{}")) + okSender := &azuretesting.MockSender{} + okSender.AppendResponse(azuretesting.NewResponseWithContent("{}")) s.sender = azuretesting.Senders{nsgSender, okSender, okSender} err := fwInst.OpenPorts(s.callCtx, "0", firewall.IngressRules{ diff --git a/provider/azure/internal/azureauth/legacyauth.go b/provider/azure/internal/azureauth/legacyauth.go deleted file mode 100644 index 0dfc4266e2f..00000000000 --- a/provider/azure/internal/azureauth/legacyauth.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2016 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package azureauth - -import ( - "sync" - - "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-06-01/subscriptions" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" - "github.com/juju/errors" - - environscloudspec "github.com/juju/juju/environs/cloudspec" - "github.com/juju/juju/provider/azure/internal/useragent" -) - -// NewLegacyAuth creates a legacy auth for the specified cloud. -// Only used with the legacy unmanaged storage API client which -// is deprecated by Azure and removed in Juju 3.0 -func NewLegacyAuth(cloud environscloudspec.CloudSpec, tenantID string) *cloudSpecAuth { - return &cloudSpecAuth{ - cloud: cloud, - tenantID: tenantID, - } -} - -// cloudSpecAuth provides an implementation of autorest.Authorizer. -type cloudSpecAuth struct { - cloud environscloudspec.CloudSpec - sender autorest.Sender - mu sync.Mutex - tokens map[string]*adal.ServicePrincipalToken - tenantID string -} - -func (c *cloudSpecAuth) Auth() *autorest.BearerAuthorizerCallback { - return autorest.NewBearerAuthorizerCallback(c.sender, func(tenantID, resourceID string) (*autorest.BearerAuthorizer, error) { - token, resourceID, err := c.getToken(resourceID) - if err != nil { - return nil, errors.Annotatef(err, "constructing service principal token for resource %q", resourceID) - } - return autorest.NewBearerAuthorizer(token), nil - }) -} - -func (c *cloudSpecAuth) getToken(resourceID string) (*adal.ServicePrincipalToken, string, error) { - c.mu.Lock() - defer c.mu.Unlock() - if c.tokens == nil { - c.tokens = make(map[string]*adal.ServicePrincipalToken) - } - - if resourceID == "" { - var err error - resourceID, err = ResourceManagerResourceId(c.cloud.StorageEndpoint) - if err != nil { - return nil, "", errors.Trace(err) - } - } - - if token := c.tokens[resourceID]; token != nil { - return token, resourceID, nil - } - logger.Debugf("get auth token for %v", resourceID) - token, err := AuthToken(c.cloud, c.sender, resourceID, c.tenantID) - if err != nil { - return nil, resourceID, errors.Annotatef(err, "constructing service principal token for resource %q", resourceID) - } - - c.tokens[resourceID] = token - return token, resourceID, nil -} - -// AuthToken returns a service principal token, suitable for authorizing -// Resource Manager API requests, based on the supplied CloudSpec. -func AuthToken(cloud environscloudspec.CloudSpec, sender autorest.Sender, resourceID, tenantID string) (*adal.ServicePrincipalToken, error) { - if authType := cloud.Credential.AuthType(); authType != "service-principal-secret" { - // We currently only support a single auth-type for - // non-interactive authentication. Interactive auth - // is used only to generate a service-principal. - return nil, errors.NotSupportedf("auth-type %q", authType) - } - - logger.Debugf("getting new auth token for resource %q", resourceID) - credAttrs := cloud.Credential.Attributes() - appId := credAttrs["application-id"] - appPassword := credAttrs["application-password"] - client := subscriptions.Client{subscriptions.NewWithBaseURI(cloud.Endpoint)} - useragent.UpdateClient(&client.Client) - client.Sender = sender - oauthConfig, err := adal.NewOAuthConfig("https://login.windows.net", tenantID) - if err != nil { - return nil, errors.Trace(err) - } - - token, err := adal.NewServicePrincipalToken( - *oauthConfig, - appId, - appPassword, - resourceID, - func(t adal.Token) error { - logger.Debugf("auth token refreshed for resource %q", resourceID) - return nil - }, - ) - if err != nil { - return nil, errors.Annotate(err, "constructing service principal token") - } - tokenClient := autorest.NewClientWithUserAgent("") - useragent.UpdateClient(&tokenClient) - tokenClient.Sender = sender - token.SetSender(&tokenClient) - return token, nil -} diff --git a/provider/azure/internal/azureauth/serviceprincipal.go b/provider/azure/internal/azureauth/serviceprincipal.go index 923aef3dcc2..51218e65be2 100644 --- a/provider/azure/internal/azureauth/serviceprincipal.go +++ b/provider/azure/internal/azureauth/serviceprincipal.go @@ -11,25 +11,23 @@ import ( "strings" "time" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" - "github.com/Azure/azure-sdk-for-go/services/authorization/mgmt/2015-07-01/authorization" - "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/go-autorest/autorest/azure" - "github.com/Azure/go-autorest/autorest/date" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3" + "github.com/google/uuid" "github.com/juju/clock" "github.com/juju/errors" - "github.com/juju/loggo" "github.com/juju/retry" "github.com/juju/utils/v3" - - "github.com/juju/juju/provider/azure/internal/tracing" - "github.com/juju/juju/provider/azure/internal/useragent" + abstractions "github.com/microsoft/kiota-abstractions-go" + "github.com/microsoftgraph/msgraph-sdk-go" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/serviceprincipals" ) -var logger = loggo.GetLogger("juju.provider.azure.internal.azureauth") - const ( // jujuApplicationId is the ID of the Azure application that we use // for interactive authentication. When the user logs in, a service @@ -54,32 +52,11 @@ func MaybeJujuApplicationObjectID(appID string) (string, error) { return "", errors.Errorf("unexpected application ID %q", appID) } +// ServicePrincipalParams are used when creating Juju service principal. type ServicePrincipalParams struct { - // GraphEndpoint of the Azure graph API. - GraphEndpoint string - - // GraphResourceId is the resource ID of the graph API that is - // used when acquiring access tokens. - GraphResourceId string - - // GraphTokenProvider is the authorization needed to contact the + // Credential is the authorization needed to contact the // Azure graph API. - GraphTokenProvider adal.OAuthTokenProvider - - // AuthEndpoint is the endpoint for auth requests. - AuthEndpoint string - - // ResourceManagerEndpoint is the endpoint of the azure resource - // manager API. - ResourceManagerEndpoint string - - // ResourceManagerResourceId is the resource ID of the resource manager API that is - // used when acquiring access tokens. - ResourceManagerResourceId string - - // ResourceManagerTokenProvider is the authorization needed to - // contact the Azure resource manager API. - ResourceManagerTokenProvider adal.OAuthTokenProvider + Credential azcore.TokenCredential // SubscriptionId is the subscription ID of the account creating // the service principal. @@ -90,55 +67,14 @@ type ServicePrincipalParams struct { TenantId string } -func (p ServicePrincipalParams) directoryClient(sender autorest.Sender, requestInspector autorest.PrepareDecorator) graphrbac.BaseClient { - directoryClient := graphrbac.NewWithBaseURI(p.GraphEndpoint, p.TenantId) - directoryClient.Authorizer = autorest.NewBearerAuthorizer(p.GraphTokenProvider) - directoryClient.Sender = sender - setClientInspectors(&directoryClient.Client, requestInspector, CheckForGraphError, "azure.directory") - return directoryClient -} - -func (p ServicePrincipalParams) authorizationClient(sender autorest.Sender, requestInspector autorest.PrepareDecorator) authorization.BaseClient { - authorizationClient := authorization.NewWithBaseURI(p.ResourceManagerEndpoint, p.SubscriptionId) - useragent.UpdateClient(&authorizationClient.Client) - authorizationClient.Authorizer = autorest.NewBearerAuthorizer(p.ResourceManagerTokenProvider) - authorizationClient.Sender = sender - setClientInspectors(&authorizationClient.Client, requestInspector, nil, "azure.authorization") - return authorizationClient -} - -func setClientInspectors( - client *autorest.Client, - requestInspector autorest.PrepareDecorator, - responseInspector autorest.RespondDecorator, - loggingModule string, -) { - logger := loggo.GetLogger(loggingModule) - client.ResponseInspector = tracing.RespondDecorator(logger) - if responseInspector != nil { - tracer := client.ResponseInspector - client.ResponseInspector = func(r autorest.Responder) autorest.Responder { - r = tracer(r) - r = responseInspector(r) - return r - } - } - client.RequestInspector = tracing.PrepareDecorator(logger) - if requestInspector != nil { - tracer := client.RequestInspector - client.RequestInspector = func(p autorest.Preparer) autorest.Preparer { - p = tracer(p) - p = requestInspector(p) - return p - } - } -} - +// ServicePrincipalCreator creates a service principal for the +// Juju enterprise application. type ServicePrincipalCreator struct { - Sender autorest.Sender - RequestInspector autorest.PrepareDecorator - Clock clock.Clock - NewUUID func() (utils.UUID, error) + RequestAdaptor abstractions.RequestAdapter + Sender policy.Transporter + + Clock clock.Clock + NewUUID func() (uuid.UUID, error) } // InteractiveCreate creates a new ServicePrincipal by performing device @@ -148,77 +84,42 @@ type ServicePrincipalCreator struct { // and SubscriptionId need to be specified in params, the other values // will be derived. func (c *ServicePrincipalCreator) InteractiveCreate(sdkCtx context.Context, stderr io.Writer, params ServicePrincipalParams) (appid, spid, password string, _ error) { - adEndpoint := params.AuthEndpoint - if adEndpoint == "" { - adEndpoint = "https://login.windows.net" - } - oauthConfig, err := adal.NewOAuthConfig(adEndpoint, params.TenantId) - if err != nil { - return "", "", "", errors.Annotate(err, "getting OAuth configuration") - } - - client := autorest.NewClientWithUserAgent("") - useragent.UpdateClient(&client) - client.Sender = c.Sender - setClientInspectors(&client, c.RequestInspector, nil, "azure.autorest") - // Perform the interactive authentication. The user will be prompted to // open a URL and input a device code, after which they will have to // enter their username and password if they are not already // authenticated with Azure. fmt.Fprintln(stderr, "Initiating interactive authentication.") fmt.Fprintln(stderr) - clientId := jujuApplicationId - deviceCode, err := adal.InitiateDeviceAuthWithContext(sdkCtx, &client, *oauthConfig, clientId, params.ResourceManagerResourceId) - if err != nil { - return "", "", "", errors.Annotate(err, "initiating interactive authentication") - } - fmt.Fprintln(stderr, toValue(deviceCode.Message)+"\n") - token, err := adal.WaitForUserCompletionWithContext(sdkCtx, &client, deviceCode) - if err != nil { - return "", "", "", errors.Annotate(err, "waiting for interactive authentication to completed") - } - - // Create service principal tokens that we can use to authorize API - // requests to Active Directory and Resource Manager. These tokens - // are only valid for a short amount of time, so we must create a - // service principal password that can be used to obtain new tokens. - armSpt, err := adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, clientId, params.ResourceManagerResourceId, *token) - if err != nil { - return "", "", "", errors.Annotate(err, "creating temporary ARM service principal token") - } - armSpt.SetSender(&client) - if err := armSpt.Refresh(); err != nil { - return "", "", "", errors.Trace(err) - } - - // The application requires permissions for both ARM and AD, so we - // can use the token for both APIs. - graphToken := armSpt.Token() - graphToken.Resource = params.GraphResourceId - graphSpt, err := adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, clientId, params.GraphResourceId, graphToken) - if err != nil { - return "", "", "", errors.Annotate(err, "creating temporary Graph service principal token") - } - graphSpt.SetSender(&client) - if err := graphSpt.Refresh(); err != nil { - return "", "", "", errors.Trace(err) - } - params.GraphTokenProvider = graphSpt - params.ResourceManagerTokenProvider = armSpt - userObject, err := graphrbac.SignedInUserClient{params.directoryClient(c.Sender, c.RequestInspector)}.Get(sdkCtx) - if err != nil { - return "", "", "", errors.Trace(err) + if params.Credential == nil { + cred, err := azidentity.NewDeviceCodeCredential(&azidentity.DeviceCodeCredentialOptions{ + ClientOptions: azcore.ClientOptions{}, + AdditionallyAllowedTenants: []string{"*"}, + ClientID: jujuApplicationId, + DisableInstanceDiscovery: false, + TenantID: params.TenantId, + }) + if err != nil { + return "", "", "", errors.Trace(err) + } + params.Credential = cred } - fmt.Fprintf(stderr, "Authenticated as %q.\n", toValue(userObject.DisplayName)) return c.Create(sdkCtx, params) } // Create creates a new service principal using the values specified in params. -func (c *ServicePrincipalCreator) Create(sdkCtx context.Context, params ServicePrincipalParams) (appid, spid, password string, _ error) { - servicePrincipalObjectId, password, err := c.createOrUpdateServicePrincipal(sdkCtx, params) +func (c *ServicePrincipalCreator) Create(sdkCtx context.Context, params ServicePrincipalParams) (appid, spid, password string, err error) { + var client *msgraphsdkgo.GraphServiceClient + if c.RequestAdaptor != nil { + client = msgraphsdkgo.NewGraphServiceClient(c.RequestAdaptor) + } else { + client, err = msgraphsdkgo.NewGraphServiceClientWithCredentials(params.Credential, nil) + } + if err != nil { + return "", "", "", errors.Trace(err) + } + servicePrincipalObjectId, password, err := c.createOrUpdateServicePrincipal(sdkCtx, client) if err != nil { return "", "", "", errors.Trace(err) } @@ -228,7 +129,7 @@ func (c *ServicePrincipalCreator) Create(sdkCtx context.Context, params ServiceP return jujuApplicationId, servicePrincipalObjectId, password, nil } -func (c *ServicePrincipalCreator) createOrUpdateServicePrincipal(sdkCtx context.Context, params ServicePrincipalParams) (servicePrincipalObjectId, password string, _ error) { +func (c *ServicePrincipalCreator) createOrUpdateServicePrincipal(sdkCtx context.Context, client *msgraphsdkgo.GraphServiceClient) (servicePrincipalObjectId, password string, _ error) { passwordCredential, err := c.preparePasswordCredential() if err != nil { return "", "", errors.Annotate(err, "preparing password credential") @@ -241,29 +142,47 @@ func (c *ServicePrincipalCreator) createOrUpdateServicePrincipal(sdkCtx context. // service principal; thus, we retry until it exists. The // error checking is based on the logic in azure-cli's // create_service_principal_for_rbac. - client := graphrbac.ServicePrincipalsClient{params.directoryClient(c.Sender, c.RequestInspector)} - var servicePrincipal graphrbac.ServicePrincipal + + addPassword := func(servicePrincipal models.ServicePrincipalable) (string, string, error) { + requestBody := serviceprincipals.NewItemAddPasswordPostRequestBody() + requestBody.SetPasswordCredential(passwordCredential) + + spID := toValue(servicePrincipal.GetId()) + addPassword, err := client.ServicePrincipals().ByServicePrincipalId(spID).AddPassword().Post(context.Background(), requestBody, nil) + if err != nil { + return "", "", errors.Annotate(ReportableError(err), "creating service principal password") + } + return toValue(servicePrincipal.GetId()), toValue(addPassword.GetSecretText()), nil + } + + // The service principal might already exist, so we need to query + // its object ID, and fetch the existing password credentials + // to update. + servicePrincipal, err := client.ServicePrincipalsWithAppId(to.Ptr(jujuApplicationId)).Get(sdkCtx, nil) + if err == nil { + id, password, err := addPassword(servicePrincipal) + if err != nil { + return "", "", errors.Annotate(err, "creating service principal password") + } + return id, password, nil + } + if !isNotFound(err) { + return "", "", errors.Annotate(ReportableError(err), "looking for existing service principal") + } + createServicePrincipal := func() error { - var err error - creds := []graphrbac.PasswordCredential{passwordCredential} - servicePrincipal, err = client.Create( - sdkCtx, - graphrbac.ServicePrincipalCreateParameters{ - AppID: to.Ptr(jujuApplicationId), - AccountEnabled: to.Ptr(true), - PasswordCredentials: &creds, - }, - ) - return err + requestBody := models.NewServicePrincipal() + requestBody.SetAppId(to.Ptr(jujuApplicationId)) + requestBody.SetAccountEnabled(to.Ptr(true)) + servicePrincipal, err = client.ServicePrincipals().Post(sdkCtx, requestBody, nil) + return errors.Annotate(ReportableError(err), "creating service principal") } retryArgs := retry.CallArgs{ Func: createServicePrincipal, IsFatalError: func(err error) bool { - if ge := AsGraphError(err); ge != nil { - if strings.Contains(ge.Message(), " does not reference ") || strings.Contains(ge.Message(), " does not exist ") { - // The application doesn't exist yet, retry later. - return false - } + err = ReportableError(err) + if strings.Contains(err.Error(), " does not reference ") || strings.Contains(err.Error(), " does not exist ") { + return false } return true }, @@ -272,120 +191,72 @@ func (c *ServicePrincipalCreator) createOrUpdateServicePrincipal(sdkCtx context. MaxDuration: time.Minute, } if err := retry.Call(retryArgs); err != nil { - if !isMultipleObjectsWithSameKeyValueErr(err) { - return "", "", errors.Annotate(err, "creating service principal") + if !isAlreadyExists(err) { + return "", "", errors.Trace(err) } // The service principal already exists, so we'll fall out // and update the service principal's password credentials. - } else { - // The service principal was created successfully, with the - // requested password credential. - return toValue(servicePrincipal.ObjectID), toValue(passwordCredential.Value), nil + servicePrincipal, err = client.ServicePrincipalsWithAppId(to.Ptr(jujuApplicationId)).Get(sdkCtx, nil) + if err != nil { + return "", "", errors.Annotate(ReportableError(err), "looking for service principal") + } } - // The service principal already exists, so we need to query - // its object ID, and fetch the existing password credentials - // to update. - servicePrincipal, err = getServicePrincipal(sdkCtx, client, jujuApplicationId) + id, password, err := addPassword(servicePrincipal) if err != nil { - return "", "", errors.Trace(err) + return "", "", errors.Annotate(err, "creating service principal password") } - if err := addServicePrincipalPasswordCredential( - sdkCtx, - client, - toValue(servicePrincipal.ObjectID), - passwordCredential, - ); err != nil { - return "", "", errors.Annotate(err, "updating password credentials") - } - return toValue(servicePrincipal.ObjectID), toValue(passwordCredential.Value), nil -} - -func isMultipleObjectsWithSameKeyValueErr(err error) bool { - if ge := AsGraphError(err); ge != nil { - return ge.Code() == "Request_MultipleObjectsWithSameKeyValue" - } - return false + return id, password, nil } -func (c *ServicePrincipalCreator) preparePasswordCredential() (graphrbac.PasswordCredential, error) { - password, err := c.newUUID() - if err != nil { - return graphrbac.PasswordCredential{}, errors.Annotate(err, "generating password") - } +func (c *ServicePrincipalCreator) preparePasswordCredential() (*models.PasswordCredential, error) { passwordKeyUUID, err := c.newUUID() if err != nil { - return graphrbac.PasswordCredential{}, errors.Annotate(err, "generating password key ID") + return nil, errors.Annotate(err, "generating password key ID") } startDate := c.clock().Now().UTC() endDate := startDate.Add(passwordExpiryDuration) - return graphrbac.PasswordCredential{ - CustomKeyIdentifier: to.Ptr([]byte("juju-" + startDate.Format("20060102"))), - KeyID: to.Ptr(passwordKeyUUID.String()), - Value: to.Ptr(password.String()), - StartDate: &date.Time{startDate}, - EndDate: &date.Time{endDate}, - }, nil -} -func addServicePrincipalPasswordCredential( - sdkCtx context.Context, - client graphrbac.ServicePrincipalsClient, - servicePrincipalObjectId string, - passwordCredential graphrbac.PasswordCredential, -) error { - existing, err := client.ListPasswordCredentials(sdkCtx, servicePrincipalObjectId) - if err != nil { - return errors.Trace(err) - } - var existingValues []graphrbac.PasswordCredential - if existing.Value != nil { - existingValues = *existing.Value - } - passwordCredentials := append(existingValues, passwordCredential) - _, err = client.UpdatePasswordCredentials( - sdkCtx, - servicePrincipalObjectId, - graphrbac.PasswordCredentialsUpdateParameters{&passwordCredentials}, - ) - return errors.Trace(err) -} - -func getServicePrincipal(sdkCtx context.Context, client graphrbac.ServicePrincipalsClient, appID string) (graphrbac.ServicePrincipal, error) { - filter := fmt.Sprintf( - "appId eq '%s'", - appID, - ) - it, err := client.ListComplete(sdkCtx, filter) - if err != nil { - return graphrbac.ServicePrincipal{}, errors.Annotate(err, "listing service principals") - } - for it.NotDone() { - sp := it.Value() - if toValue(sp.AppID) == appID { - return sp, nil - } - if err := it.NextWithContext(sdkCtx); err != nil { - return graphrbac.ServicePrincipal{}, errors.Annotate(err, "listing service principals") - } - } - return graphrbac.ServicePrincipal{}, errors.NotFoundf("service principal") + cred := models.NewPasswordCredential() + cred.SetCustomKeyIdentifier([]byte("juju-" + startDate.Format("20060102"))) + cred.SetKeyId(to.Ptr(passwordKeyUUID)) + cred.SetStartDateTime(&startDate) + cred.SetEndDateTime(&endDate) + return cred, nil } func (c *ServicePrincipalCreator) createRoleAssignment(sdkCtx context.Context, params ServicePrincipalParams, servicePrincipalObjectId string) error { - client := params.authorizationClient(c.Sender, c.RequestInspector) // Find the role definition with the name "Owner". roleScope := path.Join("subscriptions", params.SubscriptionId) - roleDefinitionsClient := authorization.RoleDefinitionsClient{client} - result, err := roleDefinitionsClient.List(sdkCtx, roleScope, "roleName eq 'Owner'") + + clientFactory, err := armauthorization.NewClientFactory(params.SubscriptionId, params.Credential, &arm.ClientOptions{ + ClientOptions: policy.ClientOptions{ + Transport: c.Sender, + }, + }) if err != nil { - return errors.Annotate(err, "listing role definitions") + return errors.Annotate(err, "failed to create auth client") + } + roleDefinitionClient := clientFactory.NewRoleDefinitionsClient() + pager := roleDefinitionClient.NewListPager(roleScope, &armauthorization.RoleDefinitionsClientListOptions{ + Filter: to.Ptr("roleName eq 'Owner'"), + }) + var roleDefinitionId string +done: + for pager.More() { + next, err := pager.NextPage(sdkCtx) + if err != nil { + return errors.Annotate(err, "fetching role definitions") + } + for _, r := range next.Value { + roleDefinitionId = toValue(r.ID) + break done + } } - ownerRoles := result.Values() - if len(ownerRoles) == 0 { + + if roleDefinitionId == "" { return errors.NotFoundf("Owner role definition") } - roleDefinitionId := ownerRoles[0].ID // The UUID value for the role assignment name is unimportant. Azure // will prevent multiple role assignments for the same role definition @@ -394,25 +265,25 @@ func (c *ServicePrincipalCreator) createRoleAssignment(sdkCtx context.Context, p if err != nil { return errors.Annotate(err, "generating role assignment ID") } - roleAssignmentsClient := authorization.RoleAssignmentsClient{client} roleAssignmentName := roleAssignmentUUID.String() + roleAssignmentClient := clientFactory.NewRoleAssignmentsClient() retryArgs := retry.CallArgs{ Func: func() error { - _, err := roleAssignmentsClient.Create( + _, err := roleAssignmentClient.Create( sdkCtx, roleScope, roleAssignmentName, - authorization.RoleAssignmentCreateParameters{ - Properties: &authorization.RoleAssignmentProperties{ - RoleDefinitionID: roleDefinitionId, + armauthorization.RoleAssignmentCreateParameters{ + Properties: &armauthorization.RoleAssignmentProperties{ + RoleDefinitionID: to.Ptr(roleDefinitionId), PrincipalID: to.Ptr(servicePrincipalObjectId), + PrincipalType: to.Ptr(armauthorization.PrincipalTypeServicePrincipal), }, - }, + }, nil, ) return err }, IsFatalError: func(err error) bool { - serviceErr, ok := serviceError(err) - if ok && strings.Contains(serviceErr.Message, " does not exist in the directory ") { + if strings.Contains(err.Error(), " does not exist in the directory ") { // The service principal doesn't exist yet, retry later. return false } @@ -423,9 +294,10 @@ func (c *ServicePrincipalCreator) createRoleAssignment(sdkCtx context.Context, p MaxDuration: time.Minute, } if err := retry.Call(retryArgs); err != nil { - if err, ok := serviceError(err); ok { + var respErr *azcore.ResponseError + if errors.As(err, &respErr) { const serviceErrorCodeRoleAssignmentExists = "RoleAssignmentExists" - if err.Code == serviceErrorCodeRoleAssignmentExists { + if respErr.ErrorCode == serviceErrorCodeRoleAssignmentExists { return nil } } @@ -434,6 +306,16 @@ func (c *ServicePrincipalCreator) createRoleAssignment(sdkCtx context.Context, p return nil } +func isAlreadyExists(err error) bool { + dataErr, ok := AsDataError(err) + return ok && dataErr.Code() == "Request_MultipleObjectsWithSameKeyValue" +} + +func isNotFound(err error) bool { + dataErr, ok := AsDataError(err) + return ok && dataErr.Code() == "Request_ResourceNotFound" +} + func (c *ServicePrincipalCreator) clock() clock.Clock { if c.Clock == nil { return clock.WallClock @@ -441,41 +323,17 @@ func (c *ServicePrincipalCreator) clock() clock.Clock { return c.Clock } -func (c *ServicePrincipalCreator) newUUID() (utils.UUID, error) { +func (c *ServicePrincipalCreator) newUUID() (uuid.UUID, error) { if c.NewUUID == nil { - return utils.NewUUID() + u, err := utils.NewUUID() + if err != nil { + return uuid.UUID{}, errors.Trace(err) + } + return uuid.Parse(u.String()) } return c.NewUUID() } -// serviceError returns the *azure.ServiceError underlying the -// supplied error, if any, and a bool indicating whether one -// was found. -func serviceError(err error) (*azure.ServiceError, bool) { - if err == nil { - return nil, false - } - err = errors.Cause(err) - if d, ok := err.(autorest.DetailedError); ok { - err = errors.Cause(d.Original) - } - if se, ok := err.(*azure.ServiceError); ok { - return se, true - } - if r, ok := err.(*azure.RequestError); ok { - return r.ServiceError, true - } - // The error Azure gives us back can also be a struct - // not a pointer. - if se, ok := err.(azure.ServiceError); ok { - return &se, true - } - if r, ok := err.(azure.RequestError); ok { - return r.ServiceError, true - } - return nil, false -} - func toValue[T any](v *T) T { if v == nil { return *new(T) diff --git a/provider/azure/internal/azureauth/serviceprincipal_test.go b/provider/azure/internal/azureauth/serviceprincipal_test.go index fd498c79787..51b5e7f8464 100644 --- a/provider/azure/internal/azureauth/serviceprincipal_test.go +++ b/provider/azure/internal/azureauth/serviceprincipal_test.go @@ -6,146 +6,109 @@ package azureauth_test import ( "bytes" "context" - "encoding/json" "fmt" - "io" "net/http" + "regexp" "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" - "github.com/Azure/azure-sdk-for-go/services/authorization/mgmt/2015-07-01/authorization" - "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/go-autorest/autorest/mocks" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3" + "github.com/google/uuid" "github.com/juju/clock/testclock" + "github.com/juju/errors" "github.com/juju/testing" jc "github.com/juju/testing/checkers" - "github.com/juju/utils/v3" + abstractions "github.com/microsoft/kiota-abstractions-go" + "github.com/microsoft/kiota-abstractions-go/authentication" + "github.com/microsoft/kiota-abstractions-go/serialization" + "github.com/microsoft/kiota-abstractions-go/store" + nethttplibrary "github.com/microsoft/kiota-http-go" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" gc "gopkg.in/check.v1" "github.com/juju/juju/provider/azure/internal/azureauth" "github.com/juju/juju/provider/azure/internal/azuretesting" ) -func clockStartTime() time.Time { - t, _ := time.Parse("2006-Jan-02 3:04am", "2016-Sep-19 9:47am") - return t +type requestResult struct { + PathPattern string + Params map[string]string + Result serialization.Parsable + Err error } -type InteractiveSuite struct { - testing.IsolationSuite - clock *testclock.Clock - newUUID func() (utils.UUID, error) -} - -var _ = gc.Suite(&InteractiveSuite{}) - -const fakeTenantId = "11111111-1111-1111-1111-111111111111" +type MockRequestAdaptor struct { + *nethttplibrary.NetHttpRequestAdapter -func deviceCodeSender() autorest.Sender { - return azuretesting.NewSenderWithValue(adal.DeviceCode{ - DeviceCode: to.Ptr("device-code"), - Interval: to.Ptr(int64(1)), // 1 second between polls - Message: to.Ptr("open your browser, etc."), - }) -} - -func tokenSender() autorest.Sender { - return azuretesting.NewSenderWithValue(adal.Token{ - RefreshToken: "refresh-token", - ExpiresOn: json.Number(fmt.Sprint(time.Now().Add(time.Hour).Unix())), - }) + results []requestResult } -func passwordCredentialsListSender() autorest.Sender { - v := []graphrbac.PasswordCredential{{ - KeyID: to.Ptr("password-credential-key-id"), - }} - return azuretesting.NewSenderWithValue(graphrbac.PasswordCredentialListResult{ - Value: &v, - }) -} - -func updatePasswordCredentialsSender() autorest.Sender { - sender := mocks.NewSender() - sender.AppendResponse(mocks.NewResponseWithStatus("", http.StatusNoContent)) - return sender -} - -func currentUserSender() autorest.Sender { - return azuretesting.NewSenderWithValue(graphrbac.User{ - DisplayName: to.Ptr("Foo Bar"), - }) -} - -func createServicePrincipalSender() autorest.Sender { - return azuretesting.NewSenderWithValue(graphrbac.ServicePrincipal{ - AppID: to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66"), - ObjectID: to.Ptr("sp-object-id"), - }) -} - -func createServicePrincipalAlreadyExistsSender() autorest.Sender { - sender := mocks.NewSender() - bodyData := `{"odata.error":{"code":"Request_MultipleObjectsWithSameKeyValue"}}` - body := mocks.NewBody(bodyData) - sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusConflict, "")) - return sender +func (m *MockRequestAdaptor) Send(ctx context.Context, requestInfo *abstractions.RequestInformation, constructor serialization.ParsableFactory, errorMappings abstractions.ErrorMappings) (serialization.Parsable, error) { + if len(m.results) == 0 { + return nil, errors.Errorf("no results for %q", requestInfo.PathParameters) + } + res := m.results[0] + m.results = m.results[1:] + if res.PathPattern != "" { + matched, err := regexp.MatchString(res.PathPattern, requestInfo.UrlTemplate) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf( + "request path %q did not match pattern %q", + requestInfo.UrlTemplate, res.PathPattern, + ) + } + } + for k, v := range res.Params { + if val := requestInfo.PathParameters[k]; val != v { + return nil, fmt.Errorf( + "request path parameter %q=%q did not match parameter %q", + k, v, val, + ) + } + } + return res.Result, res.Err } -func createServicePrincipalNotExistSender() autorest.Sender { - sender := mocks.NewSender() - bodyData := `{"odata.error":{"code":"Request_ResourceNotFound","message":{"lang":"en","value":"... does not exist in the directory ..."}}}` - body := mocks.NewBody(bodyData) - sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusNotFound, "")) - return sender +type InteractiveSuite struct { + testing.IsolationSuite + clock testclock.AdvanceableClock + newUUID func() (uuid.UUID, error) } -func createServicePrincipalNotReferenceSender() autorest.Sender { - sender := mocks.NewSender() - // Error message cribbed from https://github.com/kubernetes/kubernetes-anywhere/issues/251 - bodyData := `{"odata.error":{"code":"Request_BadRequest","message":{"lang":"en","value":"The appId of the service principal does not reference a valid application object."}}}` - body := mocks.NewBody(bodyData) - sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "")) - return sender -} +var _ = gc.Suite(&InteractiveSuite{}) -func servicePrincipalListSender() autorest.Sender { - return azuretesting.NewSenderWithValue(graphrbac.ServicePrincipalListResult{ - Value: &[]graphrbac.ServicePrincipal{{ - AppID: to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66"), - ObjectID: to.Ptr("sp-object-id"), - }}, - }) -} +const fakeTenantId = "11111111-1111-1111-1111-111111111111" -func roleDefinitionListSender() autorest.Sender { - roleDefinitions := []authorization.RoleDefinition{{ +func roleDefinitionListSender() *azuretesting.MockSender { + roleDefinitions := []*armauthorization.RoleDefinition{{ ID: to.Ptr("owner-role-id"), Name: to.Ptr("Owner"), }} - return azuretesting.NewSenderWithValue(authorization.RoleDefinitionListResult{ - Value: &roleDefinitions, + return azuretesting.NewSenderWithValue(armauthorization.RoleDefinitionListResult{ + Value: roleDefinitions, }) } -func roleAssignmentSender() autorest.Sender { - return azuretesting.NewSenderWithValue(authorization.RoleAssignment{}) +func roleAssignmentSender() *azuretesting.MockSender { + return azuretesting.NewSenderWithValue(armauthorization.RoleAssignment{}) } -func roleAssignmentAlreadyExistsSender() autorest.Sender { - sender := mocks.NewSender() - body := mocks.NewBody(`{"error":{"code":"RoleAssignmentExists", "message":"Odata v4 compliant message"}}`) - sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusConflict, "")) +func roleAssignmentAlreadyExistsSender() *azuretesting.MockSender { + sender := &azuretesting.MockSender{} + body := azuretesting.NewBody(`{"error":{"code":"RoleAssignmentExists", "message":"Odata v4 compliant message"}}`) + sender.AppendResponse(azuretesting.NewResponseWithBodyAndStatus(body, http.StatusConflict, "")) return sender } -func roleAssignmentPrincipalNotExistSender() autorest.Sender { - sender := mocks.NewSender() +func roleAssignmentPrincipalNotExistSender() *azuretesting.MockSender { + sender := &azuretesting.MockSender{} // Based on https://github.com/Azure/azure-powershell/issues/655#issuecomment-186332230 - body := mocks.NewBody(`{"error":{"code":"PrincipalNotFound","message":"Principal foo does not exist in the directory bar"}}`) - sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusNotFound, "")) + body := azuretesting.NewBody(`{"error":{"code":"PrincipalNotFound","message":"Principal foo does not exist in the directory bar"}}`) + sender.AppendResponse(azuretesting.NewResponseWithBodyAndStatus(body, http.StatusNotFound, "")) return sender } @@ -156,37 +119,47 @@ func (s *InteractiveSuite) SetUpTest(c *gc.C) { "44444444-4444-4444-4444-444444444444", // password key ID "55555555-5555-5555-5555-555555555555", // role assignment ID } - s.newUUID = func() (utils.UUID, error) { - uuid, err := utils.UUIDFromString(uuids[0]) + s.newUUID = func() (res uuid.UUID, err error) { + res, err = uuid.Parse(uuids[0]) if err != nil { - return utils.UUID{}, err + return res, err } uuids = uuids[1:] - return uuid, nil + return res, nil } - s.clock = testclock.NewClock(clockStartTime()) + s.clock = testclock.NewDilatedWallClock(10 * time.Millisecond) } func (s *InteractiveSuite) TestInteractive(c *gc.C) { - var requests []*http.Request + ra, err := nethttplibrary.NewNetHttpRequestAdapter(&authentication.AnonymousAuthenticationProvider{}) + c.Assert(err, jc.ErrorIsNil) + + sp := models.NewServicePrincipal() + sp.SetAppId(to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66")) + sp.SetId(to.Ptr("sp-object-id")) + cred := models.NewPasswordCredential() + cred.SetSecretText(to.Ptr("33333333-3333-3333-3333-333333333333")) + + mockAdaptor := &MockRequestAdaptor{NetHttpRequestAdapter: ra} + mockAdaptor.results = []requestResult{{ + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals(appId='{appId}')") + ".*", + Params: map[string]string{"appId": "60a04dc9-1857-425f-8076-5ba81ca53d66"}, + Result: sp, + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals/{servicePrincipal%2Did}/addPassword"), + Params: map[string]string{"servicePrincipal%2Did": "sp-object-id"}, + Result: cred, + }} + + authSenders := &azuretesting.Senders{ + roleDefinitionListSender(), + roleAssignmentSender(), + } spc := azureauth.ServicePrincipalCreator{ - Sender: &azuretesting.Senders{ - deviceCodeSender(), - tokenSender(), // CheckForUserCompletion returns a token. - - // Token.Refresh returns a token. We do this - // twice: once for ARM, and once for AAD. - tokenSender(), - tokenSender(), - - currentUserSender(), - createServicePrincipalSender(), - roleDefinitionListSender(), - roleAssignmentSender(), - }, - RequestInspector: azuretesting.RequestRecorder(&requests), - Clock: s.clock, - NewUUID: s.newUUID, + Sender: authSenders, + RequestAdaptor: mockAdaptor, + Clock: s.clock, + NewUUID: s.newUUID, } var stderr bytes.Buffer @@ -194,13 +167,9 @@ func (s *InteractiveSuite) TestInteractive(c *gc.C) { sdkCtx := context.Background() appId, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, &stderr, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid", - GraphResourceId: "https://graph.invalid", - ResourceManagerEndpoint: "https://arm.invalid", - ResourceManagerResourceId: "https://management.core.invalid/", - AuthEndpoint: "https://testing.invalid", - SubscriptionId: subscriptionId, - TenantId: fakeTenantId, + SubscriptionId: subscriptionId, + TenantId: fakeTenantId, + Credential: &azuretesting.FakeCredential{}, }) c.Assert(err, jc.ErrorIsNil) c.Assert(appId, gc.Equals, "60a04dc9-1857-425f-8076-5ba81ca53d66") @@ -209,239 +178,214 @@ func (s *InteractiveSuite) TestInteractive(c *gc.C) { c.Assert(stderr.String(), gc.Equals, ` Initiating interactive authentication. -open your browser, etc. - -Authenticated as "Foo Bar". `[1:]) +} - c.Assert(requests, gc.HasLen, 8) - c.Check(requests[0].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode") - c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me") - c.Check(requests[5].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") - c.Check(requests[6].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions") - c.Check(requests[7].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555") - - // The service principal creation includes the password. Check that the - // password returned from the function is the same as the one set in the - // request. - var params graphrbac.ServicePrincipalCreateParameters - err = json.NewDecoder(requests[5].Body).Decode(¶ms) +func (s *InteractiveSuite) TestInteractiveRoleAssignmentAlreadyExists(c *gc.C) { + ra, err := nethttplibrary.NewNetHttpRequestAdapter(&authentication.AnonymousAuthenticationProvider{}) c.Assert(err, jc.ErrorIsNil) - c.Assert((*params.PasswordCredentials), gc.HasLen, 1) - assertPasswordCredential(c, (*params.PasswordCredentials)[0]) -} -func assertPasswordCredential(c *gc.C, cred graphrbac.PasswordCredential) { - var startDate, endDate time.Time - if cred.StartDate != nil { - startDate = cred.StartDate.Time - } - if cred.EndDate != nil { - endDate = cred.EndDate.Time - } - c.Assert(startDate, gc.Equals, clockStartTime()) - c.Assert(endDate.Sub(startDate), gc.Equals, 365*24*time.Hour) - - cred.StartDate = nil - cred.EndDate = nil - c.Assert(cred, jc.DeepEquals, graphrbac.PasswordCredential{ - CustomKeyIdentifier: to.Ptr([]byte("juju-20160919")), - KeyID: to.Ptr("44444444-4444-4444-4444-444444444444"), - Value: to.Ptr("33333333-3333-3333-3333-333333333333"), - }) -} + sp := models.NewServicePrincipal() + sp.SetAppId(to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66")) + sp.SetId(to.Ptr("sp-object-id")) + cred := models.NewPasswordCredential() + cred.SetSecretText(to.Ptr("33333333-3333-3333-3333-333333333333")) + + mockAdaptor := &MockRequestAdaptor{NetHttpRequestAdapter: ra} + mockAdaptor.results = []requestResult{{ + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals(appId='{appId}')") + ".*", + Params: map[string]string{"appId": "60a04dc9-1857-425f-8076-5ba81ca53d66"}, + Result: sp, + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals/{servicePrincipal%2Did}/addPassword"), + Params: map[string]string{"servicePrincipal%2Did": "sp-object-id"}, + Result: cred, + }} -func (s *InteractiveSuite) TestInteractiveRoleAssignmentAlreadyExists(c *gc.C) { - var requests []*http.Request + authSenders := &azuretesting.Senders{ + roleDefinitionListSender(), + roleAssignmentAlreadyExistsSender(), + } spc := azureauth.ServicePrincipalCreator{ - Sender: &azuretesting.Senders{ - deviceCodeSender(), - tokenSender(), - tokenSender(), - tokenSender(), - currentUserSender(), - createServicePrincipalSender(), - roleDefinitionListSender(), - roleAssignmentAlreadyExistsSender(), - }, - RequestInspector: azuretesting.RequestRecorder(&requests), - Clock: s.clock, - NewUUID: s.newUUID, + Sender: authSenders, + RequestAdaptor: mockAdaptor, + Clock: s.clock, + NewUUID: s.newUUID, } + + var stderr bytes.Buffer + subscriptionId := "22222222-2222-2222-2222-222222222222" sdkCtx := context.Background() - _, _, _, err := spc.InteractiveCreate(sdkCtx, io.Discard, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid", - GraphResourceId: "https://graph.invalid", - ResourceManagerEndpoint: "https://arm.invalid", - ResourceManagerResourceId: "https://management.core.invalid/", - AuthEndpoint: "https://testing.invalid", - SubscriptionId: "22222222-2222-2222-2222-222222222222", - TenantId: fakeTenantId, + + appId, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, &stderr, azureauth.ServicePrincipalParams{ + SubscriptionId: subscriptionId, + TenantId: fakeTenantId, + Credential: &azuretesting.FakeCredential{}, }) c.Assert(err, jc.ErrorIsNil) + c.Assert(appId, gc.Equals, "60a04dc9-1857-425f-8076-5ba81ca53d66") + c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333") + c.Assert(spObjectId, gc.Equals, "sp-object-id") + c.Assert(stderr.String(), gc.Equals, ` +Initiating interactive authentication. + +`[1:]) +} + +func dataError(code string) error { + result := odataerrors.NewODataError() + mainErr := odataerrors.NewMainError() + mainErr.SetCode(to.Ptr(code)) + bs := store.NewInMemoryBackingStore() + result.SetBackingStore(bs) + result.SetErrorEscaped(mainErr) + return result } -func (s *InteractiveSuite) TestInteractiveServicePrincipalAlreadyExists(c *gc.C) { - var requests []*http.Request +func (s *InteractiveSuite) TestInteractiveServicePrincipalNotFound(c *gc.C) { + ra, err := nethttplibrary.NewNetHttpRequestAdapter(&authentication.AnonymousAuthenticationProvider{}) + c.Assert(err, jc.ErrorIsNil) + + sp := models.NewServicePrincipal() + sp.SetAppId(to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66")) + sp.SetId(to.Ptr("sp-object-id")) + cred := models.NewPasswordCredential() + cred.SetSecretText(to.Ptr("33333333-3333-3333-3333-333333333333")) + + mockAdaptor := &MockRequestAdaptor{NetHttpRequestAdapter: ra} + mockAdaptor.results = []requestResult{{ + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals(appId='{appId}')") + ".*", + Params: map[string]string{"appId": "60a04dc9-1857-425f-8076-5ba81ca53d66"}, + Err: dataError("Request_ResourceNotFound"), + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals"), + Result: sp, + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals/{servicePrincipal%2Did}/addPassword"), + Params: map[string]string{"servicePrincipal%2Did": "sp-object-id"}, + Result: cred, + }} + + authSenders := &azuretesting.Senders{ + roleDefinitionListSender(), + roleAssignmentSender(), + } spc := azureauth.ServicePrincipalCreator{ - Sender: &azuretesting.Senders{ - deviceCodeSender(), - tokenSender(), - tokenSender(), - tokenSender(), - currentUserSender(), - createServicePrincipalAlreadyExistsSender(), - servicePrincipalListSender(), - passwordCredentialsListSender(), - updatePasswordCredentialsSender(), - roleDefinitionListSender(), - roleAssignmentAlreadyExistsSender(), - }, - RequestInspector: azuretesting.RequestRecorder(&requests), - Clock: s.clock, - NewUUID: s.newUUID, + Sender: authSenders, + RequestAdaptor: mockAdaptor, + Clock: s.clock, + NewUUID: s.newUUID, } + + var stderr bytes.Buffer + subscriptionId := "22222222-2222-2222-2222-222222222222" sdkCtx := context.Background() - _, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, io.Discard, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid", - GraphResourceId: "https://graph.invalid", - ResourceManagerEndpoint: "https://arm.invalid", - ResourceManagerResourceId: "https://management.core.invalid/", - AuthEndpoint: "https://testing.invalid", - SubscriptionId: "22222222-2222-2222-2222-222222222222", - TenantId: fakeTenantId, + + appId, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, &stderr, azureauth.ServicePrincipalParams{ + SubscriptionId: subscriptionId, + TenantId: fakeTenantId, + Credential: &azuretesting.FakeCredential{}, }) c.Assert(err, jc.ErrorIsNil) + c.Assert(appId, gc.Equals, "60a04dc9-1857-425f-8076-5ba81ca53d66") c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333") c.Assert(spObjectId, gc.Equals, "sp-object-id") - - c.Assert(requests, gc.HasLen, 11) - c.Check(requests[0].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode") - c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me") - c.Check(requests[5].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") // create - c.Check(requests[6].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") // list - c.Check(requests[7].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals/sp-object-id/passwordCredentials") // list - c.Check(requests[8].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals/sp-object-id/passwordCredentials") // update - c.Check(requests[9].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions") - c.Check(requests[10].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555") - - // Make sure that we don't wipe existing password credentials, and that - // the new password credential matches the one returned from the - // function. - var params graphrbac.PasswordCredentialsUpdateParameters - err = json.NewDecoder(requests[8].Body).Decode(¶ms) - c.Assert(err, jc.ErrorIsNil) - c.Assert((*params.Value), gc.HasLen, 2) - c.Assert((*params.Value)[0], jc.DeepEquals, graphrbac.PasswordCredential{ - KeyID: to.Ptr("password-credential-key-id"), - }) - assertPasswordCredential(c, (*params.Value)[1]) } -func (s *InteractiveSuite) TestInteractiveServicePrincipalApplicationNotExist(c *gc.C) { - s.testInteractiveRetriesCreateServicePrincipal(c, createServicePrincipalNotExistSender()) -} +func (s *InteractiveSuite) TestInteractiveServicePrincipalNotFoundRace(c *gc.C) { + ra, err := nethttplibrary.NewNetHttpRequestAdapter(&authentication.AnonymousAuthenticationProvider{}) + c.Assert(err, jc.ErrorIsNil) -func (s *InteractiveSuite) TestInteractiveServicePrincipalApplicationNotReference(c *gc.C) { - s.testInteractiveRetriesCreateServicePrincipal(c, createServicePrincipalNotReferenceSender()) -} + sp := models.NewServicePrincipal() + sp.SetAppId(to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66")) + sp.SetId(to.Ptr("sp-object-id")) + cred := models.NewPasswordCredential() + cred.SetSecretText(to.Ptr("33333333-3333-3333-3333-333333333333")) + + mockAdaptor := &MockRequestAdaptor{NetHttpRequestAdapter: ra} + mockAdaptor.results = []requestResult{{ + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals(appId='{appId}')") + ".*", + Params: map[string]string{"appId": "60a04dc9-1857-425f-8076-5ba81ca53d66"}, + Err: dataError("Request_ResourceNotFound"), + }, { + Err: dataError("Request_MultipleObjectsWithSameKeyValue"), + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals(appId='{appId}')") + ".*", + Params: map[string]string{"appId": "60a04dc9-1857-425f-8076-5ba81ca53d66"}, + Result: sp, + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals/{servicePrincipal%2Did}/addPassword"), + Params: map[string]string{"servicePrincipal%2Did": "sp-object-id"}, + Result: cred, + }} -func (s *InteractiveSuite) testInteractiveRetriesCreateServicePrincipal(c *gc.C, errorSender autorest.Sender) { - var requests []*http.Request + authSenders := &azuretesting.Senders{ + roleDefinitionListSender(), + roleAssignmentSender(), + } spc := azureauth.ServicePrincipalCreator{ - Sender: &azuretesting.Senders{ - deviceCodeSender(), - tokenSender(), - tokenSender(), - tokenSender(), - currentUserSender(), - errorSender, - createServicePrincipalSender(), - roleDefinitionListSender(), - roleAssignmentAlreadyExistsSender(), - }, - RequestInspector: azuretesting.RequestRecorder(&requests), - Clock: &testclock.AutoAdvancingClock{ - Clock: s.clock, - Advance: s.clock.Advance, - }, - NewUUID: s.newUUID, + Sender: authSenders, + RequestAdaptor: mockAdaptor, + Clock: s.clock, + NewUUID: s.newUUID, } + + var stderr bytes.Buffer + subscriptionId := "22222222-2222-2222-2222-222222222222" sdkCtx := context.Background() - _, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, io.Discard, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid", - GraphResourceId: "https://graph.invalid", - ResourceManagerEndpoint: "https://arm.invalid", - ResourceManagerResourceId: "https://management.core.invalid/", - AuthEndpoint: "https://testing.invalid", - SubscriptionId: "22222222-2222-2222-2222-222222222222", - TenantId: fakeTenantId, + + appId, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, &stderr, azureauth.ServicePrincipalParams{ + SubscriptionId: subscriptionId, + TenantId: fakeTenantId, Credential: &azuretesting.FakeCredential{}, }) c.Assert(err, jc.ErrorIsNil) + c.Assert(appId, gc.Equals, "60a04dc9-1857-425f-8076-5ba81ca53d66") c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333") c.Assert(spObjectId, gc.Equals, "sp-object-id") - - c.Assert(requests, gc.HasLen, 9) - c.Check(requests[0].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode") - c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me") - c.Check(requests[5].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") // create - c.Check(requests[6].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") // create - c.Check(requests[7].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions") - c.Check(requests[8].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555") } func (s *InteractiveSuite) TestInteractiveRetriesRoleAssignment(c *gc.C) { - var requests []*http.Request + ra, err := nethttplibrary.NewNetHttpRequestAdapter(&authentication.AnonymousAuthenticationProvider{}) + c.Assert(err, jc.ErrorIsNil) + + sp := models.NewServicePrincipal() + sp.SetAppId(to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66")) + sp.SetId(to.Ptr("sp-object-id")) + cred := models.NewPasswordCredential() + cred.SetSecretText(to.Ptr("33333333-3333-3333-3333-333333333333")) + + mockAdaptor := &MockRequestAdaptor{NetHttpRequestAdapter: ra} + mockAdaptor.results = []requestResult{{ + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals(appId='{appId}')") + ".*", + Params: map[string]string{"appId": "60a04dc9-1857-425f-8076-5ba81ca53d66"}, + Result: sp, + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals/{servicePrincipal%2Did}/addPassword"), + Params: map[string]string{"servicePrincipal%2Did": "sp-object-id"}, + Result: cred, + }} + + authSenders := &azuretesting.Senders{ + roleDefinitionListSender(), + roleAssignmentPrincipalNotExistSender(), + roleAssignmentSender(), + } spc := azureauth.ServicePrincipalCreator{ - Sender: &azuretesting.Senders{ - deviceCodeSender(), - tokenSender(), - tokenSender(), - tokenSender(), - currentUserSender(), - createServicePrincipalSender(), - roleDefinitionListSender(), - roleAssignmentPrincipalNotExistSender(), - roleAssignmentSender(), - }, - RequestInspector: azuretesting.RequestRecorder(&requests), - Clock: &testclock.AutoAdvancingClock{ - Clock: s.clock, - Advance: s.clock.Advance, - }, - NewUUID: s.newUUID, + Sender: authSenders, + RequestAdaptor: mockAdaptor, + Clock: s.clock, + NewUUID: s.newUUID, } + + var stderr bytes.Buffer + subscriptionId := "22222222-2222-2222-2222-222222222222" sdkCtx := context.Background() - _, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, io.Discard, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid", - GraphResourceId: "https://graph.invalid", - ResourceManagerEndpoint: "https://arm.invalid", - ResourceManagerResourceId: "https://management.core.invalid/", - AuthEndpoint: "https://testing.invalid", - SubscriptionId: "22222222-2222-2222-2222-222222222222", - TenantId: fakeTenantId, + appId, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, &stderr, azureauth.ServicePrincipalParams{ + SubscriptionId: subscriptionId, + TenantId: fakeTenantId, Credential: &azuretesting.FakeCredential{}, }) c.Assert(err, jc.ErrorIsNil) + c.Assert(appId, gc.Equals, "60a04dc9-1857-425f-8076-5ba81ca53d66") c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333") c.Assert(spObjectId, gc.Equals, "sp-object-id") - - c.Assert(requests, gc.HasLen, 9) - c.Check(requests[0].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode") - c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me") - c.Check(requests[5].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") // create - c.Check(requests[6].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions") - c.Check(requests[7].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555") - c.Check(requests[8].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555") } diff --git a/provider/azure/internal/azureauth/utils.go b/provider/azure/internal/azureauth/utils.go index 9951be4e375..b154688df4f 100644 --- a/provider/azure/internal/azureauth/utils.go +++ b/provider/azure/internal/azureauth/utils.go @@ -4,100 +4,31 @@ package azureauth import ( - "bytes" - "encoding/json" - "io" - "net/http" - "net/url" - "strings" - - "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/azure" "github.com/juju/errors" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" ) -// ResourceManagerResourceId returns the resource ID for the -// Azure Resource Manager application to use in auth requests, -// based on the given core endpoint URI (e.g. https://core.windows.net). -// -// The core endpoint URI is the same as given in "storage-endpoint" -// in Azure cloud definitions, which serves as the suffix for blob -// storage URLs. -func ResourceManagerResourceId(coreEndpointURI string) (string, error) { - u, err := url.Parse(coreEndpointURI) - if err != nil { - return "", err - } - u.Host = "management." + u.Host - return TokenResource(u.String()), nil -} - -// TokenResource returns a resource value suitable for auth tokens, based on -// an endpoint URI. -func TokenResource(uri string) string { - resource := uri - if !strings.HasSuffix(resource, "/") { - resource += "/" - } - return resource +// DataError is a go error that wraps the odataerrors.ODataError response type. +type DataError struct { + *odataerrors.ODataError } -// CheckForGraphError attempts to unmarshal the body into a GraphError. -// If this succeeds then the GraphError is returned as an error, -// otherwise the response is passed on to the next Responder. -func CheckForGraphError(r autorest.Responder) autorest.Responder { - return autorest.ResponderFunc(func(resp *http.Response) error { - err, _ := maybeGraphError(resp) - if err != nil { - return errors.Trace(err) - } - return r.Respond(resp) - }) +// Code returns the code from the wrapped DataError. +func (e *DataError) Code() string { + return *e.ODataError.GetErrorEscaped().GetCode() } -func maybeGraphError(resp *http.Response) (error, bool) { - if resp.Body != nil { - defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) - if err != nil { - return errors.Trace(err), false - } - resp.Body = io.NopCloser(bytes.NewReader(b)) - - // Remove any UTF-8 BOM, if present. - b = bytes.TrimPrefix(b, []byte("\ufeff")) - var ge graphrbac.GraphError - if err := json.Unmarshal(b, &ge); err == nil { - if ge.OdataError != nil && ge.Code != nil { - return &GraphError{ge}, true - } - } +// Message returns the message from the wrapped DataError. +func (e *DataError) Message() string { + msg := e.ODataError.GetErrorEscaped().GetMessage() + if msg != nil { + return *msg } - return nil, false -} - -// GraphError is a go error that wraps the graphrbac.GraphError response -// type, which doesn't implement the error interface. -type GraphError struct { - graphrbac.GraphError -} - -// Code returns the code from the GraphError. -func (e *GraphError) Code() string { - return *e.GraphError.Code -} - -// Message returns the message from the GraphError. -func (e *GraphError) Message() string { - if e.GraphError.OdataError == nil || e.GraphError.ErrorMessage == nil || e.GraphError.Message == nil { - return "" - } - return *e.GraphError.Message + return e.ODataError.Message } // Error implements the error interface. -func (e *GraphError) Error() string { +func (e *DataError) Error() string { s := e.Code() if m := e.Message(); m != "" { s += ": " + m @@ -105,21 +36,31 @@ func (e *GraphError) Error() string { return s } -// AsGraphError returns a GraphError if one is contained within the given -// error, otherwise it returns nil. -func AsGraphError(err error) *GraphError { - err = errors.Cause(err) - if de, ok := err.(autorest.DetailedError); ok { - err = de.Original +// AsDataError returns a wrapped error that exposes the +// underlying error code and message (if possible). +func AsDataError(err error) (*DataError, bool) { + if err == nil { + return nil, false } - if ge, _ := err.(*GraphError); ge != nil { - return ge + var dataErr *DataError + if errors.As(err, &dataErr) { + return dataErr, true } - if de, ok := err.(*azure.RequestError); ok { - ge, ok := maybeGraphError(de.Response) - if ok { - return ge.(*GraphError) - } + + var apiErr *odataerrors.ODataError + if !errors.As(err, &apiErr) { + return nil, false + } + return &DataError{apiErr}, true +} + +// ReportableError returns a wrapped error that exposes the +// underlying error code and message (if possible), or just +// the passed in error. +func ReportableError(err error) error { + var apiErr *odataerrors.ODataError + if !errors.As(err, &apiErr) { + return err } - return nil + return &DataError{apiErr} } diff --git a/provider/azure/internal/azureauth/utils_test.go b/provider/azure/internal/azureauth/utils_test.go index e3e2212e294..8b1be322ab3 100644 --- a/provider/azure/internal/azureauth/utils_test.go +++ b/provider/azure/internal/azureauth/utils_test.go @@ -4,155 +4,39 @@ package azureauth_test import ( - "io" - "net/http" - "strings" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" - "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" - "github.com/Azure/go-autorest/autorest" - "github.com/juju/errors" "github.com/juju/testing" jc "github.com/juju/testing/checkers" + "github.com/microsoft/kiota-abstractions-go/store" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" gc "gopkg.in/check.v1" "github.com/juju/juju/provider/azure/internal/azureauth" ) -type TokenResourceSuite struct { - testing.IsolationSuite -} - -var _ = gc.Suite(&TokenResourceSuite{}) - -func (s *TokenResourceSuite) TestTokenResource(c *gc.C) { - out := azureauth.TokenResource("https://graph.windows.net") - c.Assert(out, gc.Equals, "https://graph.windows.net/") - out = azureauth.TokenResource("https://graph.windows.net/") - c.Assert(out, gc.Equals, "https://graph.windows.net/") -} - -var checkForGraphErrorTests = []struct { - about string - responseBody string - expectError string -}{{ - about: "error body", - responseBody: `{"odata.error":{"code":"ErrorCode","message":{"value": "error message"}}}`, - expectError: "ErrorCode: error message", -}, { - about: "not error", - responseBody: `{}`, - expectError: "", -}, { - about: "error body with unicode BOM", - responseBody: "\ufeff" + `{"odata.error":{"code":"ErrorCode","message":{"value": "error message"}}}`, - expectError: "ErrorCode: error message", -}, { - about: "not error with unicode BOM", - responseBody: "\ufeff{}", - expectError: "", -}} - type ErrorSuite struct { testing.IsolationSuite } var _ = gc.Suite(&ErrorSuite{}) -func (ErrorSuite) TestCheckForGraphError(c *gc.C) { - for i, test := range checkForGraphErrorTests { - c.Logf("test %d. %s", i, test.about) - - var nextResponderCalled bool - var r autorest.Responder - r = autorest.ResponderFunc(func(resp *http.Response) error { - nextResponderCalled = true - - // If the next in the chain is called then the response body should be unchanged. - defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) - c.Assert(err, jc.ErrorIsNil) - c.Check(string(b), gc.Equals, test.responseBody) - return nil - }) - - r = azureauth.CheckForGraphError(r) - - err := r.Respond(&http.Response{Body: io.NopCloser(strings.NewReader(test.responseBody))}) - if test.expectError == "" { - c.Check(nextResponderCalled, gc.Equals, true) - continue - } - c.Assert(err, gc.Not(jc.ErrorIsNil)) - ge := azureauth.AsGraphError(err) - c.Check(ge, gc.Not(gc.IsNil)) - c.Check(err.Error(), gc.Equals, test.expectError) - } -} - -var asGraphErrorTests = []struct { - about string - err error - expectGraphError string -}{{ - about: "graph error", - err: &azureauth.GraphError{ - GraphError: graphrbac.GraphError{ - OdataError: &graphrbac.OdataError{ - Code: to.Ptr("ErrorCode"), - ErrorMessage: &graphrbac.ErrorMessage{ - Message: to.Ptr("error message"), - }, - }, - }, - }, - expectGraphError: "ErrorCode: error message", -}, { - about: "nil error", - err: nil, -}, { - about: "unrelated error", - err: errors.New("test error"), -}, { - about: "in autorest.DetailedError", - err: autorest.DetailedError{ - Original: &azureauth.GraphError{ - GraphError: graphrbac.GraphError{ - OdataError: &graphrbac.OdataError{ - Code: to.Ptr("ErrorCode"), - ErrorMessage: &graphrbac.ErrorMessage{ - Message: to.Ptr("error message"), - }, - }, - }, - }, - }, - expectGraphError: "ErrorCode: error message", -}, { - about: "traced graph error", - err: errors.Trace(&azureauth.GraphError{ - GraphError: graphrbac.GraphError{ - OdataError: &graphrbac.OdataError{ - Code: to.Ptr("ErrorCode"), - ErrorMessage: &graphrbac.ErrorMessage{ - Message: to.Ptr("error message"), - }, - }, - }, - }), - expectGraphError: "ErrorCode: error message", -}} - -func (ErrorSuite) TestAsGraphError(c *gc.C) { - for i, test := range asGraphErrorTests { - c.Logf("test %d. %s", i, test.about) - ge := azureauth.AsGraphError(test.err) - if test.expectGraphError == "" { - c.Check(ge, gc.IsNil) - continue - } - c.Assert(ge, gc.Not(gc.IsNil)) - c.Check(ge.Error(), gc.Equals, test.expectGraphError) - } +func (ErrorSuite) TestAsDataError(c *gc.C) { + dataErr := odataerrors.NewODataError() + dataErr.SetBackingStore(store.NewInMemoryBackingStore()) + me := odataerrors.NewMainError() + me.SetCode(to.Ptr("code")) + me.SetMessage(to.Ptr("message")) + dataErr.SetErrorEscaped(me) + + de, ok := azureauth.AsDataError(dataErr) + c.Assert(ok, jc.IsTrue) + c.Assert(de.Error(), gc.Equals, "code: message") + + _, ok = azureauth.AsDataError(nil) + c.Assert(ok, jc.IsFalse) + + azDataErr := &azureauth.DataError{} + de, ok = azureauth.AsDataError(azDataErr) + c.Assert(ok, jc.IsTrue) + c.Assert(de, jc.DeepEquals, azDataErr) } diff --git a/provider/azure/internal/azurecli/az.go b/provider/azure/internal/azurecli/az.go index 144c47f544f..39bfcb5e40d 100644 --- a/provider/azure/internal/azurecli/az.go +++ b/provider/azure/internal/azurecli/az.go @@ -10,7 +10,6 @@ import ( "os/exec" "strings" - "github.com/Azure/go-autorest/autorest/adal" "github.com/juju/errors" "github.com/juju/loggo" "github.com/kr/pretty" @@ -80,44 +79,6 @@ func (a AzureCLI) run(v interface{}, args ...string) error { return nil } -// AccessToken contains the result of the GetAccessToken function. -type AccessToken struct { - AccessToken string `json:"accessToken"` - ExpiresOn string `json:"expiresOn"` - Subscription string `json:"subscription"` - Tenant string `json:"tenant"` - TokenType string `json:"tokenType"` -} - -// Token creates an adal.Token from the AccessToken. This token can be -// used with go-autorest to access azure endpoints. -func (t AccessToken) Token() *adal.Token { - return &adal.Token{ - AccessToken: t.AccessToken, - Type: t.TokenType, - } -} - -// GetAccessToken gets an access token from the Azure CLI to access the -// given resource using the given subscription. Either subscription or -// resource may be empty in which case the default from the az -// application are used. -func (a AzureCLI) GetAccessToken(tenant, resource string) (*AccessToken, error) { - logger.Debugf("getting access token for tenant %q", tenant) - cmd := []string{"account", "get-access-token"} - if tenant != "" { - cmd = append(cmd, "--tenant", tenant) - } - if resource != "" { - cmd = append(cmd, "--resource", resource) - } - var tok AccessToken - if err := a.run(&tok, cmd...); err != nil { - return nil, errors.Trace(err) - } - return &tok, nil -} - // Account contains details of an azure account (subscription). type Account struct { CloudName string `json:"cloudName"` @@ -189,31 +150,9 @@ func (a AzureCLI) FindAccountsWithCloudName(name string) ([]Account, error) { // Cloud contains details of a cloud configured in the Azure CLI. type Cloud struct { - Endpoints CloudEndpoints `json:"endpoints"` - IsActive bool `json:"isActive"` - Name string `json:"name"` - Profile string `json:"profile"` - Suffixes CloudSuffixes `json:"suffixes"` -} - -// CloudEndpoints contains the endpoints used by a cloud. -type CloudEndpoints struct { - ActiveDirectory string `json:"activeDirectory"` - ActiveDirectoryGraphResourceID string `json:"activeDirectoryGraphResourceId"` - ActiveDirectoryResourceID string `json:"activeDirectoryResourceId"` - BatchResourceID string `json:"batchResourceId"` - Management string `json:"management"` - ResourceManager string `json:"resourceManager"` - SQLManagement string `json:"sqlManagement"` -} - -// CloudSuffixes contains the suffixes used with a cloud. -type CloudSuffixes struct { - AzureDatalakeAnalyticsCatalogAndJobEndpoint string `json:"azureDatalakeAnalyticsCatalogAndJobEndpoint"` - AzureDatalakeStoreFileSystemEndpoint string `json:"azureDatalakeStoreFileSystemEndpoint"` - KeyvaultDNS string `json:"keyvaultDns"` - SQLServerHostname string `json:"sqlServerHostname"` - StorageEndpoint string `json:"storageEndpoint"` + IsActive bool `json:"isActive"` + Name string `json:"name"` + Profile string `json:"profile"` } // ShowCloud returns the details of the cloud with the given name. If the @@ -230,22 +169,6 @@ func (a AzureCLI) ShowCloud(name string) (*Cloud, error) { return &cloud, nil } -// FindCloudsWithResourceManagerEndpoint returns a list of clouds which -// use the given url for it's resource manager endpoint. -func (a AzureCLI) FindCloudsWithResourceManagerEndpoint(url string) ([]Cloud, error) { - var clouds []Cloud - cmd := []string{ - "cloud", - "list", - "--query", - fmt.Sprintf("[?endpoints.resourceManager=='%s']", url), - } - if err := a.run(&clouds, cmd...); err != nil { - return nil, err - } - return clouds, nil -} - // ListClouds returns the details for all clouds available in the Azure // CLI. func (a AzureCLI) ListClouds() ([]Cloud, error) { diff --git a/provider/azure/internal/azurecli/az_test.go b/provider/azure/internal/azurecli/az_test.go index 8f058113317..e6ca9458a22 100644 --- a/provider/azure/internal/azurecli/az_test.go +++ b/provider/azure/internal/azurecli/az_test.go @@ -7,7 +7,6 @@ import ( "os/exec" "strings" - "github.com/Azure/go-autorest/autorest/adal" "github.com/juju/errors" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -19,109 +18,6 @@ type azSuite struct{} var _ = gc.Suite(&azSuite{}) -func (s *azSuite) TestGetAccessToken(c *gc.C) { - azcli := azurecli.AzureCLI{ - Exec: testExecutor{ - commands: map[string]result{ - "az account get-access-token -o json": { - stdout: []byte(` -{ - "accessToken": "ACCESSTOKEN", - "expiresOn": "2017-06-07 09:27:58.063743", - "subscription": "5544b9a5-0000-0000-0000-fedceb5d3696", - "tenant": "a52afd7f-0000-0000-0000-e47a54b982da", - "tokenType": "Bearer" -} -`[1:]), - }, - }, - }.Exec, - } - tok, err := azcli.GetAccessToken("", "") - c.Assert(err, jc.ErrorIsNil) - c.Assert(tok, jc.DeepEquals, &azurecli.AccessToken{ - AccessToken: "ACCESSTOKEN", - ExpiresOn: "2017-06-07 09:27:58.063743", - Subscription: "5544b9a5-0000-0000-0000-fedceb5d3696", - Tenant: "a52afd7f-0000-0000-0000-e47a54b982da", - TokenType: "Bearer", - }) -} - -func (s *azSuite) TestGetAccessTokenWithTenantAndResource(c *gc.C) { - azcli := azurecli.AzureCLI{ - Exec: testExecutor{ - commands: map[string]result{ - "az account get-access-token --tenant 5544b9a5-0000-0000-0000-fedceb5d3696 --resource resid -o json": { - stdout: []byte(` -{ - "accessToken": "ACCESSTOKEN", - "expiresOn": "2017-06-07 09:27:58.063743", - "subscription": "5544b9a5-0000-0000-0000-fedceb5d3696", - "tenant": "a52afd7f-0000-0000-0000-e47a54b982da", - "tokenType": "Bearer" -} -`[1:]), - }, - }, - }.Exec, - } - tok, err := azcli.GetAccessToken("5544b9a5-0000-0000-0000-fedceb5d3696", "resid") - c.Assert(err, jc.ErrorIsNil) - c.Assert(tok, jc.DeepEquals, &azurecli.AccessToken{ - AccessToken: "ACCESSTOKEN", - ExpiresOn: "2017-06-07 09:27:58.063743", - Subscription: "5544b9a5-0000-0000-0000-fedceb5d3696", - Tenant: "a52afd7f-0000-0000-0000-e47a54b982da", - TokenType: "Bearer", - }) -} - -func (s *azSuite) TestGetAccessTokenError(c *gc.C) { - azcli := azurecli.AzureCLI{ - Exec: testExecutor{ - commands: map[string]result{ - "az account get-access-token -o json": { - error: &exec.ExitError{Stderr: []byte("test error")}, - }, - }, - }.Exec, - } - tok, err := azcli.GetAccessToken("", "") - c.Assert(err, gc.ErrorMatches, `execution failure: test error`) - c.Assert(tok, gc.IsNil) -} - -func (s *azSuite) TestGetAccessTokenJSONError(c *gc.C) { - azcli := azurecli.AzureCLI{ - Exec: testExecutor{ - commands: map[string]result{ - "az account get-access-token -o json": { - stdout: []byte(`}`), - }, - }, - }.Exec, - } - tok, err := azcli.GetAccessToken("", "") - c.Assert(err, gc.ErrorMatches, `cannot unmarshal output: invalid character '}' looking for beginning of value`) - c.Assert(tok, gc.IsNil) -} - -func (s *azSuite) TestAzureTokenFromAccessToken(c *gc.C) { - tok := azurecli.AccessToken{ - AccessToken: "0123456789", - ExpiresOn: "2017-06-05 10:20:43.752534", - Subscription: "00000000-0000-0000-0000-00000001", - Tenant: "00000000-0000-0000-0000-00000002", - TokenType: "Bearer", - } - tok1 := tok.Token() - c.Assert(tok1, jc.DeepEquals, &adal.Token{ - AccessToken: "0123456789", - Type: "Bearer", - }) -} - func (s *azSuite) TestShowAccount(c *gc.C) { azcli := azurecli.AzureCLI{ Exec: testExecutor{ @@ -362,26 +258,9 @@ func (s *azSuite) TestShowCloud(c *gc.C) { "az cloud show -o json": { stdout: []byte(` { - "endpoints": { - "activeDirectory": "https://login.microsoftonline.com", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "activeDirectoryResourceId": "https://management.core.windows.net/", - "batchResourceId": "https://batch.core.windows.net/", - "gallery": "https://gallery.azure.com/", - "management": "https://management.core.windows.net/", - "resourceManager": "https://management.azure.com/", - "sqlManagement": "https://management.core.windows.net:8443/" - }, "isActive": true, "name": "AzureCloud", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": "azuredatalakeanalytics.net", - "azureDatalakeStoreFileSystemEndpoint": "azuredatalakestore.net", - "keyvaultDns": ".vault.azure.net", - "sqlServerHostname": ".database.windows.net", - "storageEndpoint": "core.windows.net" - } + "profile": "latest" } `[1:]), }, @@ -391,25 +270,9 @@ func (s *azSuite) TestShowCloud(c *gc.C) { cloud, err := azcli.ShowCloud("") c.Assert(err, jc.ErrorIsNil) c.Assert(cloud, jc.DeepEquals, &azurecli.Cloud{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.microsoftonline.com", - ActiveDirectoryGraphResourceID: "https://graph.windows.net/", - ActiveDirectoryResourceID: "https://management.core.windows.net/", - BatchResourceID: "https://batch.core.windows.net/", - Management: "https://management.core.windows.net/", - ResourceManager: "https://management.azure.com/", - SQLManagement: "https://management.core.windows.net:8443/", - }, IsActive: true, Name: "AzureCloud", Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - AzureDatalakeAnalyticsCatalogAndJobEndpoint: "azuredatalakeanalytics.net", - AzureDatalakeStoreFileSystemEndpoint: "azuredatalakestore.net", - KeyvaultDNS: ".vault.azure.net", - SQLServerHostname: ".database.windows.net", - StorageEndpoint: "core.windows.net", - }, }) } @@ -420,26 +283,9 @@ func (s *azSuite) TestShowCloudWithName(c *gc.C) { "az cloud show --name AzureUSGovernment -o json": { stdout: []byte(` { - "endpoints": { - "activeDirectory": "https://login.microsoftonline.com", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "activeDirectoryResourceId": "https://management.core.usgovcloudapi.net/", - "batchResourceId": "https://batch.core.usgovcloudapi.net/", - "gallery": "https://gallery.usgovcloudapi.net/", - "management": "https://management.core.usgovcloudapi.net/", - "resourceManager": "https://management.usgovcloudapi.net/", - "sqlManagement": "https://management.core.usgovcloudapi.net:8443/" - }, "isActive": false, "name": "AzureUSGovernment", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": null, - "azureDatalakeStoreFileSystemEndpoint": null, - "keyvaultDns": ".vault.usgovcloudapi.net", - "sqlServerHostname": ".database.usgovcloudapi.net", - "storageEndpoint": "core.usgovcloudapi.net" - } + "profile": "latest" } `[1:]), }, @@ -449,23 +295,9 @@ func (s *azSuite) TestShowCloudWithName(c *gc.C) { cloud, err := azcli.ShowCloud("AzureUSGovernment") c.Assert(err, jc.ErrorIsNil) c.Assert(cloud, jc.DeepEquals, &azurecli.Cloud{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.microsoftonline.com", - ActiveDirectoryGraphResourceID: "https://graph.windows.net/", - ActiveDirectoryResourceID: "https://management.core.usgovcloudapi.net/", - BatchResourceID: "https://batch.core.usgovcloudapi.net/", - Management: "https://management.core.usgovcloudapi.net/", - ResourceManager: "https://management.usgovcloudapi.net/", - SQLManagement: "https://management.core.usgovcloudapi.net:8443/", - }, IsActive: false, Name: "AzureUSGovernment", Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - KeyvaultDNS: ".vault.usgovcloudapi.net", - SQLServerHostname: ".database.usgovcloudapi.net", - StorageEndpoint: "core.usgovcloudapi.net", - }, }) } @@ -484,81 +316,6 @@ func (s *azSuite) TestShowCloudError(c *gc.C) { c.Assert(cloud, gc.IsNil) } -func (s *azSuite) TestFindCloudsWithResourceManagerEndpoint(c *gc.C) { - azcli := azurecli.AzureCLI{ - Exec: testExecutor{ - commands: map[string]result{ - "az cloud list --query [?endpoints.resourceManager=='https://management.azure.com/'] -o json": { - stdout: []byte(` -[ - { - "endpoints": { - "activeDirectory": "https://login.microsoftonline.com", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "activeDirectoryResourceId": "https://management.core.windows.net/", - "batchResourceId": "https://batch.core.windows.net/", - "gallery": "https://gallery.azure.com/", - "management": "https://management.core.windows.net/", - "resourceManager": "https://management.azure.com/", - "sqlManagement": "https://management.core.windows.net:8443/" - }, - "isActive": true, - "name": "AzureCloud", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": "azuredatalakeanalytics.net", - "azureDatalakeStoreFileSystemEndpoint": "azuredatalakestore.net", - "keyvaultDns": ".vault.azure.net", - "sqlServerHostname": ".database.windows.net", - "storageEndpoint": "core.windows.net" - } - } -] -`[1:]), - }, - }, - }.Exec, - } - cloud, err := azcli.FindCloudsWithResourceManagerEndpoint("https://management.azure.com/") - c.Assert(err, jc.ErrorIsNil) - c.Assert(cloud, jc.DeepEquals, []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.microsoftonline.com", - ActiveDirectoryGraphResourceID: "https://graph.windows.net/", - ActiveDirectoryResourceID: "https://management.core.windows.net/", - BatchResourceID: "https://batch.core.windows.net/", - Management: "https://management.core.windows.net/", - ResourceManager: "https://management.azure.com/", - SQLManagement: "https://management.core.windows.net:8443/", - }, - IsActive: true, - Name: "AzureCloud", - Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - AzureDatalakeAnalyticsCatalogAndJobEndpoint: "azuredatalakeanalytics.net", - AzureDatalakeStoreFileSystemEndpoint: "azuredatalakestore.net", - KeyvaultDNS: ".vault.azure.net", - SQLServerHostname: ".database.windows.net", - StorageEndpoint: "core.windows.net", - }, - }}) -} - -func (s *azSuite) TestFindCloudsWithResourceManagerEndpointError(c *gc.C) { - azcli := azurecli.AzureCLI{ - Exec: testExecutor{ - commands: map[string]result{ - "az cloud list --query [?endpoints.resourceManager=='https://management.azure.com/'] -o json": { - error: errors.New("test error"), - }, - }, - }.Exec, - } - cloud, err := azcli.FindCloudsWithResourceManagerEndpoint("https://management.azure.com/") - c.Assert(err, gc.ErrorMatches, `execution failure: test error`) - c.Assert(cloud, gc.IsNil) -} - func (s *azSuite) TestListClouds(c *gc.C) { azcli := azurecli.AzureCLI{ Exec: testExecutor{ @@ -567,92 +324,24 @@ func (s *azSuite) TestListClouds(c *gc.C) { stdout: []byte(` [ { - "endpoints": { - "activeDirectory": "https://login.microsoftonline.com", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "activeDirectoryResourceId": "https://management.core.windows.net/", - "batchResourceId": "https://batch.core.windows.net/", - "gallery": "https://gallery.azure.com/", - "management": "https://management.core.windows.net/", - "resourceManager": "https://management.azure.com/", - "sqlManagement": "https://management.core.windows.net:8443/" - }, "isActive": true, "name": "AzureCloud", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": "azuredatalakeanalytics.net", - "azureDatalakeStoreFileSystemEndpoint": "azuredatalakestore.net", - "keyvaultDns": ".vault.azure.net", - "sqlServerHostname": ".database.windows.net", - "storageEndpoint": "core.windows.net" - } + "profile": "latest" }, { - "endpoints": { - "activeDirectory": "https://login.chinacloudapi.cn", - "activeDirectoryGraphResourceId": "https://graph.chinacloudapi.cn/", - "activeDirectoryResourceId": "https://management.core.chinacloudapi.cn/", - "batchResourceId": "https://batch.chinacloudapi.cn/", - "gallery": "https://gallery.chinacloudapi.cn/", - "management": "https://management.core.chinacloudapi.cn/", - "resourceManager": "https://management.chinacloudapi.cn", - "sqlManagement": "https://management.core.chinacloudapi.cn:8443/" - }, "isActive": false, "name": "AzureChinaCloud", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": null, - "azureDatalakeStoreFileSystemEndpoint": null, - "keyvaultDns": ".vault.azure.cn", - "sqlServerHostname": ".database.chinacloudapi.cn", - "storageEndpoint": "core.chinacloudapi.cn" - } + "profile": "latest" }, { - "endpoints": { - "activeDirectory": "https://login.microsoftonline.com", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "activeDirectoryResourceId": "https://management.core.usgovcloudapi.net/", - "batchResourceId": "https://batch.core.usgovcloudapi.net/", - "gallery": "https://gallery.usgovcloudapi.net/", - "management": "https://management.core.usgovcloudapi.net/", - "resourceManager": "https://management.usgovcloudapi.net/", - "sqlManagement": "https://management.core.usgovcloudapi.net:8443/" - }, "isActive": false, "name": "AzureUSGovernment", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": null, - "azureDatalakeStoreFileSystemEndpoint": null, - "keyvaultDns": ".vault.usgovcloudapi.net", - "sqlServerHostname": ".database.usgovcloudapi.net", - "storageEndpoint": "core.usgovcloudapi.net" - } + "profile": "latest" }, { - "endpoints": { - "activeDirectory": "https://login.microsoftonline.de", - "activeDirectoryGraphResourceId": "https://graph.cloudapi.de/", - "activeDirectoryResourceId": "https://management.core.cloudapi.de/", - "batchResourceId": "https://batch.cloudapi.de/", - "gallery": "https://gallery.cloudapi.de/", - "management": "https://management.core.cloudapi.de/", - "resourceManager": "https://management.microsoftazure.de", - "sqlManagement": "https://management.core.cloudapi.de:8443/" - }, "isActive": false, "name": "AzureGermanCloud", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": null, - "azureDatalakeStoreFileSystemEndpoint": null, - "keyvaultDns": ".vault.microsoftazure.de", - "sqlServerHostname": ".database.cloudapi.de", - "storageEndpoint": "core.cloudapi.de" - } + "profile": "latest" } ] @@ -664,79 +353,21 @@ func (s *azSuite) TestListClouds(c *gc.C) { clouds, err := azcli.ListClouds() c.Assert(err, jc.ErrorIsNil) c.Assert(clouds, jc.DeepEquals, []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.microsoftonline.com", - ActiveDirectoryGraphResourceID: "https://graph.windows.net/", - ActiveDirectoryResourceID: "https://management.core.windows.net/", - BatchResourceID: "https://batch.core.windows.net/", - Management: "https://management.core.windows.net/", - ResourceManager: "https://management.azure.com/", - SQLManagement: "https://management.core.windows.net:8443/", - }, IsActive: true, Name: "AzureCloud", Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - AzureDatalakeAnalyticsCatalogAndJobEndpoint: "azuredatalakeanalytics.net", - AzureDatalakeStoreFileSystemEndpoint: "azuredatalakestore.net", - KeyvaultDNS: ".vault.azure.net", - SQLServerHostname: ".database.windows.net", - StorageEndpoint: "core.windows.net", - }, }, { - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.chinacloudapi.cn", - ActiveDirectoryGraphResourceID: "https://graph.chinacloudapi.cn/", - ActiveDirectoryResourceID: "https://management.core.chinacloudapi.cn/", - BatchResourceID: "https://batch.chinacloudapi.cn/", - Management: "https://management.core.chinacloudapi.cn/", - ResourceManager: "https://management.chinacloudapi.cn", - SQLManagement: "https://management.core.chinacloudapi.cn:8443/", - }, IsActive: false, Name: "AzureChinaCloud", Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - KeyvaultDNS: ".vault.azure.cn", - SQLServerHostname: ".database.chinacloudapi.cn", - StorageEndpoint: "core.chinacloudapi.cn", - }, }, { - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.microsoftonline.com", - ActiveDirectoryGraphResourceID: "https://graph.windows.net/", - ActiveDirectoryResourceID: "https://management.core.usgovcloudapi.net/", - BatchResourceID: "https://batch.core.usgovcloudapi.net/", - Management: "https://management.core.usgovcloudapi.net/", - ResourceManager: "https://management.usgovcloudapi.net/", - SQLManagement: "https://management.core.usgovcloudapi.net:8443/", - }, IsActive: false, Name: "AzureUSGovernment", Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - KeyvaultDNS: ".vault.usgovcloudapi.net", - SQLServerHostname: ".database.usgovcloudapi.net", - StorageEndpoint: "core.usgovcloudapi.net", - }, }, { - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.microsoftonline.de", - ActiveDirectoryGraphResourceID: "https://graph.cloudapi.de/", - ActiveDirectoryResourceID: "https://management.core.cloudapi.de/", - BatchResourceID: "https://batch.cloudapi.de/", - Management: "https://management.core.cloudapi.de/", - ResourceManager: "https://management.microsoftazure.de", - SQLManagement: "https://management.core.cloudapi.de:8443/", - }, IsActive: false, Name: "AzureGermanCloud", Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - KeyvaultDNS: ".vault.microsoftazure.de", - SQLServerHostname: ".database.cloudapi.de", - StorageEndpoint: "core.cloudapi.de", - }, }}) } diff --git a/provider/azure/internal/azuretesting/json.go b/provider/azure/internal/azuretesting/json.go deleted file mode 100644 index 86a1ebb789c..00000000000 --- a/provider/azure/internal/azuretesting/json.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2020 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package azuretesting - -import ( - "encoding/json" - "fmt" - "reflect" -) - -// JsonMarshalRaw does the same a json.Marshal, except that it does not -// use the MarshalJSON methods of the value being marshaled. If any types -// are specified in the allow set then those will use their MarshalJSON -// method. -// -// Many of the types in the Azure SDK have MarshalJSON which skip over -// fields that are marked as READ-ONLY, this is useful for the client, -// but a problem when pretending to be a server. -func JsonMarshalRaw(v interface{}, allow ...reflect.Type) ([]byte, error) { - allowed := make(map[reflect.Type]bool, len(allow)) - for _, a := range allow { - allowed[a] = true - } - if v != nil { - v = rawValueMaker{allowed}.rawValue(reflect.ValueOf(v)).Interface() - } - return json.Marshal(v) -} - -type rawValueMaker struct { - allowed map[reflect.Type]bool -} - -func (m rawValueMaker) rawValue(v reflect.Value) reflect.Value { - t := v.Type() - if m.allowed[t] { - return v - } - switch t.Kind() { - case reflect.Ptr: - return m.rawPointerValue(v) - case reflect.Struct: - return m.rawStructValue(v) - case reflect.Map: - return m.rawMapValue(v) - case reflect.Slice: - return m.rawSliceValue(v) - default: - return v - } -} - -func (m rawValueMaker) rawPointerValue(v reflect.Value) reflect.Value { - if v.IsNil() { - return v - } - rv := m.rawValue(v.Elem()) - if rv.CanAddr() { - return rv.Addr() - } - pv := reflect.New(rv.Type()) - pv.Elem().Set(rv) - return pv -} - -func (m rawValueMaker) rawStructValue(v reflect.Value) reflect.Value { - t := v.Type() - - fields := make([]reflect.StructField, 0, t.NumField()) - values := make([]reflect.Value, 0, t.NumField()) - - for i := 0; i < t.NumField(); i++ { - sf := t.Field(i) - if sf.PkgPath != "" || sf.Tag.Get("json") == "-" { - // Skip fields that won't ever be marshaled. - continue - } - if tag, ok := sf.Tag.Lookup("json"); ok && tag == "" { - // Also skip fields with a present, but empty, json tag. - continue - } - rv := m.rawValue(v.Field(i)) - sf.Type = rv.Type() - sf.Anonymous = false - // Azure auth tokens use json.Number for strings and ints which confuses the SDK - // so send across the wire as string for affected fields. - if t.Name() == "Token" && (sf.Name == "ExpiresOn" || sf.Name == "ExpiresIn") { - val := fmt.Sprint(v.Field(i).Interface()) - if val != "" { - sf.Type = reflect.TypeOf("") - rv = reflect.ValueOf(fmt.Sprint(v.Field(i).Interface())) - } - } - fields = append(fields, sf) - values = append(values, rv) - } - - newT := reflect.StructOf(fields) - newV := reflect.New(newT).Elem() - - for i, v := range values { - newV.Field(i).Set(v) - } - - return newV -} - -var interfaceType = reflect.TypeOf((*interface{})(nil)).Elem() - -func (m rawValueMaker) rawMapValue(v reflect.Value) reflect.Value { - newV := reflect.MakeMap(reflect.MapOf(v.Type().Key(), interfaceType)) - for _, key := range v.MapKeys() { - value := v.MapIndex(key) - newV.SetMapIndex(key, m.rawValue(value)) - } - - return newV -} - -func (m rawValueMaker) rawSliceValue(v reflect.Value) reflect.Value { - newV := reflect.MakeSlice(reflect.SliceOf(interfaceType), v.Len(), v.Len()) - for i := 0; i < v.Len(); i++ { - newV.Index(i).Set(m.rawValue(v.Index(i))) - } - return newV -} diff --git a/provider/azure/internal/azuretesting/recorder.go b/provider/azure/internal/azuretesting/recorder.go index 7eb7a61a8c8..61e2bf29286 100644 --- a/provider/azure/internal/azuretesting/recorder.go +++ b/provider/azure/internal/azuretesting/recorder.go @@ -10,7 +10,6 @@ import ( "sync" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - "github.com/Azure/go-autorest/autorest" ) type RequestRecorderPolicy struct { @@ -41,33 +40,3 @@ func (p *RequestRecorderPolicy) Do(req *policy.Request) (*http.Response, error) return resp, err } - -// RequestRecorder returns an autorest.PrepareDecorator that records requests -// to ghe given slice. -func RequestRecorder(requests *[]*http.Request) autorest.PrepareDecorator { - if requests == nil { - return nil - } - var mu sync.Mutex - return func(p autorest.Preparer) autorest.Preparer { - return autorest.PreparerFunc(func(req *http.Request) (*http.Request, error) { - // Save the request body, since it will be consumed. - reqCopy := *req - if req.Body != nil { - var buf bytes.Buffer - if _, err := buf.ReadFrom(req.Body); err != nil { - return nil, err - } - if err := req.Body.Close(); err != nil { - return nil, err - } - reqCopy.Body = io.NopCloser(&buf) - req.Body = io.NopCloser(bytes.NewReader(buf.Bytes())) - } - mu.Lock() - *requests = append(*requests, &reqCopy) - mu.Unlock() - return req, nil - }) - } -} diff --git a/provider/azure/internal/azuretesting/senders.go b/provider/azure/internal/azuretesting/senders.go index 84f692345b8..99a6b1fba5e 100644 --- a/provider/azure/internal/azuretesting/senders.go +++ b/provider/azure/internal/azuretesting/senders.go @@ -5,15 +5,16 @@ package azuretesting import ( "context" + "encoding/json" "fmt" + "io" "net/http" "regexp" "sync" + "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/mocks" "github.com/juju/loggo" ) @@ -26,17 +27,130 @@ func (c *FakeCredential) GetToken(ctx context.Context, opts policy.TokenRequestO return azcore.AccessToken{Token: "FakeToken"}, nil } -// MockSender is a wrapper around autorest/mocks.Sender, extending it with -// request path checking to ease testing. +// Body implements acceptable body over a string. +type Body struct { + src []byte + buf []byte + isOpen bool +} + +// NewBody creates a new instance of Body. +func NewBody(s string) *Body { + return (&Body{src: []byte(s)}).reset() +} + +// Read reads into the passed byte slice and returns the bytes read. +func (body *Body) Read(b []byte) (n int, err error) { + if !body.IsOpen() { + return 0, fmt.Errorf("ERROR: Body has been closed") + } + if len(body.buf) == 0 { + return 0, io.EOF + } + n = copy(b, body.buf) + body.buf = body.buf[n:] + return n, nil +} + +// Close closes the body. +func (body *Body) Close() error { + if body.isOpen { + body.isOpen = false + } + return nil +} + +// IsOpen returns true if the Body has not been closed, false otherwise. +func (body *Body) IsOpen() bool { + return body.isOpen +} + +func (body *Body) reset() *Body { + body.isOpen = true + body.buf = body.src + return body +} + +// Length returns the number of bytes in the body. +func (body *Body) Length() int64 { + if body == nil { + return 0 + } + return int64(len(body.src)) +} + +// NewRequest instantiates a new request. +func NewRequest() *http.Request { + return NewRequestWithContent("") +} + +// NewRequestWithContent instantiates a new request using the passed string for the body content. +func NewRequestWithContent(c string) *http.Request { + r, _ := http.NewRequest("GET", "https://microsoft.com/a/b/c/", NewBody(c)) + return r +} + +// NewResponse instantiates a new response. +func NewResponse() *http.Response { + return NewResponseWithContent("") +} + +// NewResponseWithContent instantiates a new response with the passed string as the body content. +func NewResponseWithContent(c string) *http.Response { + return &http.Response{ + Status: "200 OK", + StatusCode: 200, + Proto: "HTTP/1.0", + ProtoMajor: 1, + ProtoMinor: 0, + Body: NewBody(c), + Request: NewRequest(), + } +} + +// NewResponseWithStatus instantiates a new response using the passed string and integer as the +// status and status code. +func NewResponseWithStatus(s string, c int) *http.Response { + resp := NewResponse() + resp.Status = s + resp.StatusCode = c + return resp +} + +// NewResponseWithBodyAndStatus instantiates a new response using the specified mock body, +// status and status code +func NewResponseWithBodyAndStatus(body *Body, c int, s string) *http.Response { + resp := NewResponse() + resp.Body = body + resp.ContentLength = body.Length() + resp.Status = s + resp.StatusCode = c + return resp +} + +type response struct { + r *http.Response + e error + d time.Duration +} + +// MockSender provides a mechanism to test requests and +// provide responses to/from the Azure cloud API. type MockSender struct { - *mocks.Sender + attempts int + responses []response + numResponses int + repeatResponse []int + err error + repeatError int // PathPattern, if non-empty, is assumed to be a regular expression // that must match the request path. PathPattern string } -func (s *MockSender) Do(req *http.Request) (*http.Response, error) { +// Do implements policy.Policy. +func (s *MockSender) Do(req *http.Request) (resp *http.Response, err error) { if s.PathPattern != "" { matched, err := regexp.MatchString(s.PathPattern, req.URL.Path) if err != nil { @@ -49,7 +163,89 @@ func (s *MockSender) Do(req *http.Request) (*http.Response, error) { ) } } - return s.Sender.Do(req) + s.attempts++ + + if len(s.responses) > 0 { + resp = s.responses[0].r + if resp != nil { + if b, ok := resp.Body.(*Body); ok { + b.reset() + } + } else { + err = s.responses[0].e + } + select { + case <-time.After(s.responses[0].d): + // do nothing + case <-req.Context().Done(): + err = req.Context().Err() + return + } + s.repeatResponse[0]-- + if s.repeatResponse[0] == 0 { + s.responses = s.responses[1:] + s.repeatResponse = s.repeatResponse[1:] + } + } else { + resp = NewResponse() + } + if resp != nil { + resp.Request = req + } + + if s.err != nil { + err = s.err + s.repeatError-- + if s.repeatError == 0 { + s.err = nil + } + } + + return +} + +// AppendResponse adds the passed http.Response to the response stack. +func (c *MockSender) AppendResponse(resp *http.Response) { + c.AppendAndRepeatResponse(resp, 1) +} + +// AppendAndRepeatResponse adds the passed http.Response to the response stack along with a +// repeat count. A negative repeat count will return the response for all remaining calls to Do. +func (c *MockSender) AppendAndRepeatResponse(resp *http.Response, repeat int) { + c.appendAndRepeat(response{r: resp}, repeat) +} + +func (c *MockSender) appendAndRepeat(resp response, repeat int) { + if c.responses == nil { + c.responses = []response{resp} + c.repeatResponse = []int{repeat} + } else { + c.responses = append(c.responses, resp) + c.repeatResponse = append(c.repeatResponse, repeat) + } + c.numResponses++ +} + +// Attempts returns the number of times Do was called. +func (c *MockSender) Attempts() int { + return c.attempts +} + +// SetError sets the error Do should return. +func (c *MockSender) SetError(err error) { + c.SetAndRepeatError(err, 1) +} + +// SetAndRepeatError sets the error Do should return and how many calls to Do will return the error. +// A negative repeat value will return the error for all remaining calls to Do. +func (c *MockSender) SetAndRepeatError(err error, repeat int) { + c.err = err + c.repeatError = repeat +} + +// NumResponses returns the number of responses that have been added to the sender. +func (c *MockSender) NumResponses() int { + return c.numResponses } const fakeTenantId = "11111111-1111-1111-1111-111111111111" @@ -58,13 +254,13 @@ const fakeTenantId = "11111111-1111-1111-1111-111111111111" // to JSON and sets it as the content. This function will panic if marshalling // fails. func NewSenderWithValue(v interface{}) *MockSender { - content, err := JsonMarshalRaw(v) + content, err := json.Marshal(v) if err != nil { panic(err) } - sender := &MockSender{Sender: mocks.NewSender()} - resp := mocks.NewResponseWithContent(string(content)) - mocks.SetResponseHeaderValues(resp, "WWW-Authenticate", []string{ + sender := &MockSender{} + resp := NewResponseWithContent(string(content)) + SetResponseHeaderValues(resp, "WWW-Authenticate", []string{ fmt.Sprintf( `authorization="https://testing.invalid/%s" scope="scope" resource="resource"`, fakeTenantId, @@ -74,14 +270,24 @@ func NewSenderWithValue(v interface{}) *MockSender { return sender } +// SetResponseHeaderValues adds a header containing all the passed string values. +func SetResponseHeaderValues(resp *http.Response, h string, values []string) { + if resp.Header == nil { + resp.Header = make(http.Header) + } + for _, v := range values { + resp.Header.Add(h, v) + } +} + // Senders is a Sender that includes a collection of Senders, which // will be called in sequence. -type Senders []autorest.Sender +type Senders []policy.Transporter func (s *Senders) Do(req *http.Request) (*http.Response, error) { logger.Debugf("Senders.Do(%s)", req.URL) if len(*s) == 0 { - response := mocks.NewResponseWithStatus("", http.StatusInternalServerError) + response := NewResponseWithStatus("", http.StatusInternalServerError) return response, fmt.Errorf("no sender for %q", req.URL) } sender := (*s)[0] @@ -95,7 +301,7 @@ func (s *Senders) Do(req *http.Request) (*http.Response, error) { // at a time. type SerialSender struct { mu sync.Mutex - s autorest.Sender + s policy.Transporter } func (s *SerialSender) Do(req *http.Request) (*http.Response, error) { @@ -104,6 +310,6 @@ func (s *SerialSender) Do(req *http.Request) (*http.Response, error) { return s.s.Do(req) } -func NewSerialSender(s autorest.Sender) *SerialSender { +func NewSerialSender(s policy.Transporter) *SerialSender { return &SerialSender{s: s} } diff --git a/provider/azure/internal/imageutils/images_test.go b/provider/azure/internal/imageutils/images_test.go index e90ee94bbab..7b56920423b 100644 --- a/provider/azure/internal/imageutils/images_test.go +++ b/provider/azure/internal/imageutils/images_test.go @@ -9,7 +9,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" - "github.com/Azure/go-autorest/autorest/mocks" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v3/arch" gc "gopkg.in/check.v1" @@ -25,7 +24,7 @@ import ( type imageutilsSuite struct { testing.BaseSuite - mockSender *mocks.Sender + mockSender *azuretesting.MockSender client *armcompute.VirtualMachineImagesClient callCtx *context.CloudCallContext } @@ -34,7 +33,7 @@ var _ = gc.Suite(&imageutilsSuite{}) func (s *imageutilsSuite) SetUpTest(c *gc.C) { s.BaseSuite.SetUpTest(c) - s.mockSender = mocks.NewSender() + s.mockSender = &azuretesting.MockSender{} opts := &arm.ClientOptions{ ClientOptions: policy.ClientOptions{ Transport: s.mockSender, @@ -47,7 +46,7 @@ func (s *imageutilsSuite) SetUpTest(c *gc.C) { } func (s *imageutilsSuite) TestSeriesImage(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithContent( + s.mockSender.AppendResponse(azuretesting.NewResponseWithContent( `[{"name": "20_04"}, {"name": "20_04-LTS"}, {"name": "19_04"}]`, )) image, err := imageutils.SeriesImage(s.callCtx, corebase.MakeDefaultBase("ubuntu", "20.04"), "released", "westus", s.client) @@ -61,7 +60,7 @@ func (s *imageutilsSuite) TestSeriesImage(c *gc.C) { } func (s *imageutilsSuite) TestSeriesImageInvalidSKU(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithContent( + s.mockSender.AppendResponse(azuretesting.NewResponseWithContent( `[{"name": "22_04_invalid"}, {"name": "22_04_5-LTS"}]`, )) image, err := imageutils.SeriesImage(s.callCtx, corebase.MakeDefaultBase("ubuntu", "22.04"), "released", "westus", s.client) @@ -82,7 +81,7 @@ func (s *imageutilsSuite) TestSeriesImageCentOS(c *gc.C) { } func (s *imageutilsSuite) TestSeriesImageStream(c *gc.C) { - s.mockSender.AppendAndRepeatResponse(mocks.NewResponseWithContent( + s.mockSender.AppendAndRepeatResponse(azuretesting.NewResponseWithContent( `[{"name": "22_04_2"}, {"name": "22_04_3-DAILY"}, {"name": "22_04_1-LTS"}]`), 2) base := corebase.MakeDefaultBase("ubuntu", "22.04") s.assertImageId(c, base, "daily", "Canonical:0001-com-ubuntu-server-jammy:22_04_3-DAILY:latest") @@ -90,20 +89,20 @@ func (s *imageutilsSuite) TestSeriesImageStream(c *gc.C) { } func (s *imageutilsSuite) TestSeriesImageNotFound(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithContent(`[]`)) + s.mockSender.AppendResponse(azuretesting.NewResponseWithContent(`[]`)) image, err := imageutils.SeriesImage(s.callCtx, corebase.MakeDefaultBase("ubuntu", "22.04"), "released", "westus", s.client) c.Assert(err, gc.ErrorMatches, "selecting SKU for ubuntu@22.04: Ubuntu SKUs for released stream not found") c.Assert(image, gc.IsNil) } func (s *imageutilsSuite) TestSeriesImageStreamNotFound(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithContent(`[{"name": "22_04-beta1"}]`)) + s.mockSender.AppendResponse(azuretesting.NewResponseWithContent(`[{"name": "22_04-beta1"}]`)) _, err := imageutils.SeriesImage(s.callCtx, corebase.MakeDefaultBase("ubuntu", "22.04"), "whatever", "westus", s.client) c.Assert(err, gc.ErrorMatches, "selecting SKU for ubuntu@22.04: Ubuntu SKUs for whatever stream not found") } func (s *imageutilsSuite) TestSeriesImageStreamThrewCredentialError(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized)) + s.mockSender.AppendResponse(azuretesting.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized)) called := false s.callCtx.InvalidateCredentialFunc = func(string) error { called = true @@ -116,7 +115,7 @@ func (s *imageutilsSuite) TestSeriesImageStreamThrewCredentialError(c *gc.C) { } func (s *imageutilsSuite) TestSeriesImageStreamThrewNonCredentialError(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithStatus("308 Permanent Redirect", http.StatusPermanentRedirect)) + s.mockSender.AppendResponse(azuretesting.NewResponseWithStatus("308 Permanent Redirect", http.StatusPermanentRedirect)) called := false s.callCtx.InvalidateCredentialFunc = func(string) error { called = true diff --git a/provider/azure/internal/tracing/tracing.go b/provider/azure/internal/tracing/tracing.go index 8998be2b690..43bb16deffa 100644 --- a/provider/azure/internal/tracing/tracing.go +++ b/provider/azure/internal/tracing/tracing.go @@ -8,7 +8,6 @@ import ( "net/http/httputil" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - "github.com/Azure/go-autorest/autorest" "github.com/juju/loggo" ) @@ -38,41 +37,3 @@ func (p *LoggingPolicy) Do(req *policy.Request) (*http.Response, error) { } return resp, err } - -// PrepareDecorator returns an autorest.PrepareDecorator that -// logs requests at trace level. -func PrepareDecorator(logger loggo.Logger) autorest.PrepareDecorator { - return func(p autorest.Preparer) autorest.Preparer { - return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) { - if logger.IsTraceEnabled() { - dump, err := httputil.DumpRequest(r, true) - if err != nil { - logger.Tracef("failed to dump request: %v", err) - logger.Tracef("%+v", r) - } else { - logger.Tracef("%s", dump) - } - } - return p.Prepare(r) - }) - } -} - -// RespondDecorator returns an autorest.RespondDecorator that -// logs responses at trace level. -func RespondDecorator(logger loggo.Logger) autorest.RespondDecorator { - return func(r autorest.Responder) autorest.Responder { - return autorest.ResponderFunc(func(resp *http.Response) error { - if logger.IsTraceEnabled() { - dump, err := httputil.DumpResponse(resp, true) - if err != nil { - logger.Tracef("failed to dump response: %v", err) - logger.Tracef("%+v", resp) - } else { - logger.Tracef("%s", dump) - } - } - return r.Respond(resp) - }) - } -} diff --git a/provider/azure/internal/useragent/useragent.go b/provider/azure/internal/useragent/useragent.go deleted file mode 100644 index b8f44dca273..00000000000 --- a/provider/azure/internal/useragent/useragent.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2017 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package useragent - -import ( - "github.com/Azure/go-autorest/autorest" - - "github.com/juju/juju/version" -) - -// JujuPrefix returns the User-Agent prefix set by Juju. -func JujuPrefix() string { - return "Juju/" + version.Current.String() -} - -// UpdateClient updates the UserAgent field of the given autorest.Client. -func UpdateClient(client *autorest.Client) { - if client.UserAgent == "" { - client.UserAgent = JujuPrefix() - } else { - client.UserAgent = JujuPrefix() + " " + client.UserAgent - } -} diff --git a/provider/azure/storage_test.go b/provider/azure/storage_test.go index 3adb6426682..bd6cdf72c45 100644 --- a/provider/azure/storage_test.go +++ b/provider/azure/storage_test.go @@ -11,11 +11,10 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" - autorestazure "github.com/Azure/go-autorest/autorest/azure" - "github.com/Azure/go-autorest/autorest/mocks" "github.com/juju/errors" "github.com/juju/names/v4" jc "github.com/juju/testing/checkers" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" gc "gopkg.in/check.v1" "github.com/juju/juju/core/instance" @@ -185,6 +184,7 @@ func (s *storageSuite) TestCreateVolumes(c *gc.C) { "foo": to.Ptr("bar"), } return &armcompute.Disk{ + Name: to.Ptr(name), Location: to.Ptr("westus"), Tags: tags, SKU: &armcompute.DiskSKU{ @@ -205,8 +205,8 @@ func (s *storageSuite) TestCreateVolumes(c *gc.C) { } func (s *storageSuite) createSenderWithUnauthorisedStatusCode() { - unauthSender := mocks.NewSender() - unauthSender.AppendAndRepeatResponse(mocks.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) + unauthSender := &azuretesting.MockSender{} + unauthSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) s.sender = azuretesting.Senders{unauthSender, unauthSender, unauthSender} } @@ -264,6 +264,7 @@ func (s *storageSuite) TestCreateVolumesWithInvalidCredential(c *gc.C) { "foo": to.Ptr("bar"), } return &armcompute.Disk{ + Name: to.Ptr(name), Location: to.Ptr("westus"), Tags: tags, Properties: &armcompute.DiskProperties{ @@ -314,7 +315,7 @@ func (s *storageSuite) TestListVolumesWithInvalidCredential(c *gc.C) { func (s *storageSuite) TestListVolumesErrors(c *gc.C) { volumeSource := s.volumeSource(c) - sender := mocks.NewSender() + sender := &azuretesting.MockSender{} sender.SetAndRepeatError(errors.New("no disks for you"), -1) s.sender = azuretesting.Senders{ sender, @@ -360,9 +361,9 @@ func (s *storageSuite) TestDescribeVolumesWithInvalidCredential(c *gc.C) { func (s *storageSuite) TestDescribeVolumesNotFound(c *gc.C) { volumeSource := s.volumeSource(c) - volumeSender := mocks.NewSender() - response := mocks.NewResponseWithBodyAndStatus( - mocks.NewBody("{}"), + volumeSender := &azuretesting.MockSender{} + response := azuretesting.NewResponseWithBodyAndStatus( + azuretesting.NewBody("{}"), http.StatusNotFound, "disk not found", ) @@ -378,7 +379,7 @@ func (s *storageSuite) TestDescribeVolumesNotFound(c *gc.C) { func (s *storageSuite) TestDestroyVolumes(c *gc.C) { volumeSource := s.volumeSource(c) - volume0Sender := azuretesting.NewSenderWithValue(&autorestazure.ServiceError{}) + volume0Sender := azuretesting.NewSenderWithValue(&odataerrors.ODataError{}) volume0Sender.PathPattern = `.*/Microsoft\.Compute/disks/volume-0` s.sender = azuretesting.Senders{volume0Sender} @@ -403,8 +404,8 @@ func (s *storageSuite) TestDestroyVolumesWithInvalidCredential(c *gc.C) { func (s *storageSuite) TestDestroyVolumesNotFound(c *gc.C) { volumeSource := s.volumeSource(c) - volume42Sender := mocks.NewSender() - volume42Sender.AppendResponse(mocks.NewResponseWithStatus( + volume42Sender := &azuretesting.MockSender{} + volume42Sender.AppendResponse(azuretesting.NewResponseWithStatus( "disk not found", http.StatusNotFound, )) s.sender = azuretesting.Senders{volume42Sender} @@ -522,6 +523,7 @@ func (s *storageSuite) TestAttachVolumes(c *gc.C) { }} assertRequestBody(c, s.requests[1], &armcompute.VirtualMachine{ + Name: to.Ptr("machine-0"), Properties: &armcompute.VirtualMachineProperties{ StorageProfile: &armcompute.StorageProfile{ DataDisks: machine0DataDisks, @@ -604,6 +606,7 @@ func (s *storageSuite) TestDetachVolumes(c *gc.C) { c.Assert(s.requests[1].Method, gc.Equals, "PUT") // update machine-0 assertRequestBody(c, s.requests[1], &armcompute.VirtualMachine{ + Name: to.Ptr("machine-0"), Properties: &armcompute.VirtualMachineProperties{ StorageProfile: &armcompute.StorageProfile{ DataDisks: []*armcompute.DataDisk{ @@ -665,6 +668,7 @@ func (s *storageSuite) TestDetachVolumesFinal(c *gc.C) { c.Assert(s.requests[1].Method, gc.Equals, "PUT") // update machine-0 assertRequestBody(c, s.requests[1], &armcompute.VirtualMachine{ + Name: to.Ptr("machine-0"), Properties: &armcompute.VirtualMachineProperties{ StorageProfile: &armcompute.StorageProfile{ DataDisks: []*armcompute.DataDisk{}, diff --git a/provider/azure/upgrades_test.go b/provider/azure/upgrades_test.go index 15e7c309ca4..b8524bc7fe0 100644 --- a/provider/azure/upgrades_test.go +++ b/provider/azure/upgrades_test.go @@ -12,7 +12,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" - "github.com/Azure/go-autorest/autorest/mocks" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -169,8 +168,8 @@ func (s *environUpgradeSuite) TestEnvironUpgradeOperationCreateCommonDeploymentC trueString := "true" controllerTags["juju-is-controller"] = &trueString - unauthSender := mocks.NewSender() - unauthSender.AppendAndRepeatResponse(mocks.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) + unauthSender := &azuretesting.MockSender{} + unauthSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) s.sender = append(s.sender, unauthSender, unauthSender, unauthSender) c.Assert(s.invalidCredential, jc.IsFalse) diff --git a/provider/azure/utils.go b/provider/azure/utils.go index d185e10d217..38fb8895186 100644 --- a/provider/azure/utils.go +++ b/provider/azure/utils.go @@ -43,10 +43,15 @@ func collectAPIVersions(ctx context.ProviderCallContext, client *armresources.Pr return result, nil } -func azureCloud(apiEndpoint, identityEndpoint string) azurecloud.Configuration { - // The AD graph API is deprecated. Use the new one. - if identityEndpoint == "https://graph.windows.net" { - identityEndpoint = azurecloud.AzurePublic.ActiveDirectoryAuthorityHost +func azureCloud(cloudName, apiEndpoint, identityEndpoint string) azurecloud.Configuration { + // Use well known cloud definitions from the SDk if possible. + switch cloudName { + case "azure": + return azurecloud.AzurePublic + case "azure-china": + return azurecloud.AzureChina + case "azure-gov": + return azurecloud.AzureGovernment } return azurecloud.Configuration{ ActiveDirectoryAuthorityHost: identityEndpoint, diff --git a/storage/interface.go b/storage/interface.go index 5f2cd573566..92b1e54ab39 100644 --- a/storage/interface.go +++ b/storage/interface.go @@ -86,7 +86,7 @@ type Provider interface { // returning an error if it is invalid. ValidateConfig(*Config) error - // ValidateForkK8s validates if a storage provider can be set for + // ValidateForK8s validates if a storage provider can be set for // a given K8s configuration. ValidateForK8s(map[string]any) error }