Skip to content

Commit

Permalink
Provide certificate file login method (#14738)
Browse files Browse the repository at this point in the history
* Use certificate file as input

* Address review comments
  • Loading branch information
msJinLei authored Jun 8, 2021
1 parent a820ce7 commit b877850
Show file tree
Hide file tree
Showing 8 changed files with 442 additions and 46 deletions.
42 changes: 39 additions & 3 deletions src/Accounts/Accounts/Account/ConnectAzureRmAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Management.Automation;
using System.Security;
using System.Threading;
Expand Down Expand Up @@ -52,6 +53,7 @@ public class ConnectAzureRmAccountCommand : AzureContextModificationCmdlet, IMod
public const string UserWithCredentialParameterSet = "UserWithCredential";
public const string ServicePrincipalParameterSet = "ServicePrincipalWithSubscriptionId";
public const string ServicePrincipalCertificateParameterSet= "ServicePrincipalCertificateWithSubscriptionId";
public const string ServicePrincipalCertificateFileParameterSet = "ServicePrincipalCertificateFileWithSubscriptionId";
public const string AccessTokenParameterSet = "AccessTokenWithSubscriptionId";
public const string ManagedServiceParameterSet = "ManagedServiceLogin";
public const string MSIEndpointVariable = "MSI_ENDPOINT";
Expand Down Expand Up @@ -79,12 +81,16 @@ public class ConnectAzureRmAccountCommand : AzureContextModificationCmdlet, IMod

[Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet,
Mandatory = true, HelpMessage = "SPN")]
[Parameter(ParameterSetName = ServicePrincipalCertificateFileParameterSet,
Mandatory = true, HelpMessage = "SPN")]
public string ApplicationId { get; set; }

[Parameter(ParameterSetName = ServicePrincipalParameterSet,
Mandatory = true)]
[Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet,
Mandatory = false)]
[Parameter(ParameterSetName = ServicePrincipalCertificateFileParameterSet,
Mandatory = false)]
public SwitchParameter ServicePrincipal { get; set; }

[Parameter(ParameterSetName = UserParameterSet,
Expand All @@ -97,6 +103,8 @@ public class ConnectAzureRmAccountCommand : AzureContextModificationCmdlet, IMod
Mandatory = false, HelpMessage = "Tenant name or ID")]
[Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet,
Mandatory = true, HelpMessage = "Tenant name or ID")]
[Parameter(ParameterSetName = ServicePrincipalCertificateFileParameterSet,
Mandatory = true, HelpMessage = "Tenant name or ID")]
[Parameter(ParameterSetName = ManagedServiceParameterSet,
Mandatory = false, HelpMessage = "Optional tenant name or ID")]
[Alias("Domain", "TenantId")]
Expand Down Expand Up @@ -138,6 +146,8 @@ public class ConnectAzureRmAccountCommand : AzureContextModificationCmdlet, IMod
Mandatory = false, HelpMessage = "Subscription Name or ID", ValueFromPipeline = true)]
[Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet,
Mandatory = false, HelpMessage = "Subscription Name or ID", ValueFromPipeline = true)]
[Parameter(ParameterSetName = ServicePrincipalCertificateFileParameterSet,
Mandatory = false, HelpMessage = "Subscription Name or ID", ValueFromPipeline = true)]
[Parameter(ParameterSetName = AccessTokenParameterSet,
Mandatory = false, HelpMessage = "Subscription Name or ID", ValueFromPipeline = true)]
[Parameter(ParameterSetName = ManagedServiceParameterSet,
Expand Down Expand Up @@ -187,6 +197,7 @@ public class ConnectAzureRmAccountCommand : AzureContextModificationCmdlet, IMod
[Parameter(ParameterSetName = UserWithCredentialParameterSet, Mandatory = false, HelpMessage = "Max subscription number to populate contexts after login. Default is " + DefaultMaxContextPopulationString + ". To populate all subscriptions to contexts, set to -1.")]
[Parameter(ParameterSetName = ServicePrincipalParameterSet, Mandatory = false, HelpMessage = "Max subscription number to populate contexts after login. Default is " + DefaultMaxContextPopulationString + ". To populate all subscriptions to contexts, set to -1.")]
[Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet, Mandatory = false, HelpMessage = "Max subscription number to populate contexts after login. Default is " + DefaultMaxContextPopulationString + ". To populate all subscriptions to contexts, set to -1.")]
[Parameter(ParameterSetName = ServicePrincipalCertificateFileParameterSet, Mandatory = false, HelpMessage = "Max subscription number to populate contexts after login. Default is " + DefaultMaxContextPopulationString + ". To populate all subscriptions to contexts, set to -1.")]
[Parameter(ParameterSetName = AccessTokenParameterSet, Mandatory = false, HelpMessage = "Max subscription number to populate contexts after login. Default is " + DefaultMaxContextPopulationString + ". To populate all subscriptions to contexts, set to -1.")]
[Parameter(ParameterSetName = ManagedServiceParameterSet, Mandatory = false, HelpMessage = "Max subscription number to populate contexts after login. Default is " + DefaultMaxContextPopulationString + ". To populate all subscriptions to contexts, set to -1.")]
[PSDefaultValue(Help = DefaultMaxContextPopulationString, Value = DefaultMaxContextPopulation)]
Expand All @@ -201,9 +212,17 @@ public class ConnectAzureRmAccountCommand : AzureContextModificationCmdlet, IMod
[Parameter(Mandatory = false, HelpMessage = "Overwrite the existing context with the same name, if any.")]
public SwitchParameter Force { get; set; }

[Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet, Mandatory = false, HelpMessage = "Specifies if the x5c claim (public key of the certificate) should be sent to the STS to achieve easy certificate rollover in Azure AD.")]
[Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet, HelpMessage = "Specifies if the x5c claim (public key of the certificate) should be sent to the STS to achieve easy certificate rollover in Azure AD.")]
[Parameter(ParameterSetName = ServicePrincipalCertificateFileParameterSet, HelpMessage = "Specifies if the x5c claim (public key of the certificate) should be sent to the STS to achieve easy certificate rollover in Azure AD.")]
public SwitchParameter SendCertificateChain { get; set; }


[Parameter(ParameterSetName = ServicePrincipalCertificateFileParameterSet, Mandatory = true, HelpMessage = "The path of certficate file in pkcs#12 format.")]
public String CertificatePath { get; set; }

[Parameter(ParameterSetName = ServicePrincipalCertificateFileParameterSet, HelpMessage = "The password required to access the pkcs#12 certificate file.")]
public SecureString CertificatePassword { get; set; }

protected override IAzureContext DefaultContext
{
get
Expand Down Expand Up @@ -305,6 +324,7 @@ public override void ExecuteCmdlet()
azureAccount.SetProperty(AzureAccount.Property.KeyVaultAccessToken, KeyVaultAccessToken);
break;
case ServicePrincipalCertificateParameterSet:
case ServicePrincipalCertificateFileParameterSet:
case ServicePrincipalParameterSet:
azureAccount.Type = AzureAccount.AccountType.ServicePrincipal;
break;
Expand Down Expand Up @@ -345,7 +365,23 @@ public override void ExecuteCmdlet()
azureAccount.SetThumbprint(CertificateThumbprint);
}

if (ParameterSetName == ServicePrincipalCertificateParameterSet && SendCertificateChain)
if( !string.IsNullOrWhiteSpace(CertificatePath))
{
var resolvedPath = this.SessionState.Path.GetResolvedPSPathFromPSPath(CertificatePath).FirstOrDefault()?.Path;
if (string.IsNullOrEmpty(resolvedPath))
{
var parametersLog = $"- Invalid certificate path :'{CertificatePath}'.";
throw new InvalidOperationException(parametersLog);
}
azureAccount.SetProperty(AzureAccount.Property.CertificatePath, resolvedPath);
if (CertificatePassword != null)
{
azureAccount.SetProperty(AzureAccount.Property.CertificatePassword, CertificatePassword.ConvertToString());
}
}

