Skip to content

Commit

Permalink
-TimeoutSeconds for Managed Identity, Managed Identity auth as firs…
Browse files Browse the repository at this point in the history
…t option in noninteractive auth (#89)

* upgrade gitversion config to support version #87

* -TimeoutSeconds for managed identity and noninteractive, adds managedidentity to noninteractive auth, closes #85, closes #86

* gitversion vars

* gitversion is-main-branch explicit

* gitversion GitHubFlow default template
  • Loading branch information
PalmEmanuel committed Aug 21, 2024
1 parent e82204e commit 305ef8d
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 40 deletions.
12 changes: 11 additions & 1 deletion .github/actions/build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,21 @@ runs:
shell: pwsh
run: ./build.ps1 -ResolveDependency -Task noop

# Replace dot in semVer with dash, for Sampler validation in pre-release
- name: Format semVer for Sampler
id: formatSemVer
shell: pwsh
run: |
$SemVer = '${{ steps.gitversion.outputs.semVer }}'
# Replace last dot with dash for Sampler to accept it as pre-release, does not allow dots in pre-release name
$SemVer = $SemVer -replace '^([\d\.]+\-\w+)\.(\d+)$','$1-$2'
Add-Content -Path $env:GITHUB_OUTPUT -Value "formattedSemVer=$SemVer"
- name: Build module
shell: pwsh
run: ./build.ps1 -tasks pack
env:
ModuleVersion: ${{ env.gitVersion.NuGetVersionV2 }}
ModuleVersion: ${{ steps.formatSemVer.outputs.formattedSemVer }}

- name: Publish build artifacts
uses: actions/upload-artifact@v4
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ The format is based on and uses the types of changes according to [Keep a Change
### Added

- Adds related links links to blog posts for Get-AzToken and the parameters -WorkloadIdentity & -ExternalToken
- Added `-TimeoutSeconds` parameter for Managed Identity authentication and non-interactive authentication
- Added Managed Identity authentication as first option of non-interactive login

## [2.2.10] - 2024-05-22

Expand Down
131 changes: 109 additions & 22 deletions GitVersion.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,116 @@
strategies:
- Mainline
next-version: 0.0.1
major-version-bump-message: '(breaking\schange|breaking|major)\b'
minor-version-bump-message: '(adds?|features?|minor)\b'
patch-version-bump-message: '\s?(fix|patch)'
assembly-versioning-scheme: MajorMinorPatch
assembly-file-versioning-scheme: MajorMinorPatch
tag-prefix: '[vV]?'
version-in-branch-pattern: (?<version>[vV]?\d+(\.\d+)?(\.\d+)?).*
major-version-bump-message: '\+semver:\s?(breaking|major)'
minor-version-bump-message: '\+semver:\s?(feature|minor)'
patch-version-bump-message: '\+semver:\s?(fix|patch)'
no-bump-message: '\+semver:\s?(none|skip)'
assembly-informational-format: 'MajorMinorPatch'
tag-pre-release-weight: 60000
commit-date-format: yyyy-MM-dd
merge-message-formats: {}
update-build-number: true
semantic-version-format: Strict
strategies:
- Fallback
- ConfiguredNextVersion
- MergeMessage
- TaggedCommit
- TrackReleaseBranches
- VersionInBranchName
branches:
main:
label: preview
regex: ^main$
label: 'preview'
increment: Patch
pull-request:
label: PR
feature:
label: useBranchName
increment: Minor
regex: f(eature(s)?)?[\/-]
source-branches: ['main']
hotfix:
label: fix
prevent-increment:
of-merged-branch: true
track-merge-target: false
track-merge-message: true
regex: ^master$|^main$
source-branches: []
is-source-branch-for: []
tracks-release-branches: false
is-release-branch: false
is-main-branch: true
pre-release-weight: 55000
release:
mode: ManualDeployment
label: beta
increment: Patch
regex: (hot)?fix(es)?[\/-]
source-branches: ['main']

prevent-increment:
of-merged-branch: true
when-branch-merged: false
when-current-commit-tagged: false
track-merge-target: false
track-merge-message: true
regex: ^releases?[/-](?<BranchName>.+)
source-branches:
- main
is-source-branch-for: []
tracks-release-branches: false
is-release-branch: true
is-main-branch: false
pre-release-weight: 30000
feature:
mode: ManualDeployment
label: '{BranchName}'
increment: Inherit
prevent-increment:
when-current-commit-tagged: false
track-merge-message: true
regex: ^features?[/-](?<BranchName>.+)
source-branches:
- main
- release
is-source-branch-for: []
is-main-branch: false
pre-release-weight: 30000
pull-request:
mode: ContinuousDelivery
label: PullRequest
increment: Inherit
prevent-increment:
of-merged-branch: true
when-current-commit-tagged: false
label-number-pattern: '[/-](?<number>\d+)'
track-merge-message: true
regex: ^(pull|pull\-requests|pr)[/-]
source-branches:
- main
- release
- feature
is-source-branch-for: []
pre-release-weight: 30000
unknown:
mode: ManualDeployment
label: '{BranchName}'
increment: Inherit
prevent-increment:
when-current-commit-tagged: false
track-merge-message: false
regex: (?<BranchName>.+)
source-branches:
- main
- release
- feature
- pull-request
is-source-branch-for: []
is-main-branch: false
ignore:
sha: []
merge-message-formats: {}
mode: ContinuousDelivery
label: '{BranchName}'
increment: Inherit
prevent-increment:
of-merged-branch: false
when-branch-merged: false
when-current-commit-tagged: true
track-merge-target: false
track-merge-message: true
commit-message-incrementing: Enabled
regex: ''
source-branches: []
is-source-branch-for: []
tracks-release-branches: false
is-release-branch: false
is-main-branch: false
9 changes: 5 additions & 4 deletions docs/help/Get-AzToken.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ Gets a new Azure access token.

### NonInteractive (Default)
```
Get-AzToken [[-Resource] <String>] [[-Scope] <String[]>] [-TenantId <String>] [-Claim <String>] [-Force]
[<CommonParameters>]
Get-AzToken [[-Resource] <String>] [[-Scope] <String[]>] [-TenantId <String>] [-Claim <String>]
[-TimeoutSeconds <Int32>] [-Force] [<CommonParameters>]
```

### Cache
Expand All @@ -43,7 +43,8 @@ Get-AzToken [[-Resource] <String>] [[-Scope] <String[]>] [-TenantId <String>] [-
### ManagedIdentity
```
Get-AzToken [[-Resource] <String>] [[-Scope] <String[]>] [-TenantId <String>] [-Claim <String>]
[-ClientId <String>] [-ManagedIdentity] [-Force] [<CommonParameters>]
[-ClientId <String>] [-TimeoutSeconds <Int32>] [-ManagedIdentity] [-Force]
[<CommonParameters>]
```

### WorkloadIdentity
Expand Down Expand Up @@ -409,7 +410,7 @@ The number of seconds to wait until the login times out.

```yaml
Type: Int32
Parameter Sets: Interactive, DeviceCode
Parameter Sets: NonInteractive, Interactive, DeviceCode, ManagedIdentity
Aliases:
Required: False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ internal static partial class TokenManager
/// <summary>
/// Gets token as a managed identity.
/// </summary>
internal static AzToken GetTokenManagedIdentity(string resource, string[] scopes, string? claims, string? clientId, string? tenantId, CancellationToken cancellationToken) =>
taskFactory.Run(() => GetTokenManagedIdentityAsync(resource, scopes, claims, clientId, tenantId, cancellationToken));
internal static AzToken GetTokenManagedIdentity(string resource, string[] scopes, string? claims, string? clientId, string? tenantId, int timeoutSeconds, CancellationToken cancellationToken) =>
taskFactory.Run(() => GetTokenManagedIdentityAsync(resource, scopes, claims, clientId, tenantId, timeoutSeconds, cancellationToken));

/// <summary>
/// Gets token as a managed identity.
Expand All @@ -20,6 +20,7 @@ internal static async Task<AzToken> GetTokenManagedIdentityAsync(
string? claims,
string? clientId,
string? tenantId,
int timeoutSeconds,
CancellationToken cancellationToken)
{
var fullScopes = scopes.Select(s => $"{resource.TrimEnd('/')}/{s}").ToArray();
Expand All @@ -28,7 +29,14 @@ internal static async Task<AzToken> GetTokenManagedIdentityAsync(
// Re-use the previous managed identity credential if client id didn't change
if (credential is not ManagedIdentityCredential || previousClientId != clientId)
{
credential = new ManagedIdentityCredential(clientId);
credential = new ManagedIdentityCredential(clientId, options: new TokenCredentialOptions{
Retry = {
NetworkTimeout = TimeSpan.FromSeconds(timeoutSeconds),
MaxRetries = 0,
Delay = TimeSpan.Zero,
MaxDelay = TimeSpan.Zero
}
});
}

previousClientId = clientId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using Azure.Core;
using Azure.Core.Diagnostics;
using Azure.Core.Pipeline;
using Azure.Identity;
using System.Diagnostics.Tracing;
using System.Text.RegularExpressions;

namespace PipeHow.AzAuth;
Expand All @@ -9,8 +12,8 @@ internal static partial class TokenManager
/// <summary>
/// Gets token noninteractively.
/// </summary>
internal static AzToken GetTokenNonInteractive(string resource, string[] scopes, string? claims, string? tenantId, CancellationToken cancellationToken) =>
taskFactory.Run(() => GetTokenNonInteractiveAsync(resource, scopes, claims, tenantId, cancellationToken));
internal static AzToken GetTokenNonInteractive(string resource, string[] scopes, string? claims, string? tenantId, int? timeoutSeconds, int managedIdentityTimeoutSeconds, CancellationToken cancellationToken) =>
taskFactory.Run(() => GetTokenNonInteractiveAsync(resource, scopes, claims, tenantId, timeoutSeconds, managedIdentityTimeoutSeconds, cancellationToken));

/// <summary>
/// Gets token noninteractively.
Expand All @@ -20,19 +23,42 @@ internal static async Task<AzToken> GetTokenNonInteractiveAsync(
string[] scopes,
string? claims,
string? tenantId,
int? timeoutSeconds,
int managedIdentityTimeoutSeconds,
CancellationToken cancellationToken)
{
var fullScopes = scopes.Select(s => $"{resource.TrimEnd('/')}/{s}").ToArray();

// If timeoutSeconds is not null, create a new TokenCredentialOptions with the specified timeout
// Otherwise, set to null to use default timeout
TokenCredentialOptions? genericTimeoutOptions = timeoutSeconds.HasValue ? new TokenCredentialOptions
{
Retry = {
NetworkTimeout = TimeSpan.FromSeconds(timeoutSeconds.Value),
MaxRetries = 0,
Delay = TimeSpan.Zero,
MaxDelay = TimeSpan.Zero
}
} : null;

// Create our own credential chain because we want to change the order
var sources = new List<TokenCredential>()
{
new EnvironmentCredential(),
new AzurePowerShellCredential(),
new AzureCliCredential(),
new VisualStudioCodeCredential(),
new VisualStudioCredential(),
new SharedTokenCacheCredential()
// ManagedIdentityCredential with custom timeout
new ManagedIdentityCredential(options: new TokenCredentialOptions{
Retry = {
NetworkTimeout = TimeSpan.FromSeconds(managedIdentityTimeoutSeconds),
MaxRetries = 0,
Delay = TimeSpan.Zero,
MaxDelay = TimeSpan.Zero
}
}),
new EnvironmentCredential(genericTimeoutOptions),
new AzurePowerShellCredential(genericTimeoutOptions as AzurePowerShellCredentialOptions),
new AzureCliCredential(genericTimeoutOptions as AzureCliCredentialOptions),
new VisualStudioCodeCredential(genericTimeoutOptions as VisualStudioCodeCredentialOptions),
new VisualStudioCredential(genericTimeoutOptions as VisualStudioCredentialOptions),
new SharedTokenCacheCredential(genericTimeoutOptions as SharedTokenCacheCredentialOptions)
};

// If user authenticated interactively in the same session and tenant didn't change, add it as the first option to find tokens from
Expand Down
21 changes: 19 additions & 2 deletions source/AzAuth.PS/Cmdlets/GetAzToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ public class GetAzToken : PSLoggerCmdletBase
[ArgumentCompleter(typeof(ExistingAccounts))]
public string Username { get; set; }

[Parameter(ParameterSetName = "NonInteractive")]
[Parameter(ParameterSetName = "Interactive")]
[Parameter(ParameterSetName = "ManagedIdentity")]
[Parameter(ParameterSetName = "DeviceCode")]
[ValidateRange(1, int.MaxValue)]
public int TimeoutSeconds { get; set; } = 120;
Expand Down Expand Up @@ -146,15 +148,25 @@ protected override void EndProcessing()

if (ParameterSetName == "NonInteractive")
{
// If user didn't specify a timeout, default to 1 second for managed identity
int managedIdentityTimeoutSeconds = 1;
int? noninteractiveTimeoutSeconds = null;
if (MyInvocation.BoundParameters.ContainsKey("TimeoutSeconds"))
{
managedIdentityTimeoutSeconds = TimeoutSeconds;
noninteractiveTimeoutSeconds = TimeoutSeconds;
}

WriteVerbose(@"Looking for a token from the following sources:
Managed Identity (https://learn.microsoft.com/en-us/dotnet/api/azure.identity.managedidentitycredential)
Environment variables (https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential)
Azure PowerShell (https://learn.microsoft.com/en-us/dotnet/api/azure.identity.azurepowershellcredential)
Azure CLI (https://learn.microsoft.com/en-us/dotnet/api/azure.identity.azureclicredential)
Visual Studio Code (https://learn.microsoft.com/en-us/dotnet/api/azure.identity.visualstudiocodecredential)
Visual Studio (https://learn.microsoft.com/en-us/dotnet/api/azure.identity.visualstudiocredential)
Shared token cache (https://learn.microsoft.com/en-us/dotnet/api/azure.identity.sharedtokencachecredential)
");
WriteObject(TokenManager.GetTokenNonInteractive(Resource, Scope, Claim, TenantId, stopProcessing.Token));
WriteObject(TokenManager.GetTokenNonInteractive(Resource, Scope, Claim, TenantId, noninteractiveTimeoutSeconds, managedIdentityTimeoutSeconds, stopProcessing.Token));
}
else if (ParameterSetName == "Cache")
{
Expand Down Expand Up @@ -190,8 +202,13 @@ Shared token cache (https://learn.microsoft.com/en-us/dotnet/api/azure.identity.
}
else if (ManagedIdentity.IsPresent)
{
// If user didn't specify a timeout, default to 1 second for managed identity
if (!MyInvocation.BoundParameters.ContainsKey("TimeoutSeconds"))
{
TimeoutSeconds = 1;
}
WriteVerbose("Getting token using a managed identity (https://learn.microsoft.com/en-us/dotnet/api/azure.identity.managedidentitycredential).");
WriteObject(TokenManager.GetTokenManagedIdentity(Resource, Scope, Claim, ClientId, TenantId, stopProcessing.Token));
WriteObject(TokenManager.GetTokenManagedIdentity(Resource, Scope, Claim, ClientId, TenantId, TimeoutSeconds, stopProcessing.Token));
}
else if (WorkloadIdentity.IsPresent)
{
Expand Down
2 changes: 2 additions & 0 deletions tests/Get-AzToken.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ BeforeDiscovery {
Name = 'TimeoutSeconds'
Type = 'int'
ParameterSets = @(
@{ Name = 'NonInteractive'; Mandatory = $false }
@{ Name = 'Interactive'; Mandatory = $false }
@{ Name = 'ManagedIdentity'; Mandatory = $false }
@{ Name = 'DeviceCode'; Mandatory = $false }
)
}
Expand Down

0 comments on commit 305ef8d

Please sign in to comment.