diff --git a/docs/azrepos-misp.md b/docs/azrepos-misp.md index 60a3c3e..9d11dbf 100644 --- a/docs/azrepos-misp.md +++ b/docs/azrepos-misp.md @@ -108,6 +108,7 @@ Type|Git Configuration|Environment Variable -|-|- Client Secret|[`credential.azreposServicePrincipalSecret`][gcm-sp-secret-config]|[`GCM_AZREPOS_SP_SECRET`][gcm-sp-secret-env] Certificate|[`credential.azreposServicePrincipalCertificateThumbprint`][gcm-sp-cert-config]|[`GCM_AZREPOS_SP_CERT_THUMBPRINT`][gcm-sp-cert-env] +Send X5C|[`credential.azreposServicePrincipalCertificateSendX5C`][gcm-sp-cert-x5c-config]|[`GCM_AZREPOS_SP_CERT_SEND_X5C`][gcm-sp-cert-x5c-env] The value for these options should be the client secret or the thumbrint of the certificate that is associated with the Service Principal. @@ -126,4 +127,6 @@ current user or the local machine. [gcm-sp-secret-config]: https://gh.io/gcm/config#credentialazreposserviceprincipalsecret [gcm-sp-secret-env]: https://gh.io/gcm/env#GCM_AZREPOS_SP_SECRET [gcm-sp-cert-config]: https://gh.io/gcm/config#credentialazreposserviceprincipalcertificatethumbprint +[gcm-sp-cert-x5c-config]: https://gh.io/gcm/config#credentialazreposserviceprincipalcertificatesendx5c [gcm-sp-cert-env]: https://gh.io/gcm/env#GCM_AZREPOS_SP_CERT_THUMBPRINT +[gcm-sp-cert-x5c-env]: https://gh.io/gcm/env#GCM_AZREPOS_SP_CERT_SEND_X5C diff --git a/docs/configuration.md b/docs/configuration.md index 2439c92..f6993df 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -858,6 +858,7 @@ You must also set at least one authentication mechanism if you set this value: - [credential.azreposServicePrincipalSecret][credential-azrepos-sp-secret] - [credential.azreposServicePrincipalCertificateThumbprint][credential-azrepos-sp-cert-thumbprint] +- [credential.azreposServicePrincipalCertificateSendX5C][credential-azrepos-sp-cert-x5c] For more information about service principals, see the Azure DevOps [documentation][azrepos-sp-mid]. @@ -904,6 +905,25 @@ git config --global credential.azreposServicePrincipalCertificateThumbprint "9b6 --- +### credential.azreposServicePrincipalCertificateSendX5C + +When using a certificate for [service principal][service-principal] authentication, this configuration +specifies whether the X5C claim should be should be sent to the STS. Sending the x5c +enables application developers to achieve easy certificate rollover in Azure AD: +this method will send the public certificate to Azure AD along with the token request, +so that Azure AD can use it to validate the subject name based on a trusted issuer +policy. This saves the application admin from the need to explicitly manage the +certificate rollover. For details see [https://aka.ms/msal-net-sni](https://aka.ms/msal-net-sni). + +#### Example + +```shell +git config --global credential.azreposServicePrincipalCertificateSendX5C true +``` +**Also see: [GCM_AZREPOS_SP_CERT_SEND_X5C][gcm-azrepos-sp-cert-x5c]** + +--- + ### trace2.normalTarget Turns on Trace2 Normal Format tracing - see [Git's Trace2 Normal Format @@ -1034,6 +1054,8 @@ Defaults to disabled. [credential-azrepos-sp]: #credentialazreposserviceprincipal [credential-azrepos-sp-secret]: #credentialazreposserviceprincipalsecret [credential-azrepos-sp-cert-thumbprint]: #credentialazreposserviceprincipalcertificatethumbprint +[credential-azrepos-sp-cert-x5c]: #credentialazreposserviceprincipalcertificatesendx5c [gcm-azrepos-service-principal]: environment.md#GCM_AZREPOS_SERVICE_PRINCIPAL [gcm-azrepos-sp-secret]: environment.md#GCM_AZREPOS_SP_SECRET [gcm-azrepos-sp-cert-thumbprint]: environment.md#GCM_AZREPOS_SP_CERT_THUMBPRINT +[gcm-azrepos-sp-cert-x5c]: environment.md#GCM_AZREPOS_SP_CERT_SEND_X5C diff --git a/docs/environment.md b/docs/environment.md index 18f3f05..edda0d7 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -1039,6 +1039,32 @@ export GCM_AZREPOS_SP_CERT_THUMBPRINT="9b6555292e4ea21cbc2ebd23e66e2f91ebbe92dc" --- +### GCM_AZREPOS_SP_CERT_SEND_X5C + +When using a certificate for service principal authentication, this configuration +specifies whether the X5C claim should be should be sent to the STS. Sending the x5c +enables application developers to achieve easy certificate rollover in Azure AD: +this method will send the public certificate to Azure AD along with the token request, +so that Azure AD can use it to validate the subject name based on a trusted issuer +policy. This saves the application admin from the need to explicitly manage the +certificate rollover. For details see [https://aka.ms/msal-net-sni](https://aka.ms/msal-net-sni). + +#### Windows + +```batch +SET GCM_AZREPOS_SP_CERT_SEND_X5C="true" +``` + +#### macOS/Linux + +```bash +export GCM_AZREPOS_SP_CERT_SEND_X5C="true" +``` + +**Also see: [credential.azreposServicePrincipalCertificateSendX5C][credential-azrepos-sp-cert-x5c]** + +--- + ### GIT_TRACE2 Turns on Trace2 Normal Format tracing - see [Git's Trace2 Normal Format @@ -1184,6 +1210,8 @@ Defaults to disabled. [gcm-azrepos-sp]: #gcm_azrepos_service_principal [gcm-azrepos-sp-secret]: #gcm_azrepos_sp_secret [gcm-azrepos-sp-cert-thumbprint]: #gcm_azrepos_sp_cert_thumbprint +[gcm-azrepos-sp-cert-x5c]: #gcm_azrepos_sp_cert_send_x5c [credential-azrepos-sp]: configuration.md#credentialazreposserviceprincipal [credential-azrepos-sp-secret]: configuration.md#credentialazreposserviceprincipalsecret [credential-azrepos-sp-cert-thumbprint]: configuration.md#credentialazreposserviceprincipalcertificatethumbprint +[credential-azrepos-sp-cert-x5c]: configuration.md#credentialazreposserviceprincipalcertificatesendx5c diff --git a/src/shared/Core/Authentication/MicrosoftAuthentication.cs b/src/shared/Core/Authentication/MicrosoftAuthentication.cs index b39cc1a..12bccf5 100644 --- a/src/shared/Core/Authentication/MicrosoftAuthentication.cs +++ b/src/shared/Core/Authentication/MicrosoftAuthentication.cs @@ -92,6 +92,11 @@ namespace GitCredentialManager.Authentication /// If both and are set, the certificate will be used. /// public string ClientSecret { get; set; } + + /// + /// Whether the authentication should send X5C + /// + public bool SendX5C { get; set; } } public interface IMicrosoftAuthenticationResult @@ -269,12 +274,14 @@ namespace GitCredentialManager.Authentication try { - AuthenticationResult result = await app.AcquireTokenForClient(scopes).ExecuteAsync(); + Context.Trace.WriteLine($"Sending with X5C: '{sp.SendX5C}'."); + AuthenticationResult result = await app.AcquireTokenForClient(scopes).WithSendX5C(sp.SendX5C).ExecuteAsync();; + return new MsalResult(result); } catch (Exception ex) { - Context.Trace.WriteLine($"Failed to acquire token for service principal '{sp.TenantId}/{sp.TenantId}'."); + Context.Trace.WriteLine($"Failed to acquire token for service principal '{sp.TenantId}/{sp.Id}'."); Context.Trace.WriteException(ex); throw; } diff --git a/src/shared/Microsoft.AzureRepos/AzureDevOpsConstants.cs b/src/shared/Microsoft.AzureRepos/AzureDevOpsConstants.cs index c46f08c..a282d4e 100644 --- a/src/shared/Microsoft.AzureRepos/AzureDevOpsConstants.cs +++ b/src/shared/Microsoft.AzureRepos/AzureDevOpsConstants.cs @@ -44,6 +44,7 @@ namespace Microsoft.AzureRepos public const string ServicePrincipalId = "GCM_AZREPOS_SERVICE_PRINCIPAL"; public const string ServicePrincipalSecret = "GCM_AZREPOS_SP_SECRET"; public const string ServicePrincipalCertificateThumbprint = "GCM_AZREPOS_SP_CERT_THUMBPRINT"; + public const string ServicePrincipalCertificateSendX5C = "GCM_AZREPOS_SP_CERT_SEND_X5C"; public const string ManagedIdentity = "GCM_AZREPOS_MANAGEDIDENTITY"; } @@ -59,6 +60,7 @@ namespace Microsoft.AzureRepos public const string ServicePrincipal = "azreposServicePrincipal"; public const string ServicePrincipalSecret = "azreposServicePrincipalSecret"; public const string ServicePrincipalCertificateThumbprint = "azreposServicePrincipalCertificateThumbprint"; + public const string ServicePrincipalCertificateSendX5C = "azreposServicePrincipalCertificateSendX5C"; public const string ManagedIdentity = "azreposManagedIdentity"; } } diff --git a/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs b/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs index 55b1449..1d5c649 100644 --- a/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs +++ b/src/shared/Microsoft.AzureRepos/AzureReposHostProvider.cs @@ -549,6 +549,12 @@ namespace Microsoft.AzureRepos if (hasCertThumbprint) { + sp.SendX5C = _context.Settings.TryGetSetting( + AzureDevOpsConstants.EnvironmentVariables.ServicePrincipalCertificateSendX5C, + Constants.GitConfiguration.Credential.SectionName, + AzureDevOpsConstants.GitConfiguration.Credential.ServicePrincipalCertificateSendX5C, + out string certHasX5CStr) && certHasX5CStr.ToBooleanyOrDefault(false); + X509Certificate2 cert = X509Utils.GetCertificateByThumbprint(certThumbprint); if (cert is null) {