if ((ParameterSetName == ServicePrincipalCertificateParameterSet || ParameterSetName == ServicePrincipalCertificateFileParameterSet)
&& SendCertificateChain)
{
azureAccount.SetProperty(AzureAccount.Property.SendCertificateChain, SendCertificateChain.ToString());
bool supressWarningOrError = false;
Expand All @@ -368,7 +404,7 @@ public override void ExecuteCmdlet()
azureAccount.SetProperty(AzureAccount.Property.Tenants, Tenant);
}

if (azureAccount.Type == AzureAccount.AccountType.ServicePrincipal && string.IsNullOrEmpty(CertificateThumbprint))
if (azureAccount.Type == AzureAccount.AccountType.ServicePrincipal && password != null)
{
azureAccount.SetProperty(AzureAccount.Property.ServicePrincipalSecret, password.ConvertToString());
if (GetContextModificationScope() == ContextModificationScope.CurrentUser)
Expand Down
3 changes: 2 additions & 1 deletion src/Accounts/Accounts/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@
-->

## Upcoming Release
* Supported certificate file as input parameter of Connect-AzAccount

## Version 2.3.0
* Upgraded Azure.Identity to 1.4 and MSAL to 4.30.1
* Removed obsolete parameters `ManagedServiceHostName`, `ManagedServicePort` and `ManagedServiceSecret` of cmdlet `Connect-AzAccount`, environment variables `MSI_ENDPOINT` and `MSI_SECRET` could be used instead
* Customize display format of PSAzureRmAccount to hide secret of service principal [#14208]
* Customized display format of PSAzureRmAccount to hide secret of service principal [#14208]
* Added optional parameter `AuthScope` to `Connect-AzAccount` to support enhanced authentication of data plane features
* Set retry times by environment variable [#14748]
* Supported subject name issuer authentication
Expand Down
83 changes: 70 additions & 13 deletions src/Accounts/Accounts/help/Connect-AzAccount.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ Connect-AzAccount [-Environment <String>] -CertificateThumbprint <String> -Appli
[<CommonParameters>]
```

### ServicePrincipalCertificateFileWithSubscriptionId
```
Connect-AzAccount [-Environment <String>] -ApplicationId <String> [-ServicePrincipal] -Tenant <String>
[-Subscription <String>] [-ContextName <String>] [-SkipContextPopulation] [-MaxContextPopulation <Int32>]
[-Force] [-SendCertificateChain] -CertificatePath <String> [-CertificatePassword <SecureString>]
[-Scope <ContextModificationScope>] [-DefaultProfile <IAzureContextContainer>] [-WhatIf] [-Confirm]
[<CommonParameters>]
```

### AccessTokenWithSubscriptionId
```
Connect-AzAccount [-Environment <String>] [-Tenant <String>] -AccessToken <String> [-GraphAccessToken <String>]
Expand Down Expand Up @@ -184,21 +193,21 @@ more information on creating a self-signed certificates and assigning them permi
[Use Azure PowerShell to create a service principal with a certificate](/azure/active-directory/develop/howto-authenticate-service-principal-powershell)

```powershell
$Thumbprint = '0SZTNJ34TCCMUJ5MJZGR8XQD3S0RVHJBA33Z8ZXV'
$TenantId = '4cd76576-b611-43d0-8f2b-adcb139531bf'
$ApplicationId = '3794a65a-e4e4-493d-ac1d-f04308d712dd'
$Thumbprint = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
$TenantId = 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyy'
$ApplicationId = '00000000-0000-0000-0000-00000000'
Connect-AzAccount -CertificateThumbprint $Thumbprint -ApplicationId $ApplicationId -Tenant $TenantId -ServicePrincipal
```

```Output
Account SubscriptionName TenantId Environment
------- ---------------- -------- -----------
xxxx-xxxx-xxxx-xxxx Subscription1 xxxx-xxxx-xxxx-xxxx AzureCloud
Account SubscriptionName TenantId Environment
------- ---------------- -------- -----------
xxxxxxxx-xxxx-xxxx-xxxxxxxxx Subscription1 yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyy AzureCloud
Account : 3794a65a-e4e4-493d-ac1d-f04308d712dd
Account : xxxxxxxx-xxxx-xxxx-xxxxxxxx
SubscriptionName : MyTestSubscription
SubscriptionId : 85f0f653-1f86-4d2c-a9f1-042efc00085c
TenantId : 4cd76576-b611-43d0-8f2b-adcb139531bf
SubscriptionId : zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzz
TenantId : yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyy
Environment : AzureCloud
```

Expand All @@ -216,6 +225,24 @@ Account SubscriptionName TenantId Environment
yyyy-yyyy-yyyy-yyyy Subscription1 xxxx-xxxx-xxxx-xxxx AzureCloud
```

### Example 9: Connect using certificate file

This example connects to an Azure account using certificate-based service principal authentication.
The certificate file, which is specified by `CertficatePath`, should contains both certificate and private key as the input.

```powershell
$securePassword = $plainPassword | ConvertTo-SecureString -AsPlainText -Force
$TenantId = 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyy'
$ApplicationId = 'zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzz'
Connect-AzAccount -ServicePrincipal -ApplicationId $ApplicationId -TenantId $TenantId -CertificatePath './certificatefortest.pfx' -CertificatePassword $securePassword
```

```Output
Account SubscriptionName TenantId Environment
------- ---------------- -------- -----------
xxxxxxxx-xxxx-xxxx-xxxxxxxx Subscription1 yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyy AzureCloud
```

## PARAMETERS

### -AccessToken
Expand Down Expand Up @@ -275,7 +302,7 @@ Application ID of the service principal.
```yaml
Type: System.String
Parameter Sets: ServicePrincipalCertificateWithSubscriptionId
Parameter Sets: ServicePrincipalCertificateWithSubscriptionId, ServicePrincipalCertificateFileWithSubscriptionId
Aliases:

Required: True
Expand All @@ -300,6 +327,36 @@ Accept pipeline input: False
Accept wildcard characters: False
```
### -CertificatePassword
The password required to access the pkcs#12 certificate file.
```yaml
Type: System.Security.SecureString
Parameter Sets: ServicePrincipalCertificateFileWithSubscriptionId
Aliases:

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -CertificatePath
The path of certficate file in pkcs#12 format.
```yaml
Type: System.String
Parameter Sets: ServicePrincipalCertificateFileWithSubscriptionId
Aliases:

Required: True
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -CertificateThumbprint
Certificate Hash or Thumbprint.
Expand Down Expand Up @@ -486,7 +543,7 @@ Specifies if the x5c claim (public key of the certificate) should be sent to the

```yaml
Type: System.Management.Automation.SwitchParameter
Parameter Sets: ServicePrincipalCertificateWithSubscriptionId
Parameter Sets: ServicePrincipalCertificateWithSubscriptionId, ServicePrincipalCertificateFileWithSubscriptionId
Aliases:
Required: False
Expand Down Expand Up @@ -514,7 +571,7 @@ Accept wildcard characters: False

```yaml
Type: System.Management.Automation.SwitchParameter
Parameter Sets: ServicePrincipalCertificateWithSubscriptionId
Parameter Sets: ServicePrincipalCertificateWithSubscriptionId, ServicePrincipalCertificateFileWithSubscriptionId
Aliases:
Required: False
Expand Down Expand Up @@ -594,7 +651,7 @@ Accept wildcard characters: False

```yaml
Type: System.String
Parameter Sets: ServicePrincipalWithSubscriptionId, ServicePrincipalCertificateWithSubscriptionId
Parameter Sets: ServicePrincipalWithSubscriptionId, ServicePrincipalCertificateWithSubscriptionId, ServicePrincipalCertificateFileWithSubscriptionId
Aliases: Domain, TenantId
Required: True
Expand Down
Loading

0 comments on commit b877850

Please sign in to comment.