diff --git a/go.mod b/go.mod index 62ae8114d..da34e3ca8 100644 --- a/go.mod +++ b/go.mod @@ -41,11 +41,11 @@ require ( cloud.google.com/go/compute/metadata v0.9.0 // indirect cyphar.com/go-pathrs v0.2.6 // indirect github.com/42wim/httpsig v1.2.4 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.3 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.15.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.13.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20261013165250-51075c6ea6bc // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.10.1 // indirect github.com/BurntSushi/toml v1.6.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect diff --git a/go.sum b/go.sum index f601859cf..17962e42f 100644 --- a/go.sum +++ b/go.sum @@ -12,20 +12,20 @@ github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU= github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1/go.mod h1:iptorS+VYKFL2N6PnebpS91dubG35eAOEERnT4PJbQU= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 h1:u93s+zU2JD62im61Bm5CZIc1ZrOJaIAWEg0WOrMVkEo= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1/go.mod h1:oXtinPO4OLj9d1DOTrqrL1oRwGhcqadvAmrl6wTeGlk= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.3 h1:qgnWTiWs1G7AqcKZegFvkJD1bmXJcaDU3uC0ELSgZ5U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.3/go.mod h1:XtxD48QPInqoj+GMlcRlXHEYsoOxFnP6w2JgIZ9rDS4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.15.0 h1:w3gDUJlJ0BfBsHH02DCBDwbkmMSXJqBj6nBOCo9iocY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.15.0/go.mod h1:7scl8mG/IFLVjJ9ef/ltdND2T1z0CkgVSaQoUxLvmeU= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDHnGGwZ6QvQ3KHTtWl2MCK+GMt2vxs= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0/go.mod h1:mCBhUhlMjLLJKr5aqw2TNS/VqJOie8MzWq3DAMJeKso= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.13.0 h1:Terj1CMW4+NY5CpWjSRkqLoxh2DkYDdv/FH2z0897Uc= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.13.0/go.mod h1:7Ut2Qw9vcwpLW5Q4MBy43uSxXS7tK2yjs94OJ6j5thE= github.com/Azure/go-ansiterm v0.0.0-20261013165250-51075c6ea6bc h1:58s5YzGPNAP7+e1uemaL0ax/NPSeJOHdRpRIowd+JDk= github.com/Azure/go-ansiterm v0.0.0-20261013165250-51075c6ea6bc/go.mod h1:DMNF6Kxv+EcuUvVuNkvC3f+BjEEmc/Fn9aU+qiD8zpU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI= -github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.10.1 h1:0O6j18nQDoIff6/mtosT5LNU2WehEwp+LqfX6699oqY= +github.com/AzureAD/microsoft-authentication-library-for-go v1.10.1/go.mod h1:xdYAf5bjkOpsd3auA8riiJW4vneBubt9caavL7626jQ= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/CHANGELOG.md b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/CHANGELOG.md index f745dca9d..8cda72f56 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/CHANGELOG.md +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/CHANGELOG.md @@ -1,5 +1,22 @@ # Release History +## 1.23.3 (2026-10-06) + +### Bugs Fixed + +* Fixed `arm.ResourceID.Location` being empty when the `locations` segment uses different casing. + +### Other Changes + +* Added `azure-deprecating` to the default set of allowed (non-redacted) response headers for logging. +* Replaced internal default HTTP client with the one from `sdk/internal`. + +## 1.23.2 (2026-09-28) + +### Bugs Fixed + +* Fixed unmarshalling `datetime.RFC7231` to use a fixed `GMT` zone. + ## 1.23.1 (2026-08-27) ### Other Changes diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource/resource_identifier.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource/resource_identifier.go index c602e77d1..4b4aa1fb9 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource/resource_identifier.go +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource/resource_identifier.go @@ -141,7 +141,7 @@ func (id *ResourceID) init(parent *ResourceID, resourceType ResourceType, name s id.SubscriptionID = name } - if resourceType.lastType() == locationsKey { + if strings.EqualFold(resourceType.lastType(), locationsKey) { id.Location = name } diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/ci.yml b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/ci.yml index b81b62103..5807f6b69 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/ci.yml +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/ci.yml @@ -3,7 +3,6 @@ trigger: branches: include: - main - - feature/* - hotfix/* - release/* paths: diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/constants.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/constants.go index fcbf011e7..bfbea747d 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/constants.go +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/constants.go @@ -37,5 +37,5 @@ const ( Module = "azcore" // Version is the semantic version (see http://semver.org) of this module. - Version = "v1.23.1" + Version = "v1.23.3" ) diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pipeline.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pipeline.go index 3d95fe30d..b93c9c362 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pipeline.go +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pipeline.go @@ -6,6 +6,7 @@ package runtime import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + azt "github.com/Azure/azure-sdk-for-go/sdk/internal/transport" ) // PipelineOptions contains Pipeline options for SDK developers @@ -85,7 +86,7 @@ func NewPipeline(module, version string, plOpts PipelineOptions, options *policy policies = append(policies, exported.PolicyFunc(bodyDownloadPolicy)) transport := cp.Transport if transport == nil { - transport = defaultHTTPClient + transport = azt.DefaultHTTPClient } return exported.NewPipeline(transport, policies...) } diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_logging.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_logging.go index d9e6481de..61ba383aa 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_logging.go +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_logging.go @@ -34,6 +34,7 @@ func NewLogPolicy(o *policy.LogOptions) policy.Policy { // construct default hash set of allowed headers allowedHeaders := map[string]struct{}{ "accept": {}, + "azure-deprecating": {}, "cache-control": {}, "connection": {}, "content-length": {}, diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/BREAKING_CHANGES.md b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/BREAKING_CHANGES.md index 567e6975b..a7551e49a 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/BREAKING_CHANGES.md +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/BREAKING_CHANGES.md @@ -1,11 +1,22 @@ # Breaking Changes +## v1.15.0 + +### Service Fabric transport requirements + +`ManagedIdentityCredential` now requires a transport that supports the Service Fabric endpoint's +certificate pinning. Applications with a custom `ClientOptions.Transport` must provide an +`*http.Client` whose Transport is `nil` or a standard `*http.Transport` without custom TLS dialing +or verification callbacks. Unsupported transports cause credential construction to fail. +Leave `ClientOptions.Transport` `nil` to use the default transport. The credential derives the +pinned client without modifying a caller-supplied client and rejects redirects. + ## v1.8.0 ### New errors from `NewManagedIdentityCredential` in some environments `NewManagedIdentityCredential` now returns an error when `ManagedIdentityCredentialOptions.ID` is set in a hosting environment whose managed identity API doesn't support user-assigned identities. `ManagedIdentityCredential.GetToken()` formerly logged a warning in these cases. Returning an error instead prevents the credential authenticating an unexpected identity. The affected hosting environments are: - * Azure Arc + * Azure Arc (user-assigned identities are supported starting in v1.15.0 when the agent supports them) * Azure ML (when a resource or object ID is specified; client IDs are supported) * Cloud Shell * Service Fabric diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/CHANGELOG.md b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/CHANGELOG.md index 3dea713cc..ce7aff7ee 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/CHANGELOG.md +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/CHANGELOG.md @@ -1,5 +1,25 @@ # Release History +## 1.15.0 (2026-10-14) + +### Features Added + +- `ManagedIdentityCredential` and `DefaultAzureCredential` support user-assigned managed identities + on Azure Arc when the agent supports them +- `WorkloadIdentityCredential` supports identity binding mode via `WorkloadIdentityCredentialOptions.EnableAzureProxy` + +### Bugs Fixed + +- `ManagedIdentityCredential` verifies the Service Fabric endpoint's certificate against + `IDENTITY_SERVER_THUMBPRINT` and rejects redirects + +### Other Changes + +- On Service Fabric, a custom `ClientOptions.Transport` must be an `*http.Client` with a `nil` + Transport or a standard `*http.Transport` without custom TLS dialing or verification callbacks. + Unsupported transports now cause credential construction to fail +- Upgraded dependencies + ## 1.14.1 (2026-08-27) ### Other Changes diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/README.md b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/README.md index 127c25b72..2abfccfa3 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/README.md +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/README.md @@ -68,6 +68,25 @@ in any hosting environment which supports managed identities, such as (this list * [Azure Service Fabric](https://learn.microsoft.com/azure/service-fabric/concepts-managed-identity) * [Azure Virtual Machines](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-to-use-vm-token) +### Azure Arc user-assigned identities + +On Azure Arc, `ManagedIdentityCredential` supports selecting a user-assigned identity by client, +object, or resource ID through `ManagedIdentityCredentialOptions.ID`. `DefaultAzureCredential` +supports selecting one by client ID through the `AZURE_CLIENT_ID` environment variable. +The Azure Arc agent must support user-assigned +identities and confirm the requested identity in its token response. Otherwise, token acquisition +fails rather than authenticating the machine's system-assigned identity. + +### Service Fabric transport requirements + +On Service Fabric, `ManagedIdentityCredential` pins the endpoint certificate to +`IDENTITY_SERVER_THUMBPRINT` and rejects redirects. Leave `ClientOptions.Transport` `nil` to use +the default transport. A custom transport must be an `*http.Client` whose `Transport` is `nil` or +an `*http.Transport` without custom TLS dialing or verification callbacks. The credential derives +the pinned client without modifying the caller's client. Unsupported transports cause credential +construction to fail. See the [troubleshooting guide](https://aka.ms/azsdk/go/identity/troubleshoot#azure-service-fabric-managed-identity) +for configuration errors. + ## Examples - [Authenticate with DefaultAzureCredential](#authenticate-with-defaultazurecredential "Authenticate with DefaultAzureCredential") @@ -244,5 +263,3 @@ additional questions or comments. [ctc_overview]: https://aka.ms/azsdk/go/identity/credential-chains#chainedtokencredential-overview [dac_overview]: https://aka.ms/azsdk/go/identity/credential-chains#defaultazurecredential-overview - - diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TROUBLESHOOTING.md b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TROUBLESHOOTING.md index c69664a84..82c2ba7b9 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TROUBLESHOOTING.md +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TROUBLESHOOTING.md @@ -20,6 +20,8 @@ This troubleshooting guide covers failure investigation techniques, common error - [Troubleshoot ManagedIdentityCredential authentication issues](#troubleshoot-managedidentitycredential-authentication-issues) - [Azure App Service and Azure Functions managed identity](#azure-app-service-and-azure-functions-managed-identity) - [Azure Virtual Machine managed identity](#azure-virtual-machine-managed-identity) + - [Azure Arc managed identity](#azure-arc-managed-identity) + - [Azure Service Fabric managed identity](#azure-service-fabric-managed-identity) - [Troubleshoot WorkloadIdentityCredential authentication issues](#troubleshoot-workloadidentitycredential-authentication-issues) - [Get additional help](#get-additional-help) @@ -120,8 +122,35 @@ azlog.SetEvents(azidentity.EventAuthentication) |---|---|---| |Azure Virtual Machines and Scale Sets|[Configuration](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm)|[Troubleshooting](#azure-virtual-machine-managed-identity)| |Azure App Service and Azure Functions|[Configuration](https://learn.microsoft.com/azure/app-service/overview-managed-identity)|[Troubleshooting](#azure-app-service-and-azure-functions-managed-identity)| -|Azure Arc|[Configuration](https://learn.microsoft.com/azure/azure-arc/servers/managed-identity-authentication)|| -|Azure Service Fabric|[Configuration](https://learn.microsoft.com/azure/service-fabric/concepts-managed-identity)|| +|Azure Arc|[Configuration](https://learn.microsoft.com/azure/azure-arc/servers/managed-identity-authentication)|[Troubleshooting](#azure-arc-managed-identity)| +|Azure Service Fabric|[Configuration](https://learn.microsoft.com/azure/service-fabric/concepts-managed-identity)|[Troubleshooting](#azure-service-fabric-managed-identity)| + +### Azure Arc managed identity + +User-assigned identities require an Azure Arc agent that supports selecting an identity by client, +object, or resource ID. `NewManagedIdentityCredential` accepts these IDs, but token acquisition +fails if the agent doesn't confirm the requested identity. This prevents an older agent from +silently authenticating the machine's system-assigned identity instead. + +| Error Message | Description | Mitigation | +|---|---|---| +|azure arc did not confirm the requested user-assigned managed identity in the token response|The agent's response omitted the requested identity selector or returned a different identity.|Ensure the Azure Arc agent supports user-assigned identities, the identity is assigned to the resource, and the configured ID is correct. Upgrade the agent if necessary. Only omit the ID if the application intends to authenticate the system-assigned identity.| + +### Azure Service Fabric managed identity + +The credential requires an HTTPS endpoint, pins its certificate to `IDENTITY_SERVER_THUMBPRINT`, +and rejects redirects. It derives a pinned HTTP client without modifying a caller-supplied client. + +| Error Message | Description | Mitigation | +|---|---|---| +|Service Fabric managed identity requires ClientOptions.Transport to be an *http.Client|The configured transport can't be augmented with the required certificate pinning.|Leave `ClientOptions.Transport` `nil`, or provide an `*http.Client` with a `nil` Transport or a standard `*http.Transport`.| +|managed identity on Service Fabric requires a standard *http.Transport|The HTTP client's underlying transport can't be augmented with certificate pinning.|Use a standard `*http.Transport`, or leave the transport `nil`.| +|managed identity on Service Fabric does not support a transport with custom TLS dialing|A custom `DialTLS` or `DialTLSContext` callback can bypass certificate verification.|Remove the custom TLS dialing callback. Use `DialContext` if custom connection setup is needed.| +|managed identity on Service Fabric does not support custom TLS verification|The transport has a custom `VerifyPeerCertificate` or `VerifyConnection` callback.|Remove the custom verification callback so the credential can apply the required certificate pinning.| +|IDENTITY_SERVER_THUMBPRINT must be a SHA-1 certificate thumbprint|The configured thumbprint isn't a valid 40-digit hexadecimal SHA-1 thumbprint. Colons and whitespace are allowed.|Verify the Service Fabric environment configuration supplies the endpoint certificate's thumbprint.| +|TLS certificate thumbprint from Service Fabric did not match IDENTITY_SERVER_THUMBPRINT|The endpoint presented a certificate that doesn't match the configured thumbprint.|Verify the endpoint and thumbprint configuration, including any certificate rotation. Don't disable certificate verification.| +|managed identity endpoint for Service Fabric must use HTTPS|The configured endpoint isn't an absolute HTTPS URL.|Verify `IDENTITY_ENDPOINT` contains the HTTPS endpoint provided by Service Fabric.| +|redirects are not permitted for managed identity on Service Fabric|The endpoint returned a redirect.|Verify `IDENTITY_ENDPOINT` points directly to the Service Fabric managed identity endpoint.| ### Azure Virtual Machine managed identity @@ -249,6 +278,7 @@ Get-AzAccessToken -ResourceUrl "https://management.core.windows.net" | Error Message |Description| Mitigation | |---|---|---| |no client ID/tenant ID/token file specified|Incomplete configuration|In most cases these values are provided via environment variables set by Azure Workload Identity. +|In an application using [Azure Kubernetes Service identity bindings](https://learn.microsoft.com/azure/aks/identity-bindings-concepts): |`WorkloadIdentityCredential` isn't configured to use the identity binding proxy|Set `WorkloadIdentityCredentialOptions.EnableAzureProxy` to `true` ## Troubleshoot AzurePipelinesCredential authentication issues diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/ci.yml b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/ci.yml index 7e05ddcec..c50b0be2e 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/ci.yml +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/ci.yml @@ -3,7 +3,6 @@ trigger: branches: include: - main - - feature/* - hotfix/* - release/* paths: diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/configurable_client.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/configurable_client.go new file mode 100644 index 000000000..0ebba8d85 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/configurable_client.go @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azidentity + +import ( + "errors" + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo" +) + +// configurableClient is installed at the bottom of the managed identity pipeline as the transport +// for sources that require MSAL to configure the client/transport (Service Fabric today, mTLS later). +type configurableClient struct { + client *http.Client +} + +// Do implements policy.Transporter. +func (c *configurableClient) Do(req *http.Request) (*http.Response, error) { + if c.client == nil { + return nil, errorinfo.NonRetriableError(errors.New(credNameManagedIdentity + ": managed identity transport was not configured")) + } + return c.client.Do(req) +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_client.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_client.go index 02f02fec4..1173b8deb 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_client.go +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_client.go @@ -15,6 +15,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/internal/log" + "github.com/Azure/azure-sdk-for-go/sdk/internal/transport" msalerrors "github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity" ) @@ -44,6 +45,11 @@ type managedIdentityClient struct { // a credentialUnavailableError instead of an AuthenticationFailedError for an unexpected IMDS response. chained bool msalClient msalManagedIdentityClient + // configurableClient is set when a source (Service Fabric today, mTLS later) requires MSAL to + // configure the transport. MSAL applies certificate pinning and the redirect policy via + // ConfigureClient, preserving the azcore pipeline's middleware. Its client field holds the + // caller-supplied base until ConfigureClient replaces it with the augmented client. + configurableClient configurableClient } // setIMDSRetryOptionDefaults sets zero-valued fields to default values appropriate for IMDS @@ -104,6 +110,22 @@ func newManagedIdentityClient(options *ManagedIdentityCredentialOptions) (*manag setIMDSRetryOptionDefaults(&cp.Retry) } + if source == managedidentity.ServiceFabric { + if cp.Transport != nil { + // This source has MSAL pin the base client, so a caller-supplied Transport must be an + // *http.Client MSAL can clone. Any other transporter can't be pinned, so fail closed + // rather than send credentials unpinned. + base, ok := cp.Transport.(*http.Client) + if !ok { + return nil, errors.New(credNameManagedIdentity + ": Service Fabric managed identity requires ClientOptions.Transport to be an *http.Client") + } + c.configurableClient.client = base + } + // Install a client MSAL configures (see ConfigureClient) at the bottom of the pipeline, + // so certificate verification and the redirect policy apply without discarding middleware. + cp.Transport = &c.configurableClient + } + c.azClient, err = azcore.NewClient(module, version, azruntime.PipelineOptions{ Tracing: azruntime.TracingOptions{ Namespace: traceNamespace, @@ -157,6 +179,24 @@ func (c *managedIdentityClient) Do(r *http.Request) (*http.Response, error) { return doForClient(c.azClient, r) } +// ConfigureClient implements managedidentity.ClientConfigurer. MSAL passes an augmentation +// that applies a source's certificate pinning and redirect policy; azidentity supplies the caller's +// Transport as the base (or the SDK default when none was set) and installs the augmented client at +// the bottom of the pipeline, preserving the azcore middleware above. +func (c *managedIdentityClient) ConfigureClient(augment func(*http.Client) (*http.Client, error)) error { + base := c.configurableClient.client + if base == nil { + // no caller provided transport so use our default + base = transport.DefaultHTTPClient + } + client, err := augment(base) + if err != nil { + return err + } + c.configurableClient.client = client + return nil +} + // authenticate acquires an access token func (c *managedIdentityClient) GetToken(ctx context.Context, tro policy.TokenRequestOptions) (azcore.AccessToken, error) { // no need to synchronize around this value because it's true only when DefaultAzureCredential constructed the client, @@ -220,3 +260,5 @@ func (c *managedIdentityClient) GetToken(ctx context.Context, tro policy.TokenRe err = newAuthenticationFailedErrorFromMSAL(credNameManagedIdentity, err) return azcore.AccessToken{}, err } + +var _ managedidentity.ClientConfigurer = (*managedIdentityClient)(nil) diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_credential.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_credential.go index d0a5ecf97..074c0b62b 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_credential.go +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_credential.go @@ -32,7 +32,6 @@ type ManagedIDKind interface { // ClientID is the client ID of a user-assigned managed identity. [NewManagedIdentityCredential] // returns an error when a ClientID is specified on the following platforms: // -// - Azure Arc // - Cloud Shell // - Service Fabric type ClientID string @@ -49,7 +48,6 @@ func (c ClientID) String() string { // ObjectID is the object ID of a user-assigned managed identity. [NewManagedIdentityCredential] // returns an error when an ObjectID is specified on the following platforms: // -// - Azure Arc // - Azure ML // - Cloud Shell // - Service Fabric @@ -67,7 +65,6 @@ func (o ObjectID) String() string { // ResourceID is the resource ID of a user-assigned managed identity. [NewManagedIdentityCredential] // returns an error when a ResourceID is specified on the following platforms: // -// - Azure Arc // - Azure ML // - Cloud Shell // - Service Fabric @@ -84,12 +81,18 @@ func (r ResourceID) String() string { // ManagedIdentityCredentialOptions contains optional parameters for ManagedIdentityCredential. type ManagedIdentityCredentialOptions struct { + // ClientOptions configures the credential's HTTP pipeline. On Service Fabric, Transport must be nil + // or an *http.Client whose Transport is nil or an *http.Transport. Custom TLS dialing and verification + // callbacks aren't supported. The credential derives a client that pins the endpoint certificate + // and rejects redirects without modifying the caller's client. azcore.ClientOptions // ID of a managed identity the credential should authenticate. Set this field to use a specific identity instead of // the hosting environment's default. The value may be the identity's client, object, or resource ID. // NewManagedIdentityCredential returns an error when the hosting environment doesn't support user-assigned managed // identities, or the specified kind of ID. + // On Azure Arc, user-assigned identities require an agent that supports them. GetToken returns an error if the + // agent doesn't confirm the requested identity in its response. ID ManagedIDKind // dac indicates whether the credential is part of DefaultAzureCredential. When true, and the environment doesn't have diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/version.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/version.go index fb5014489..17841c50d 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/version.go +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/version.go @@ -11,5 +11,5 @@ const ( module = "github.com/Azure/azure-sdk-for-go/sdk/" + component // Version is the semantic version (see http://semver.org) of this module. - version = "v1.14.1" + version = "v1.15.0" ) diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/workload_identity.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/workload_identity.go index 87399aed3..11b43e090 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/workload_identity.go +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/workload_identity.go @@ -52,14 +52,14 @@ type WorkloadIdentityCredentialOptions struct { // the application responsible for ensuring the configured authority is valid and trustworthy. DisableInstanceDiscovery bool - // enableAzureProxy determines whether the credential reads proxy configuration from environment variables. When + // EnableAzureProxy determines whether the credential reads proxy configuration from environment variables. When // this value is true and proxy configuration isn't present or this value is false, the credential will request // tokens directly from Entra ID. // // The proxy feature is designed for applications that deploy to many clusters and clusters that host many // applications. See the Azure Kubernetes Service identity bindings documentation for more information on when // to set this option: https://learn.microsoft.com/azure/aks/identity-bindings-concepts - enableAzureProxy bool + EnableAzureProxy bool // TenantID of the service principal. Defaults to the value of the environment variable AZURE_TENANT_ID. TenantID string @@ -103,7 +103,7 @@ func NewWorkloadIdentityCredential(options *WorkloadIdentityCredentialOptions) ( DisableInstanceDiscovery: options.DisableInstanceDiscovery, } - if options.enableAzureProxy { + if options.EnableAzureProxy { if err := customtokenproxy.Configure(&caco.ClientOptions); err != nil { return nil, err } diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_http_client.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/transport/transport.go similarity index 88% rename from vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_http_client.go rename to vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/transport/transport.go index 928e9bf92..dce3e118b 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_http_client.go +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/transport/transport.go @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package runtime +package transport import ( "crypto/tls" @@ -12,7 +12,8 @@ import ( "golang.org/x/net/http2" ) -var defaultHTTPClient *http.Client +// DefaultHTTPClient is the default configured http.Client used by all clients. +var DefaultHTTPClient *http.Client func init() { defaultTransport := &http.Transport{ @@ -39,7 +40,7 @@ func init() { // if there's no response to the ping within the timeout, the connection will be closed http2Transport.PingTimeout = 5 * time.Second } - defaultHTTPClient = &http.Client{ + DefaultHTTPClient = &http.Client{ Transport: defaultTransport, } } diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_other.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/transport/transport_other.go similarity index 94% rename from vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_other.go rename to vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/transport/transport_other.go index 1c75d771f..2477c308a 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_other.go +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/transport/transport_other.go @@ -3,7 +3,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package runtime +package transport import ( "context" diff --git a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_wasm.go b/vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/transport/transport_wasm.go similarity index 94% rename from vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_wasm.go rename to vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/transport/transport_wasm.go index 3dc9eeecd..9b14ecab6 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_wasm.go +++ b/vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/transport/transport_wasm.go @@ -3,7 +3,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package runtime +package transport import ( "context" diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go index fe4599e3d..4a4979fcf 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go @@ -77,9 +77,18 @@ const ( ) // CertFromPEM converts a PEM file (.pem or .key) for use with [NewCredFromCert]. The file -// must contain the public certificate and the private key. If a PEM block is encrypted and -// password is not an empty string, it attempts to decrypt the PEM blocks using the password. +// must contain the public certificate and the unencrypted private key. // Multiple certs are due to certificate chaining for use cases like TLS that sign from root to leaf. +// +// Encrypted PEM private keys are not supported. Legacy RFC 1423 encrypted PEM blocks (identified +// by a DEK-Info header, e.g. "DEK-Info: DES-EDE3-CBC,...") rely on a weak key derivation function +// (a single MD5 iteration) and obsolete DES/3DES ciphers, so CertFromPEM rejects them with an +// error instead of decrypting them. Provide the private key unencrypted and protect it with +// filesystem permissions; you can remove legacy encryption with, for example: +// +// openssl pkcs8 -topk8 -nocrypt -in legacy.key -out key.pem +// +// The password parameter is retained for backward compatibility and is ignored. func CertFromPEM(pemData []byte, password string) ([]*x509.Certificate, crypto.PrivateKey, error) { var certs []*x509.Certificate var priv crypto.PrivateKey @@ -89,16 +98,8 @@ func CertFromPEM(pemData []byte, password string) ([]*x509.Certificate, crypto.P break } - //nolint:staticcheck // x509.IsEncryptedPEMBlock and x509.DecryptPEMBlock are deprecated. They are used here only to support a usecase. - if x509.IsEncryptedPEMBlock(block) { - b, err := x509.DecryptPEMBlock(block, []byte(password)) - if err != nil { - return nil, nil, fmt.Errorf("could not decrypt encrypted PEM block: %v", err) - } - block, _ = pem.Decode(b) - if block == nil { - return nil, nil, fmt.Errorf("encounter encrypted PEM block that did not decode") - } + if _, encrypted := block.Headers["DEK-Info"]; encrypted { + return nil, nil, fmt.Errorf("legacy RFC 1423 encrypted PEM blocks are not supported because they use a weak key derivation function (single MD5 iteration) and obsolete DES/3DES ciphers; provide the private key unencrypted (e.g. `openssl pkcs8 -topk8 -nocrypt -in legacy.key -out key.pem`) and protect it with filesystem permissions") } switch block.Type { @@ -985,7 +986,7 @@ func WithFMIPath(path string) interface { // WithAttribute specifies an identity attribute to include in the token request. // The attribute is sent as "attributes" in the request body and returned as "xmc_attr" -// in the access token claims. This is sometimes used withFMIPath +// in the access token claims. It can be used with or without [WithFMIPath]. func WithAttribute(attrValue string) interface { AcquireByCredentialOption options.CallOption @@ -1001,6 +1002,10 @@ func WithAttribute(attrValue string) interface { if t.extraBodyParameters == nil { t.extraBodyParameters = make(map[string]string) } + if t.cacheKeyComponents == nil { + t.cacheKeyComponents = make(map[string]string) + } + t.cacheKeyComponents["attributes"] = attrValue t.extraBodyParameters["attributes"] = attrValue default: return fmt.Errorf("unexpected options type %T", a) diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors/errors.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors/errors.go index b5cbb5721..2d6e9c23e 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors/errors.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors/errors.go @@ -36,10 +36,7 @@ type verboser interface { // Verbose prints the most verbose error that the error message has. func Verbose(err error) string { build := strings.Builder{} - for { - if err == nil { - break - } + for err != nil { if v, ok := err.(verboser); ok { build.WriteString(v.Verbose()) } else { diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go index ed622e5ec..27068cad0 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go @@ -328,6 +328,30 @@ func (b Client) AuthCodeURL(ctx context.Context, clientID, redirectURI string, s return baseURL.String(), nil } +func (b Client) proactiveRefresh(ctx context.Context, silent AcquireTokenSilentParameters, authParams authority.AuthParams, refreshToken accesstokens.RefreshToken) (accesstokens.TokenResponse, bool, error) { + if silent.IsAppCache { + token, err := b.Token.Credential(ctx, authParams, silent.Credential) + return token, err == nil, nil + } + if silent.AuthorizationType == authority.ATOnBehalfOf { + token, err := b.Token.OnBehalfOf(ctx, authParams, silent.Credential) + return token, err == nil, nil + } + + switch silent.RequestType { + case accesstokens.ATConfidential: + token, err := b.Token.Refresh(ctx, silent.RequestType, authParams, silent.Credential, refreshToken) + return token, err == nil, nil + case accesstokens.ATPublic: + token, err := b.Token.Refresh(ctx, silent.RequestType, authParams, silent.Credential, refreshToken) + return token, err == nil, err + case accesstokens.ATUnknown: + return accesstokens.TokenResponse{}, false, errors.New("silent request type cannot be ATUnknown") + default: + return accesstokens.TokenResponse{}, false, fmt.Errorf("unsupported silent request type %s", silent.RequestType) + } +} + func (b Client) AcquireTokenSilent(ctx context.Context, silent AcquireTokenSilentParameters) (AuthResult, error) { ar := AuthResult{} // when tenant == "", the caller didn't specify a tenant and WithTenant will choose the client's configured tenant @@ -391,19 +415,12 @@ func (b Client) AcquireTokenSilent(ctx context.Context, silent AcquireTokenSilen // If the token is not same, we don't need to refresh it. // Which means it refreshed. if str, err := m.Read(ctx, authParams); err == nil && str.AccessToken.Secret == ar.AccessToken { - switch silent.RequestType { - case accesstokens.ATConfidential: - if tr, er := b.Token.Credential(ctx, authParams, silent.Credential); er == nil { - return b.AuthResultFromToken(ctx, authParams, tr) - } - case accesstokens.ATPublic: - token, err := b.Token.Refresh(ctx, silent.RequestType, authParams, silent.Credential, storageTokenResponse.RefreshToken) - if err != nil { - return ar, err - } + token, refreshed, err := b.proactiveRefresh(ctx, silent, authParams, storageTokenResponse.RefreshToken) + if err != nil { + return ar, err + } + if refreshed { return b.AuthResultFromToken(ctx, authParams, token) - case accesstokens.ATUnknown: - return ar, errors.New("silent request type cannot be ATUnknown") } } } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/items.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/items.go index b7d1a670b..cda000afc 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/items.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/items.go @@ -180,7 +180,7 @@ func NewIDToken(homeID, env, realm, clientID, idToken string) IDToken { HomeAccountID: homeID, Environment: env, Realm: realm, - CredentialType: "IDToken", + CredentialType: "IdToken", ClientID: clientID, Secret: idToken, } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go index b0311ca6d..382d7253e 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go @@ -45,7 +45,7 @@ func Marshal(i interface{}) ([]byte, error) { enc.SetIndent("", "") v := reflect.ValueOf(i) - if v.Kind() != reflect.Ptr && v.CanAddr() { + if v.Kind() != reflect.Pointer && v.CanAddr() { v = v.Addr() } err := marshalStruct(v, &buff, enc) @@ -124,7 +124,7 @@ func hasMarshalJSON(v reflect.Value) bool { ok := false if _, ok = v.Interface().(json.Marshaler); !ok { var i any - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { i = v.Elem().Interface() } else if v.CanAddr() { i = v.Addr().Interface() @@ -141,7 +141,7 @@ func callMarshalJSON(v reflect.Value) ([]byte, error) { return marsh.MarshalJSON() } - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { if marsh, ok := v.Elem().Interface().(json.Marshaler); ok { return marsh.MarshalJSON() } @@ -160,7 +160,7 @@ func callMarshalJSON(v reflect.Value) ([]byte, error) { // the UnmarshalJSON method. func hasUnmarshalJSON(v reflect.Value) bool { // You can't unmarshal on a non-pointer type. - if v.Kind() != reflect.Ptr { + if v.Kind() != reflect.Pointer { if !v.CanAddr() { return false } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/mapslice.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/mapslice.go index cef442f25..3e90d8272 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/mapslice.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/mapslice.go @@ -11,7 +11,7 @@ import ( // unmarshalMap unmarshal's a map. func unmarshalMap(dec *json.Decoder, m reflect.Value) error { - if m.Kind() != reflect.Ptr || m.Elem().Kind() != reflect.Map { + if m.Kind() != reflect.Pointer || m.Elem().Kind() != reflect.Map { panic("unmarshalMap called on non-*map value") } mapValueType := m.Elem().Type().Elem() @@ -72,7 +72,7 @@ func (m *mapWalk) start() (stateFn, error) { return nil, fmt.Errorf("Unmarshal expected opening {, received %v", delim) } return m.next, nil - case reflect.Ptr: + case reflect.Pointer: return nil, fmt.Errorf("do not support maps with values of '**type' or '*reference") } @@ -105,7 +105,7 @@ func (m *mapWalk) storeValue() (stateFn, error) { v := m.valueType for { switch v.Kind() { - case reflect.Ptr: + case reflect.Pointer: v = v.Elem() continue case reflect.Struct: @@ -125,7 +125,7 @@ func (m *mapWalk) storeStruct() (stateFn, error) { return nil, err } - if m.valueType.Kind() == reflect.Ptr { + if m.valueType.Kind() == reflect.Pointer { m.m.Elem().SetMapIndex(reflect.ValueOf(m.key), v) return m.next, nil } @@ -163,7 +163,7 @@ func (m *mapWalk) storeSlice() (stateFn, error) { func (m *mapWalk) valueBaseType() (reflect.Type, bool) { ptr := false v := m.valueType - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { ptr = true v = v.Elem() } @@ -174,7 +174,7 @@ func (m *mapWalk) valueBaseType() (reflect.Type, bool) { // ptrSlice, which must be a pointer to a slice. newValue() can be use to // create the slice. func unmarshalSlice(dec *json.Decoder, ptrSlice reflect.Value) error { - if ptrSlice.Kind() != reflect.Ptr || ptrSlice.Elem().Kind() != reflect.Slice { + if ptrSlice.Kind() != reflect.Pointer || ptrSlice.Elem().Kind() != reflect.Slice { panic("unmarshalSlice called on non-*[]slice value") } sliceValueType := ptrSlice.Elem().Type().Elem() @@ -227,7 +227,7 @@ func (s *sliceWalk) start() (stateFn, error) { t := s.valueBaseType() switch t.Kind() { - case reflect.Ptr: + case reflect.Pointer: return nil, fmt.Errorf("cannot unmarshal into a ** or *") case reflect.Struct, reflect.Map, reflect.Slice: delim, err := s.dec.Token() @@ -262,7 +262,7 @@ func (s *sliceWalk) next() (stateFn, error) { func (s *sliceWalk) storeValue() (stateFn, error) { t := s.valueBaseType() switch t.Kind() { - case reflect.Ptr: + case reflect.Pointer: return nil, fmt.Errorf("do not support 'pointer to pointer' or 'pointer to reference' types") case reflect.Struct: return s.storeStruct, nil @@ -280,7 +280,7 @@ func (s *sliceWalk) storeStruct() (stateFn, error) { return nil, err } - if s.valueType.Kind() == reflect.Ptr { + if s.valueType.Kind() == reflect.Pointer { s.s.Elem().Set(reflect.Append(s.s.Elem(), v)) return s.next, nil } @@ -318,7 +318,7 @@ func (s *sliceWalk) storeSlice() (stateFn, error) { // struct, etc... func (s *sliceWalk) valueBaseType() reflect.Type { v := s.valueType - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } return v @@ -326,7 +326,7 @@ func (s *sliceWalk) valueBaseType() reflect.Type { // newValue() returns a new *type that represents type passed. func newValue(valueType reflect.Type) reflect.Value { - if valueType.Kind() == reflect.Ptr { + if valueType.Kind() == reflect.Pointer { return reflect.New(valueType.Elem()) } return reflect.New(valueType) diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/marshal.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/marshal.go index df5dc6e11..f5d7a5373 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/marshal.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/marshal.go @@ -15,7 +15,7 @@ import ( // as JSON into buff (sometimes with writes to buff directly, sometimes via enc). // This call is recursive for all fields of *struct or struct type. func marshalStruct(v reflect.Value, buff *bytes.Buffer, enc *json.Encoder) error { - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } // We only care about custom Marshalling a struct. @@ -79,9 +79,9 @@ func marshalStruct(v reflect.Value, buff *bytes.Buffer, enc *json.Encoder) error // Write out the field name part. jsonName := translator.jsonName(t.Field(x).Name) - buff.WriteString(fmt.Sprintf("%q:", jsonName)) + fmt.Fprintf(buff, "%q:", jsonName) - if field.Kind() == reflect.Ptr { + if field.Kind() == reflect.Pointer { field = field.Elem() } @@ -169,13 +169,13 @@ func (m *mapEncode) start() (stateFn, error) { } valueBaseType := m.m.Type().Elem() - if valueBaseType.Kind() == reflect.Ptr { + if valueBaseType.Kind() == reflect.Pointer { valueBaseType = valueBaseType.Elem() } m.valueBaseType = valueBaseType switch valueBaseType.Kind() { - case reflect.Ptr: + case reflect.Pointer: return nil, fmt.Errorf("Marshal does not support ** or *") case reflect.Struct, reflect.Map, reflect.Slice: return m.encode, nil @@ -196,7 +196,7 @@ func (m *mapEncode) encode() (stateFn, error) { for iter.Next() { // Write the key. k := iter.Key() - m.buff.WriteString(fmt.Sprintf("%q:", k.String())) + fmt.Fprintf(m.buff, "%q:", k.String()) v := iter.Value() switch m.valueBaseType.Kind() { @@ -273,13 +273,13 @@ func (s *sliceEncode) start() (stateFn, error) { } valueBaseType := s.s.Type().Elem() - if valueBaseType.Kind() == reflect.Ptr { + if valueBaseType.Kind() == reflect.Pointer { valueBaseType = valueBaseType.Elem() } s.valueBaseType = valueBaseType switch valueBaseType.Kind() { - case reflect.Ptr: + case reflect.Pointer: return nil, fmt.Errorf("Marshal does not support ** or *") case reflect.Struct, reflect.Map, reflect.Slice: return s.encode, nil @@ -331,7 +331,7 @@ func writeAddFields(i interface{}, buff *bytes.Buffer, enc *json.Encoder) error x := 0 for k, v := range m { - buff.WriteString(fmt.Sprintf("%q:", k)) + fmt.Fprintf(buff, "%q:", k) if err := enc.Encode(v); err != nil { return err } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/struct.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/struct.go index 07751544a..77032d050 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/struct.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/struct.go @@ -12,7 +12,7 @@ import ( func unmarshalStruct(jdec *json.Decoder, i interface{}) error { v := reflect.ValueOf(i) - if v.Kind() != reflect.Ptr { + if v.Kind() != reflect.Pointer { return fmt.Errorf("Unmarshal() received type %T, which is not a *struct", i) } v = v.Elem() @@ -201,11 +201,11 @@ func fieldBaseType(v reflect.Value, fieldName string) (t reflect.Type, isPtr boo return nil, false, fmt.Errorf("bug: fieldBaseType() lookup of field(%s) on type(%s): do not have field", fieldName, v.Type().Name()) } t = sf.Type - if t.Kind() == reflect.Ptr { + if t.Kind() == reflect.Pointer { t = t.Elem() isPtr = true } - if t.Kind() == reflect.Ptr { + if t.Kind() == reflect.Pointer { return nil, isPtr, fmt.Errorf("received pointer to pointer type, not supported") } return t, isPtr, nil @@ -248,7 +248,7 @@ var umarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() // findFields parses a struct and writes the field tags for lookup. It will return an error // if any field has a type of *struct or struct that does not implement json.Marshaler. func findFields(v reflect.Value) (translateFields, error) { - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } if v.Kind() != reflect.Struct { @@ -267,7 +267,7 @@ func findFields(v reflect.Value) (translateFields, error) { tfs = append(tfs, tf) f := v.Field(i) - if f.Kind() == reflect.Ptr { + if f.Kind() == reflect.Pointer { f = f.Elem() } if f.Kind() == reflect.Struct { diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/local/server.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/local/server.go index 1fb012fea..309dda688 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/local/server.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/local/server.go @@ -174,7 +174,7 @@ func (s *Server) handler(w http.ResponseWriter, r *http.Request) { escapedHeaderErr := html.EscapeString(headerErr) // Note: It is a little weird we handle some errors by not going to the failPage. If they all should, // change this to s.error() and make s.error() write the failPage instead of an error code. - _, _ = w.Write([]byte(fmt.Sprintf(failPage, escapedHeaderErr, desc))) + _, _ = fmt.Fprintf(w, failPage, escapedHeaderErr, desc) s.putResult(Result{Err: fmt.Errorf("%s", desc)}) return diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go index 9b7d3a5f4..47eba9782 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go @@ -311,8 +311,9 @@ func isWaitDeviceCodeErr(err error) bool { return false } var dCErr deviceCodeError - defer c.Resp.Body.Close() body, err := io.ReadAll(c.Resp.Body) + // A close error can't change the result after the response body has been consumed. + _ = c.Resp.Body.Close() if err != nil { return false } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go index dc07c558d..d4fca719e 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go @@ -387,7 +387,7 @@ func (c Client) DeviceCodeResult(ctx context.Context, authParameters authority.A qv.Set(clientID, authParameters.ClientID) addScopeQueryParam(qv, authParameters) - endpoint := strings.Replace(authParameters.Endpoints.TokenEndpoint, "token", "devicecode", -1) + endpoint := strings.ReplaceAll(authParameters.Endpoints.TokenEndpoint, "token", "devicecode") resp := DeviceCodeResponse{} err := c.Comm.URLFormCall(ctx, endpoint, qv, &resp) diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go index 07702ef77..8c3050991 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go @@ -25,14 +25,14 @@ import ( ) const ( - authorizationEndpoint = "https://%v/%v/oauth2/v2.0/authorize" - aadInstanceDiscoveryEndpoint = "https://%v/common/discovery/instance" - tenantDiscoveryEndpointWithRegion = "https://%s.%s/%s/v2.0/.well-known/openid-configuration" - regionName = "REGION_NAME" - defaultAPIVersion = "2021-02-01" - imdsEndpoint = "http://169.254.169.254/metadata/instance/compute?api-version=" + defaultAPIVersion - autoDetectRegion = "TryAutoDetect" - AccessTokenTypeBearer = "Bearer" + authorizationEndpoint = "https://%v/%v/oauth2/v2.0/authorize" + aadInstanceDiscoveryEndpoint = "https://%v/common/discovery/instance" + regionalTenantDiscoveryEndpoint = "https://%s/%s/v2.0/.well-known/openid-configuration" + regionName = "REGION_NAME" + defaultAPIVersion = "2021-02-01" + imdsEndpoint = "http://169.254.169.254/metadata/instance/compute?api-version=" + defaultAPIVersion + autoDetectRegion = "TryAutoDetect" + AccessTokenTypeBearer = "Bearer" ) // These are various hosts that host AAD Instance discovery endpoints. @@ -44,8 +44,8 @@ const ( loginMicrosoftOnline = defaultHost ) -// validRegion matches Azure region names: lowercase alphanumeric and hyphens only. -var validRegion = regexp.MustCompile(`^[a-z][a-z0-9-]*$`) +// validRegion matches Azure region names that are valid lowercase ASCII DNS labels. +var validRegion = regexp.MustCompile(`^[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$`) // jsonCaller is an interface that allows us to mock the JSONCall method. type jsonCaller interface { @@ -134,17 +134,19 @@ func (r *TenantDiscoveryResponse) ValidateIssuerMatchesAuthority(authorityURI st } // Fast path: exact scheme + host match - if issuerURL.Scheme == authorityURL.Scheme && issuerURL.Host == authorityURL.Host { + if issuerURL.Scheme == authorityURL.Scheme && strings.EqualFold(issuerURL.Host, authorityURL.Host) { return nil } // Alias-based acceptance - if aliases != nil && aliases[issuerURL.Host] { - return nil + for alias, trusted := range aliases { + if trusted && strings.EqualFold(alias, issuerURL.Host) { + return nil + } } - issuerHost := issuerURL.Host - authorityHost := authorityURL.Host + issuerHost := strings.ToLower(issuerURL.Host) + authorityHost := strings.ToLower(authorityURL.Host) // Accept if issuer host is trusted if TrustedHost(issuerHost) { @@ -669,25 +671,39 @@ func (c Client) AADInstanceDiscovery(ctx context.Context, authorityInfo Info) (I var err error resp := InstanceDiscoveryResponse{} if authorityInfo.Region != "" && authorityInfo.Region != autoDetectRegion { + if !validRegion.MatchString(authorityInfo.Region) { + return resp, fmt.Errorf("invalid region %q: region must be a lowercase ASCII DNS label of at most 63 characters", authorityInfo.Region) + } region = authorityInfo.Region } else if authorityInfo.Region == autoDetectRegion { region = detectRegion(ctx) } if region != "" { - if !validRegion.MatchString(region) { - return resp, fmt.Errorf("invalid region %q: region must contain only lowercase alphanumeric characters and hyphens", region) - } environment := authorityInfo.Host + canonicalAlias := "" switch environment { case loginMicrosoft, loginWindows, loginSTSWindows, defaultHost: environment = loginMicrosoft + default: + if metadata, ok := GetKnownMetadata(environment); ok { + environment = metadata.PreferredNetwork + if environment != authorityInfo.Host { + canonicalAlias = environment + } + } } - resp.TenantDiscoveryEndpoint = fmt.Sprintf(tenantDiscoveryEndpointWithRegion, region, environment, authorityInfo.Tenant) + regionalEnvironment := fmt.Sprintf("%s.%s", region, environment) + aliases := []string{regionalEnvironment} + if canonicalAlias != "" { + aliases = append(aliases, canonicalAlias) + } + aliases = append(aliases, authorityInfo.Host) + resp.TenantDiscoveryEndpoint = fmt.Sprintf(regionalTenantDiscoveryEndpoint, regionalEnvironment, authorityInfo.Tenant) metadata := InstanceDiscoveryMetadata{ - PreferredNetwork: fmt.Sprintf("%v.%v", region, authorityInfo.Host), + PreferredNetwork: regionalEnvironment, PreferredCache: authorityInfo.Host, - Aliases: []string{fmt.Sprintf("%v.%v", region, authorityInfo.Host), authorityInfo.Host}, + Aliases: aliases, } resp.Metadata = []InstanceDiscoveryMetadata{metadata} } else { @@ -717,8 +733,10 @@ func (c Client) AADInstanceDiscovery(ctx context.Context, authorityInfo Info) (I func detectRegion(ctx context.Context) string { region := os.Getenv(regionName) if region != "" { - region = strings.ReplaceAll(region, " ", "") - return strings.ToLower(region) + if validRegion.MatchString(region) { + return region + } + return "" } // HTTP call to IMDS endpoint to get region // Refer : https://identitydivision.visualstudio.com/DevEx/_git/AuthLibrariesApiReview?path=%2FPinAuthToRegion%2FAAD%20SDK%20Proposal%20to%20Pin%20Auth%20to%20region.md&_a=preview&version=GBdev @@ -726,20 +744,37 @@ func detectRegion(ctx context.Context) string { client := http.Client{ Timeout: time.Duration(2 * time.Second), } - req, _ := http.NewRequestWithContext(ctx, http.MethodGet, imdsEndpoint, nil) + return detectRegionWithClient(ctx, &client, imdsEndpoint) +} + +func detectRegionWithClient(ctx context.Context, client *http.Client, endpoint string) string { + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) req.Header.Set("Metadata", "true") resp, err := client.Do(req) if err == nil { - defer resp.Body.Close() + if resp.StatusCode == http.StatusOK { + response, readErr := io.ReadAll(resp.Body) + // A close error can't change the result after the response body has been consumed. + _ = resp.Body.Close() + if readErr != nil { + return "" + } + return parseRegionFromIMDSResponse(response) + } + _ = resp.Body.Close() } // If the request times out or there is an error, it is retried once - if err != nil || resp.StatusCode != http.StatusOK { - resp, err = client.Do(req) - if err != nil || resp.StatusCode != http.StatusOK { - return "" - } + resp, err = client.Do(req) + if err != nil { + return "" + } + if resp.StatusCode != http.StatusOK { + _ = resp.Body.Close() + return "" } response, err := io.ReadAll(resp.Body) + // A close error can't change the result after the response body has been consumed. + _ = resp.Body.Close() if err != nil { return "" } @@ -760,6 +795,9 @@ func parseRegionFromIMDSResponse(body []byte) string { if err := json.Unmarshal(body, &parsed); err != nil { return "" } + if !validRegion.MatchString(parsed.Location) { + return "" + } return parsed.Location } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/comm.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/comm.go index 87e38798a..ee3053730 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/comm.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/comm.go @@ -240,9 +240,10 @@ func (c *Client) do(ctx context.Context, req *http.Request) ([]byte, error) { if err != nil { return nil, fmt.Errorf("server response error:\n %w", err) } - defer reply.Body.Close() data, err := c.readBody(reply) + // A close error can't change the result after the response body has been consumed. + _ = reply.Body.Close() if err != nil { return nil, fmt.Errorf("could not read the body of an HTTP Response: %w", err) } @@ -274,7 +275,7 @@ func (c *Client) do(ctx context.Context, req *http.Request) ([]byte, error) { // checkResp checks a response object o make sure it is a pointer to a struct. func (c *Client) checkResp(v reflect.Value) error { - if v.Kind() != reflect.Ptr { + if v.Kind() != reflect.Pointer { return fmt.Errorf("bug: resp argument must a *struct, was %T", v.Interface()) } v = v.Elem() diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.go index e3d19886e..9796728ea 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.go @@ -34,15 +34,14 @@ type MexDocument struct { bindings map[string]wsEndpointData } -func updateEndpoint(cached *Endpoint, found Endpoint) { - if cached == nil || cached.Version == TrustUnknown { - *cached = found - return +func updateEndpoint(cached Endpoint, found Endpoint) Endpoint { + if cached.Version == TrustUnknown { + return found } - if (*cached).Version == Trust2005 && found.Version == Trust13 { - *cached = found - return + if cached.Version == Trust2005 && found.Version == Trust13 { + return found } + return cached } // TODO(msal): Someone needs to write tests for everything below. @@ -113,11 +112,12 @@ func bindings(defs Definitions, policies map[string]endpointType) (map[string]ws bindingName := binding.Name specVersion := binding.Operation.Operation.SoapAction - if specVersion == trust13Spec { + switch specVersion { + case trust13Spec: bindings[bindingName] = wsEndpointData{Trust13, policy} - } else if specVersion == trust2005Spec { + case trust2005Spec: bindings[bindingName] = wsEndpointData{Trust2005, policy} - } else { + default: return nil, errors.New("found unknown spec version in mex document") } } @@ -147,9 +147,9 @@ func endpoints(defs Definitions, bindings map[string]wsEndpointData) (userPass, switch binding.EndpointType { case etUsernamePassword: - updateEndpoint(&userPass, endpoint) + userPass = updateEndpoint(userPass, endpoint) case etWindowsTransport: - updateEndpoint(&windows, endpoint) + windows = updateEndpoint(windows, endpoint) default: return Endpoint{}, Endpoint{}, errors.New("found unknown port type in MEX document") } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.go index 92170a762..86e88dd51 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.go @@ -22,12 +22,10 @@ import ( type cacheEntry struct { Endpoints authority.Endpoints ValidForDomainsInList map[string]bool - // Aliases stores host aliases from instance discovery for quick lookup - Aliases map[string]bool } func createcacheEntry(endpoints authority.Endpoints) cacheEntry { - return cacheEntry{endpoints, map[string]bool{}, map[string]bool{}} + return cacheEntry{endpoints, map[string]bool{}} } // AuthorityEndpoint retrieves endpoints from an authority for auth and token acquisition. @@ -61,7 +59,7 @@ func (m *authorityEndpoint) ResolveEndpoints(ctx context.Context, authorityInfo return endpoints, nil } - endpoint, err := m.openIDConfigurationEndpoint(ctx, authorityInfo) + endpoint, metadata, err := m.openIDConfigurationEndpoint(ctx, authorityInfo) if err != nil { return authority.Endpoints{}, err } @@ -77,18 +75,19 @@ func (m *authorityEndpoint) ResolveEndpoints(ctx context.Context, authorityInfo tenant := authorityInfo.Tenant endpoints := authority.NewEndpoints( - strings.Replace(resp.AuthorizationEndpoint, "{tenant}", tenant, -1), - strings.Replace(resp.TokenEndpoint, "{tenant}", tenant, -1), - strings.Replace(resp.Issuer, "{tenant}", tenant, -1), + strings.ReplaceAll(resp.AuthorizationEndpoint, "{tenant}", tenant), + strings.ReplaceAll(resp.TokenEndpoint, "{tenant}", tenant), + strings.ReplaceAll(resp.Issuer, "{tenant}", tenant), authorityInfo.Host) - aliases := m.addCachedEndpoints(authorityInfo, userPrincipalName, endpoints) + aliases := aliasesFromMetadata(metadata, authorityInfo.Host) if err := resp.ValidateIssuerMatchesAuthority(authorityInfo.CanonicalAuthorityURI, aliases); err != nil { return authority.Endpoints{}, fmt.Errorf("ResolveEndpoints(): %w", err) } + m.addCachedEndpoints(authorityInfo, userPrincipalName, endpoints) return endpoints, nil }) if err != nil { @@ -117,7 +116,22 @@ func (m *authorityEndpoint) cachedEndpoints(authorityInfo authority.Info, userPr return authority.Endpoints{}, false } -func (m *authorityEndpoint) addCachedEndpoints(authorityInfo authority.Info, userPrincipalName string, endpoints authority.Endpoints) map[string]bool { +func aliasesFromMetadata(metadata []authority.InstanceDiscoveryMetadata, authorityHost string) map[string]bool { + for _, entry := range metadata { + for _, alias := range entry.Aliases { + if strings.EqualFold(alias, authorityHost) { + aliases := make(map[string]bool, len(entry.Aliases)) + for _, alias := range entry.Aliases { + aliases[alias] = true + } + return aliases + } + } + } + return map[string]bool{} +} + +func (m *authorityEndpoint) addCachedEndpoints(authorityInfo authority.Info, userPrincipalName string, endpoints authority.Endpoints) { m.mu.Lock() defer m.mu.Unlock() @@ -137,40 +151,30 @@ func (m *authorityEndpoint) addCachedEndpoints(authorityInfo authority.Info, use } } - // Extract aliases from instance discovery metadata and add to cache - for _, metadata := range authorityInfo.InstanceDiscoveryMetadata { - for _, alias := range metadata.Aliases { - updatedCacheEntry.Aliases[alias] = true - } - } - m.cache[authorityInfo.CanonicalAuthorityURI] = updatedCacheEntry - return updatedCacheEntry.Aliases } -func (m *authorityEndpoint) openIDConfigurationEndpoint(ctx context.Context, authorityInfo authority.Info) (string, error) { +func (m *authorityEndpoint) openIDConfigurationEndpoint(ctx context.Context, authorityInfo authority.Info) (string, []authority.InstanceDiscoveryMetadata, error) { if authorityInfo.AuthorityType == authority.ADFS { - return fmt.Sprintf("https://%s/adfs/.well-known/openid-configuration", authorityInfo.Host), nil + return fmt.Sprintf("https://%s/adfs/.well-known/openid-configuration", authorityInfo.Host), authorityInfo.InstanceDiscoveryMetadata, nil } else if authorityInfo.AuthorityType == authority.DSTS { - return fmt.Sprintf("https://%s/dstsv2/%s/v2.0/.well-known/openid-configuration", authorityInfo.Host, authority.DSTSTenant), nil + return fmt.Sprintf("https://%s/dstsv2/%s/v2.0/.well-known/openid-configuration", authorityInfo.Host, authority.DSTSTenant), authorityInfo.InstanceDiscoveryMetadata, nil } else if authorityInfo.ValidateAuthority && !authority.TrustedHost(authorityInfo.Host) { resp, err := m.rest.Authority().AADInstanceDiscovery(ctx, authorityInfo) if err != nil { - return "", err + return "", nil, err } - authorityInfo.InstanceDiscoveryMetadata = resp.Metadata - return resp.TenantDiscoveryEndpoint, nil + return resp.TenantDiscoveryEndpoint, resp.Metadata, nil } else if authorityInfo.Region != "" { resp, err := m.rest.Authority().AADInstanceDiscovery(ctx, authorityInfo) if err != nil { - return "", err + return "", nil, err } - authorityInfo.InstanceDiscoveryMetadata = resp.Metadata - return resp.TenantDiscoveryEndpoint, nil + return resp.TenantDiscoveryEndpoint, resp.Metadata, nil } - return authorityInfo.CanonicalAuthorityURI + "v2.0/.well-known/openid-configuration", nil + return authorityInfo.CanonicalAuthorityURI + "v2.0/.well-known/openid-configuration", authorityInfo.InstanceDiscoveryMetadata, nil } func adfsDomainFromUpn(userPrincipalName string) (string, error) { diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go index d19d8376a..1dd67289e 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go @@ -8,4 +8,4 @@ package version const SKU = "MSAL.Go" // Version is the version of this client package that is communicated to the server. -const Version = "1.8.0" +const Version = "1.10.1" diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/azure_ml.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/azure_ml.go index d7cffc295..d638f426e 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/azure_ml.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/azure_ml.go @@ -10,6 +10,7 @@ import ( ) func createAzureMLAuthRequest(ctx context.Context, id ID, resource string) (*http.Request, error) { + // #nosec G704 -- MSI_ENDPOINT is supplied by the Azure ML managed identity host. req, err := http.NewRequestWithContext(ctx, http.MethodGet, os.Getenv(msiEndpointEnvVar), nil) if err != nil { return nil, err diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/cloud_shell.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/cloud_shell.go index be9a0bca3..e6ede188b 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/cloud_shell.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/cloud_shell.go @@ -25,6 +25,7 @@ func createCloudShellAuthRequest(ctx context.Context, resource string) (*http.Re msiDataEncoded := data.Encode() body := io.NopCloser(strings.NewReader(msiDataEncoded)) + // #nosec G704 -- MSI_ENDPOINT is supplied by the Cloud Shell managed identity host. req, err := http.NewRequestWithContext(ctx, http.MethodPost, msiEndpointParsed.String(), body) if err != nil { return nil, fmt.Errorf("error creating http request %s", err) diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/managedidentity.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/managedidentity.go index ecb0fbeca..ffd23bc96 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/managedidentity.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/managedidentity.go @@ -10,6 +10,7 @@ without using credentials. package managedidentity import ( + "bytes" "context" "encoding/json" "fmt" @@ -61,11 +62,11 @@ const ( wwwAuthenticateHeaderName = "www-authenticate" // UAMI query parameter name - miQueryParameterClientId = "client_id" - miQueryParameterObjectId = "object_id" - miQueryParameterPrincipalId = "principal_id" - miQueryParameterResourceIdIMDS = "msi_res_id" - miQueryParameterResourceId = "mi_res_id" + miQueryParameterClientId = "client_id" + miQueryParameterObjectId = "object_id" + miQueryParameterPrincipalId = "principal_id" + miQueryParameterMsiResourceId = "msi_res_id" + miQueryParameterResourceId = "mi_res_id" // IMDS imdsDefaultEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token" @@ -177,6 +178,7 @@ type Client struct { httpClient ops.HTTPClient miType ID source Source + serviceFabricURL string authParams authority.AuthParams retryPolicyEnabled bool canRefresh *atomic.Value @@ -198,13 +200,48 @@ func WithClaims(claims string) AcquireTokenOption { } } -// WithHTTPClient allows for a custom HTTP client to be set. +// WithHTTPClient allows for a custom HTTP client to be set. Flows that must configure the +// transport underlying the client (for example, Service Fabric certificate pinning) require a +// [ClientConfigurer], because a plain ops.HTTPClient exposes no way to apply those requirements; +// pass a [ClientConfigurer] here and MSAL will invoke it to install the configuration it needs. +// +// Only the Service Fabric source currently consumes a [ClientConfigurer]; every other managed +// identity source treats the value as a plain ops.HTTPClient and never calls ConfigureClient. func WithHTTPClient(httpClient ops.HTTPClient) ClientOption { return func(c *Client) { c.httpClient = httpClient } } +// ClientConfigurer is an [ops.HTTPClient] that lets MSAL install the transport and client +// configuration a flow requires, such as Service Fabric certificate pinning. Pass one to +// [WithHTTPClient] and MSAL will call ConfigureClient during [New]. +// +// Only the Service Fabric managed identity source consumes a ClientConfigurer. For every other +// source MSAL uses the value directly as an ops.HTTPClient and does not call ConfigureClient, so +// implementations should not rely on ConfigureClient being invoked outside Service Fabric. +// +// Implementations of ConfigureClient must: +// - call augment exactly once, synchronously, before ConfigureClient returns, passing the +// non-nil client MSAL should build upon; +// - route every subsequent Do call through the client augment returns, without bypassing its +// transport, TLS configuration, or redirect policy; +// - forward CloseIdleConnections to that same client; +// - return any error augment reports and complete all configuration before returning. +// +// MSAL fails closed: if augment reports an error, [New] returns it even when ConfigureClient +// discards the error and returns nil, so a misconfigured client is never returned. +// +// MSAL calls ConfigureClient once, during [New]; a ClientConfigurer need not be safe for +// concurrent configuration. An implementation may wrap additional middleware around the client +// augment returns so long as requests still traverse the augmented transport and redirect policy. +type ClientConfigurer interface { + ops.HTTPClient + // ConfigureClient receives augment, which derives the client MSAL requires from the supplied + // base client. See [ClientConfigurer] for the contract implementations must satisfy. + ConfigureClient(augment func(*http.Client) (*http.Client, error)) error +} + func WithRetryPolicyDisabled() ClientOption { return func(c *Client) { c.retryPolicyEnabled = false @@ -223,11 +260,6 @@ func New(id ID, options ...ClientOption) (Client, error) { // Check for user-assigned restrictions based on the source switch source { - case AzureArc: - switch id.(type) { - case UserAssignedClientID, UserAssignedResourceID, UserAssignedObjectID: - return Client{}, errors.New("Azure Arc doesn't support user-assigned managed identities") - } case AzureML: switch id.(type) { case UserAssignedObjectID, UserAssignedResourceID: @@ -274,6 +306,45 @@ func New(id ID, options ...ClientOption) (Client, error) { for _, option := range options { option(&client) } + if source == ServiceFabric { + serviceFabricURL, err := serviceFabricEndpoint() + if err != nil { + return Client{}, err + } + + switch tt := client.httpClient.(type) { + case ClientConfigurer: + augmentCalls := 0 + var augmentErr error + err = tt.ConfigureClient(func(c *http.Client) (*http.Client, error) { + augmentCalls++ + var configured *http.Client + configured, augmentErr = serviceFabricCertificateVerifiedHTTPClient(c) + return configured, augmentErr + }) + // Fail closed: if augment failed, New must return that error even when + // ConfigureClient ignores it, so a caller never receives a client that lacks + // the mandatory certificate pinning and redirect policy. + if err == nil { + err = augmentErr + } + if err == nil && augmentCalls != 1 { + return Client{}, fmt.Errorf("ConfigureClient must call augment exactly once to install the Service Fabric client, got %d calls", augmentCalls) + } + case *http.Client: + var serviceFabricClient *http.Client + serviceFabricClient, err = serviceFabricCertificateVerifiedHTTPClient(tt) + client.httpClient = serviceFabricClient + default: + return Client{}, errors.New("Service Fabric managed identity requires an *http.Client or a ClientConfigurer") + } + + if err != nil { + return Client{}, err + } + + client.serviceFabricURL = serviceFabricURL + } fakeAuthInfo, err := authority.NewInfoFromAuthorityURI("https://login.microsoftonline.com/managed_identity", false, true) if err != nil { return Client{}, err @@ -414,7 +485,7 @@ func (c Client) acquireTokenForAzureML(ctx context.Context, resource string) (Au } func (c Client) acquireTokenForServiceFabric(ctx context.Context, resource string) (AuthResult, error) { - req, err := createServiceFabricAuthRequest(ctx, resource) + req, err := createServiceFabricAuthRequest(ctx, c.serviceFabricURL, resource) if err != nil { return AuthResult{}, err } @@ -426,7 +497,7 @@ func (c Client) acquireTokenForServiceFabric(ctx context.Context, resource strin } func (c Client) acquireTokenForAzureArc(ctx context.Context, resource string) (AuthResult, error) { - req, err := createAzureArcAuthRequest(ctx, resource, "") + req, err := createAzureArcAuthRequest(ctx, c.miType, resource, "") if err != nil { return AuthResult{}, err } @@ -435,7 +506,8 @@ func (c Client) acquireTokenForAzureArc(ctx context.Context, resource string) (A if err != nil { return AuthResult{}, err } - defer response.Body.Close() + // The response body is unused; a close error can't change its status or headers. + _ = response.Body.Close() if response.StatusCode != http.StatusUnauthorized { return AuthResult{}, fmt.Errorf("expected a 401 response, received %d", response.StatusCode) @@ -446,7 +518,7 @@ func (c Client) acquireTokenForAzureArc(ctx context.Context, resource string) (A return AuthResult{}, err } - secondRequest, err := createAzureArcAuthRequest(ctx, resource, string(secret)) + secondRequest, err := createAzureArcAuthRequest(ctx, c.miType, resource, string(secret)) if err != nil { return AuthResult{}, err } @@ -455,9 +527,77 @@ func (c Client) acquireTokenForAzureArc(ctx context.Context, resource string) (A if err != nil { return AuthResult{}, err } + if err := verifyAzureArcUserAssignedIdentity(c.miType, tokenResponse); err != nil { + return AuthResult{}, err + } return authResultFromToken(c.authParams, tokenResponse) } +// setUserAssignedQueryParam adds the user-assigned identity selector to params. Azure Arc and IMDS +// both use the IMDS "msi_res_id" spelling for the resource id; a system-assigned identity adds +// nothing. An unsupported ID type is rejected (New already validates the caller's input). +func setUserAssignedQueryParam(params url.Values, id ID) error { + switch t := id.(type) { + case UserAssignedClientID: + params.Set(miQueryParameterClientId, string(t)) + case UserAssignedResourceID: + params.Set(miQueryParameterMsiResourceId, string(t)) + case UserAssignedObjectID: + params.Set(miQueryParameterObjectId, string(t)) + case systemAssignedValue: + default: + return fmt.Errorf("unsupported type %T", id) + } + return nil +} + +// verifyAzureArcUserAssignedIdentity fails closed when a user-assigned identity was requested +// but Azure Arc did not confirm it in the token response. A legacy Azure Arc agent ignores the +// client_id / msi_res_id / object_id selector and silently returns the machine's system-assigned +// identity. An agent that supports user-assigned managed identity echoes the identity it used in +// the token response; when that echo is missing or does not match the requested selector, MSAL +// must not hand back a token for a different identity than the one requested. +func verifyAzureArcUserAssignedIdentity(id ID, token accesstokens.TokenResponse) error { + // Reuse the request selector mapping so the request and validation stay in lock-step. + selector := url.Values{} + if err := setUserAssignedQueryParam(selector, id); err != nil { + return err + } + if len(selector) == 0 { + // System-assigned: there is no requested identity to confirm. + return nil + } + var name, requested string + for k := range selector { + name, requested = k, selector.Get(k) + } + // Accept either resource-id spelling on the echo as a safety net; Azure Arc returns msi_res_id. + keys := []string{name} + if name == miQueryParameterMsiResourceId { + keys = append(keys, miQueryParameterResourceId) + } + echoed := additionalStringField(token.AdditionalFields, keys...) + // Compare case-insensitively: client_id / object_id are GUIDs, and an ARM resource id can + // legitimately differ in segment casing. + if echoed == "" || !strings.EqualFold(echoed, requested) { + return errors.New("azure arc did not confirm the requested user-assigned managed identity in the token response; the agent likely does not support user-assigned managed identities and returned the system-assigned identity") + } + return nil +} + +// additionalStringField returns the first non-empty string value among the given keys from a +// token response's additional (untyped) fields. +func additionalStringField(fields map[string]interface{}, keys ...string) string { + for _, k := range keys { + if v, ok := fields[k]; ok { + if s, ok := v.(string); ok && s != "" { + return s + } + } + } + return "" +} + func authResultFromToken(authParams authority.AuthParams, token accesstokens.TokenResponse) (AuthResult, error) { if cacheManager == nil { return AuthResult{}, errors.New("cache instance is nil") @@ -490,33 +630,73 @@ func contains[T comparable](list []T, element T) bool { return false } +// bufferResponseBody reads resp.Body fully into memory and replaces it with an +// in-memory reader. This lets the caller consume the response after the +// per-attempt context that produced it has been canceled. See issue #634. +func bufferResponseBody(resp *http.Response) error { + if resp == nil || resp.Body == nil { + return nil + } + body, err := io.ReadAll(resp.Body) + // A close error can't change the result after the response body has been consumed. + _ = resp.Body.Close() + if err != nil { + return err + } + resp.Body = io.NopCloser(bytes.NewReader(body)) + return nil +} + // retry performs an HTTP request with retries based on the provided options. func (c Client) retry(maxRetries int, req *http.Request) (*http.Response, error) { var resp *http.Response var err error + // cancelPrev cancels the context of the previous attempt. It is invoked only + // after that attempt's body has been drained, so the transport connection can + // still be reused, while avoiding the resource retention of deferring every + // per-attempt cancel until retry() returns. + var cancelPrev context.CancelFunc + retrylist := retryStatusCodes + if c.source == DefaultToIMDS { + retrylist = retryCodesForIMDS + } for attempt := 0; attempt < maxRetries; attempt++ { tryCtx, tryCancel := context.WithTimeout(req.Context(), time.Minute) - defer tryCancel() if resp != nil && resp.Body != nil { _, _ = io.Copy(io.Discard, resp.Body) - resp.Body.Close() + // The previous response is discarded, so a close error is non-actionable. + _ = resp.Body.Close() + } + if cancelPrev != nil { + cancelPrev() } + cancelPrev = tryCancel cloneReq := req.Clone(tryCtx) resp, err = c.httpClient.Do(cloneReq) - retrylist := retryStatusCodes - if c.source == DefaultToIMDS { - retrylist = retryCodesForIMDS - } - if err == nil && !contains(retrylist, resp.StatusCode) { - return resp, nil + succeeded := err == nil && !contains(retrylist, resp.StatusCode) + if succeeded || attempt == maxRetries-1 { + // Buffer the body into memory while tryCtx is still alive so the + // caller can read resp.Body after we cancel this attempt's context. + // Without this, the deferred/explicit cancel would race the caller's + // read and surface as "context canceled" on an otherwise successful + // response. See issue #634. + if bufErr := bufferResponseBody(resp); bufErr != nil && err == nil { + err = bufErr + } + tryCancel() + return resp, err } select { case <-time.After(time.Second): case <-req.Context().Done(): err = req.Context().Err() + tryCancel() return resp, err } } + if cancelPrev != nil { + cancelPrev() + } return resp, err } @@ -534,7 +714,8 @@ func (c Client) getTokenForRequest(req *http.Request, resource string) (accessto return r, err } responseBytes, err := io.ReadAll(resp.Body) - defer resp.Body.Close() + // A close error can't change the result after the response body has been consumed. + _ = resp.Body.Close() if err != nil { return r, err } @@ -566,6 +747,12 @@ func (c Client) getTokenForRequest(req *http.Request, resource string) (accessto Err: fmt.Errorf("error parsing the json error: %s", err), } } + // Capture the raw response fields so source-specific logic (such as the Azure Arc + // user-assigned identity echo check) can read fields the typed response drops. + var additionalFields map[string]interface{} + if json.Unmarshal(responseBytes, &additionalFields) == nil { + r.AdditionalFields = additionalFields + } r.GrantedScopes.Slice = append(r.GrantedScopes.Slice, resource) return r, err @@ -573,6 +760,7 @@ func (c Client) getTokenForRequest(req *http.Request, resource string) (accessto func createAppServiceAuthRequest(ctx context.Context, id ID, resource string) (*http.Request, error) { identityEndpoint := os.Getenv(identityEndpointEnvVar) + // #nosec G704 -- IDENTITY_ENDPOINT is supplied by the App Service managed identity host. req, err := http.NewRequestWithContext(ctx, http.MethodGet, identityEndpoint, nil) if err != nil { return nil, err @@ -605,19 +793,12 @@ func createIMDSAuthRequest(ctx context.Context, id ID, resource string) (*http.R msiParameters.Set(apiVersionQueryParameterName, imdsAPIVersion) msiParameters.Set(resourceQueryParameterName, resource) - switch t := id.(type) { - case UserAssignedClientID: - msiParameters.Set(miQueryParameterClientId, string(t)) - case UserAssignedResourceID: - msiParameters.Set(miQueryParameterResourceIdIMDS, string(t)) - case UserAssignedObjectID: - msiParameters.Set(miQueryParameterObjectId, string(t)) - case systemAssignedValue: // not adding anything - default: - return nil, fmt.Errorf("unsupported type %T", id) + if err := setUserAssignedQueryParam(msiParameters, id); err != nil { + return nil, err } msiEndpoint.RawQuery = msiParameters.Encode() + // #nosec G704 -- imdsDefaultEndpoint is a library constant for the Azure IMDS link-local endpoint. req, err := http.NewRequestWithContext(ctx, http.MethodGet, msiEndpoint.String(), nil) if err != nil { return nil, fmt.Errorf("error creating http request %s", err) @@ -629,7 +810,7 @@ func createIMDSAuthRequest(ctx context.Context, id ID, resource string) (*http.R return req, nil } -func createAzureArcAuthRequest(ctx context.Context, resource string, key string) (*http.Request, error) { +func createAzureArcAuthRequest(ctx context.Context, id ID, resource string, key string) (*http.Request, error) { identityEndpoint := os.Getenv(identityEndpointEnvVar) if identityEndpoint == "" { identityEndpoint = azureArcEndpoint @@ -644,7 +825,14 @@ func createAzureArcAuthRequest(ctx context.Context, resource string, key string) msiParameters.Set(apiVersionQueryParameterName, azureArcAPIVersion) msiParameters.Set(resourceQueryParameterName, resource) + // Azure Arc honors the IMDS msi_res_id spelling for the resource-id selector; the mi_res_id + // spelling is silently ignored and returns the system-assigned identity. + if err := setUserAssignedQueryParam(msiParameters, id); err != nil { + return nil, err + } + msiEndpoint.RawQuery = msiParameters.Encode() + // #nosec G704 -- IDENTITY_ENDPOINT is supplied by the Azure Arc managed identity host. req, err := http.NewRequestWithContext(ctx, http.MethodGet, msiEndpoint.String(), nil) if err != nil { return nil, fmt.Errorf("error creating http request %s", err) diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/servicefabric.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/servicefabric.go index 535065e9d..c7b44ac74 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/servicefabric.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/servicefabric.go @@ -5,13 +5,124 @@ package managedidentity import ( "context" + + /* #nosec */ + "crypto/sha1" + "crypto/subtle" + "crypto/tls" + "crypto/x509" + "encoding/hex" + "errors" "net/http" "os" + "strings" + "unicode" ) -func createServiceFabricAuthRequest(ctx context.Context, resource string) (*http.Request, error) { - identityEndpoint := os.Getenv(identityEndpointEnvVar) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, identityEndpoint, nil) +// serviceFabricCertificateVerifiedHTTPClient derives a client with Service Fabric's required certificate pinning. +// Only standard clients and transports can be safely cloned and augmented without changing the +// caller's behavior for other requests. +func serviceFabricCertificateVerifiedHTTPClient(httpClient *http.Client) (*http.Client, error) { + if httpClient == nil { + return nil, errors.New("managed identity on Service Fabric requires a non-nil *http.Client") + } + pin, err := serviceFabricThumbprint(os.Getenv(identityServerThumbprintEnvVar)) + if err != nil { + return nil, err + } + + derivedClient := *httpClient + + var callerTransport *http.Transport + if httpClient.Transport == nil { + var ok bool + callerTransport, ok = http.DefaultTransport.(*http.Transport) + if !ok { + return nil, errors.New("managed identity on Service Fabric requires a standard *http.Transport") + } + } else { + var ok bool + callerTransport, ok = httpClient.Transport.(*http.Transport) + if !ok { + return nil, errors.New("managed identity on Service Fabric requires a standard *http.Transport") + } + } + //nolint:staticcheck // DialTLS must be rejected because it bypasses TLSClientConfig. + if callerTransport.DialTLS != nil || callerTransport.DialTLSContext != nil { + return nil, errors.New("managed identity on Service Fabric does not support a transport with custom TLS dialing") + } + if callerTransport.TLSClientConfig != nil && + (callerTransport.TLSClientConfig.VerifyPeerCertificate != nil || callerTransport.TLSClientConfig.VerifyConnection != nil) { + return nil, errors.New("managed identity on Service Fabric does not support custom TLS verification") + } + derivedTransport := callerTransport.Clone() + // Give the derived transport its own HTTP/2 state so it can never serve a Service Fabric request over a + // connection from the caller's pool. golang.org/x/net/http2.ConfigureTransports (used by azure-sdk-for-go's + // default transport) installs an "h2" callback that retains the original connection pool, and Transport.Clone + // copies that callback by reference. Without clearing it, a pre-existing, unpinned connection to the same + // authority could be reused, bypassing the certificate pin below. Resetting TLSNextProto makes the standard + // library lazily re-establish HTTP/2 with a fresh pool governed by this transport's pinned TLS configuration. + derivedTransport.TLSNextProto = nil + tlsConfig := derivedTransport.TLSClientConfig.Clone() + if tlsConfig == nil { + tlsConfig = &tls.Config{} + } + tlsConfig.InsecureSkipVerify = true // #nosec G402 -- VerifyConnection below pins the Service Fabric self-signed certificate. + tlsConfig.VerifyConnection = func(connectionState tls.ConnectionState) error { + if len(connectionState.PeerCertificates) == 0 { + return errors.New("TLS connection to Service Fabric did not provide a certificate") + } + if subtle.ConstantTimeCompare(serviceFabricCertificateThumbprint(connectionState.PeerCertificates[0]), pin) != 1 { + return errors.New("TLS certificate thumbprint from Service Fabric did not match IDENTITY_SERVER_THUMBPRINT") + } + return nil + } + derivedTransport.TLSClientConfig = tlsConfig + derivedClient.Transport = derivedTransport + derivedClient.CheckRedirect = func(*http.Request, []*http.Request) error { + return errors.New("redirects are not permitted for managed identity on Service Fabric") + } + return &derivedClient, nil +} + +func serviceFabricEndpoint() (string, error) { + endpoint := os.Getenv(identityEndpointEnvVar) + // #nosec G704 -- the URL is restricted to HTTPS and the caller pins its certificate thumbprint. + request, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + return "", err + } + if request.URL.Scheme != "https" || request.URL.Host == "" { + return "", errors.New("managed identity endpoint for Service Fabric must use HTTPS") + } + return request.URL.String(), nil +} + +func serviceFabricThumbprint(value string) ([]byte, error) { + normalized := strings.Map(func(r rune) rune { + if r == ':' || unicode.IsSpace(r) { + return -1 + } + return r + }, value) + if len(normalized) != 40 { + return nil, errors.New("IDENTITY_SERVER_THUMBPRINT must be a SHA-1 certificate thumbprint") + } + thumbprint, err := hex.DecodeString(normalized) + if err != nil || len(thumbprint) != 20 { + return nil, errors.New("IDENTITY_SERVER_THUMBPRINT must be a SHA-1 certificate thumbprint") + } + return thumbprint, nil +} + +func serviceFabricCertificateThumbprint(certificate *x509.Certificate) []byte { + // Service Fabric exposes SHA-1 certificate thumbprints through IDENTITY_SERVER_THUMBPRINT. + thumbprint := sha1.Sum(certificate.Raw) /* #nosec G401 -- Service Fabric publishes SHA-1 thumbprints. */ // NOSONAR -- Service Fabric defines IDENTITY_SERVER_THUMBPRINT as the SHA-1 hash of its self-signed endpoint certificate; this compares that platform-defined identifier, not a security digest. + return thumbprint[:] +} + +func createServiceFabricAuthRequest(ctx context.Context, endpoint, resource string) (*http.Request, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, err } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/public/public.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/public/public.go index 97c615452..93f6e702e 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/public/public.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/public/public.go @@ -72,7 +72,7 @@ type clientOptions struct { func (p *clientOptions) validate() error { u, err := url.Parse(p.authority) if err != nil { - return fmt.Errorf("Authority options cannot be URL parsed: %w", err) + return fmt.Errorf("authority options cannot be URL parsed: %w", err) } if u.Scheme != "https" { return fmt.Errorf("Authority(%s) did not start with https://", u.String()) diff --git a/vendor/modules.txt b/vendor/modules.txt index 853e45c27..4a4848781 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -16,7 +16,7 @@ dario.cat/mergo # github.com/42wim/httpsig v1.2.4 ## explicit; go 1.25.0 github.com/42wim/httpsig -# github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 +# github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.3 ## explicit; go 1.25.0 github.com/Azure/azure-sdk-for-go/sdk/azcore github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource @@ -37,25 +37,26 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore/policy github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing -# github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 +# github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.15.0 ## explicit; go 1.25.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal/customtokenproxy -# github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 -## explicit; go 1.24.0 +# github.com/Azure/azure-sdk-for-go/sdk/internal v1.13.0 +## explicit; go 1.25.0 github.com/Azure/azure-sdk-for-go/sdk/internal/diag github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo github.com/Azure/azure-sdk-for-go/sdk/internal/exported github.com/Azure/azure-sdk-for-go/sdk/internal/log github.com/Azure/azure-sdk-for-go/sdk/internal/poller github.com/Azure/azure-sdk-for-go/sdk/internal/temporal +github.com/Azure/azure-sdk-for-go/sdk/internal/transport github.com/Azure/azure-sdk-for-go/sdk/internal/uuid # github.com/Azure/go-ansiterm v0.0.0-20261013165250-51075c6ea6bc ## explicit; go 1.25.0 github.com/Azure/go-ansiterm github.com/Azure/go-ansiterm/winterm -# github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 +# github.com/AzureAD/microsoft-authentication-library-for-go v1.10.1 ## explicit; go 1.18 github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential