Skip to content

Commit aacf013

Browse files
authored
Add experimental feature to AzureCLIV2 which keeps the session running in the background in the case of ARM service connections with workload identity federation scheme (#19989)
Today, any attempts made to get the access token for a different resource than originally will fail with the Entra error AADSTS700024 after the IdToken expires, which is after 10 minutes. The same happens 60 minutes after the issuance date of the original access token, when it expires, and we cannot refresh. This change is a tactical solution to this problem that is to be used until az-cli supports this internally. This is an EXPERIMENTAL feature which means we provide no guarantees and no support for it. That said, it should work well for 99.9% of the cases. Please note that this feature can be removed at any point in time, particularly when az-cli delivers the long-term solution to this problem.
1 parent 0ca90ff commit aacf013

File tree

13 files changed

+215
-32
lines changed

13 files changed

+215
-32
lines changed

Tasks/AzureCLIV2/Strings/resources.resjson/en-US/resources.resjson

+6-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
"loc.input.help.powerShellIgnoreLASTEXITCODE": "If this is false, the line `if ((Test-Path -LiteralPath variable:\\LASTEXITCODE)) { exit $LASTEXITCODE }` is appended to the end of your script. This will cause the last exit code from an external command to be propagated as the exit code of powershell. Otherwise the line is not appended to the end of your script.",
3232
"loc.input.label.visibleAzLogin": "az login output visibility",
3333
"loc.input.help.visibleAzLogin": "If this is set to true, az login command will output to the task. Setting it to false will suppress the az login output",
34+
"loc.input.label.keepAzSessionActive": "[Experimental] Keep Azure CLI session active",
35+
"loc.input.help.keepAzSessionActive": "When enabled, this task will continuously sign into Azure to avoid AADSTS700024 errors when requesting access tokens beyond the IdToken expiry date. Note that this feature is EXPERIMENTAL, may not work in all scenarios and you are using it without any guarantees. Valid only for service connections using the Workload Identity Federation authentication scheme.",
3436
"loc.messages.ScriptReturnCode": "Script exited with return code: %d",
3537
"loc.messages.ScriptFailed": "Script failed with error: %s",
3638
"loc.messages.ScriptFailedStdErr": "Script has output to stderr. Failing as failOnStdErr is set to true.",
@@ -49,5 +51,8 @@
4951
"loc.messages.GlobalCliConfigAgentVersionWarning": "For agent version < 2.115.0, only global Azure CLI configuration can be used",
5052
"loc.messages.UnacceptedScriptLocationValue": "%s is not a valid value for task input 'Script Location' (scriptLocation in YAML). Value can either be'inlineScript' or 'scriptPath'",
5153
"loc.messages.ExpiredServicePrincipalMessageWithLink": "Secret expired, update service connection at %s See https://aka.ms/azdo-rm-workload-identity-conversion to learn more about conversion to secret-less service connections.",
52-
"loc.messages.ProxyConfig": "az tool is configured to use %s as proxy server"
54+
"loc.messages.ProxyConfig": "az tool is configured to use %s as proxy server",
55+
"loc.messages.FailedToRefreshAzSession": "The following error occurred while trying to refresh az-cli session: %s",
56+
"loc.messages.RefreshingAzSession": "Attempting to refresh az-cli session...",
57+
"loc.messages.KeepingAzSessionActiveUnsupportedScheme": "The 'keepAzSessionActive' input might be used only for workload identity federation ARM service connection. The referenced service endpoint auth scheme was unexpected: %s. Change the scheme or remove 'keepAzSessionActive' input."
5358
}

Tasks/AzureCLIV2/azureclitask.ts

+34-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getHandlerFromToken, WebApi } from "azure-devops-node-api";
88
import { ITaskApi } from "azure-devops-node-api/TaskApi";
99

1010
const FAIL_ON_STDERR: string = "FAIL_ON_STDERR";
11+
const AZ_SESSION_REFRESH_INTERVAL_MS: number = 480000; // 8 minutes, 2 minutes before IdToken expiry date
1112

1213
export class azureclitask {
1314

@@ -41,8 +42,23 @@ export class azureclitask {
4142
this.setConfigDirectory();
4243
this.setAzureCloudBasedOnServiceEndpoint();
4344
var connectedService: string = tl.getInput("connectedServiceNameARM", true);
45+
const authorizationScheme = tl.getEndpointAuthorizationScheme(connectedService, true).toLowerCase();
46+
4447
await this.loginAzureRM(connectedService);
4548

49+
var keepAzSessionActive: boolean = tl.getBoolInput('keepAzSessionActive', false);
50+
var stopRefreshingSession: () => void = () => {};
51+
if (keepAzSessionActive) {
52+
// This is a tactical workaround to keep the session active for the duration of the task to avoid AADSTS700024 errors.
53+
// This is a temporary solution until the az cli provides a way to refresh the session.
54+
if (authorizationScheme !== 'workloadidentityfederation') {
55+
const errorMessage = tl.loc('KeepingAzSessionActiveUnsupportedScheme', authorizationScheme);
56+
tl.error(errorMessage);
57+
throw errorMessage;
58+
}
59+
stopRefreshingSession = this.keepRefreshingAzSession(connectedService);
60+
}
61+
4662
let errLinesCount: number = 0;
4763
let aggregatedErrorLines: string[] = [];
4864
tool.on('errline', (errorLine: string) => {
@@ -52,8 +68,7 @@ export class azureclitask {
5268
errLinesCount++;
5369
});
5470

55-
var addSpnToEnvironment: boolean = tl.getBoolInput('addSpnToEnvironment', false);
56-
var authorizationScheme = tl.getEndpointAuthorizationScheme(connectedService, true).toLowerCase();
71+
const addSpnToEnvironment: boolean = tl.getBoolInput('addSpnToEnvironment', false);
5772
if (!!addSpnToEnvironment && authorizationScheme == 'serviceprincipal') {
5873
exitCode = await tool.exec({
5974
failOnStdErr: false,
@@ -92,6 +107,10 @@ export class azureclitask {
92107
}
93108
}
94109
finally {
110+
if (keepAzSessionActive) {
111+
stopRefreshingSession();
112+
}
113+
95114
if (scriptType) {
96115
await scriptType.cleanUp();
97116
}
@@ -276,6 +295,19 @@ export class azureclitask {
276295

277296
return response.oidcToken;
278297
}
298+
299+
private static keepRefreshingAzSession(connectedService: string): () => void {
300+
const intervalId = setInterval(async () => {
301+
try {
302+
tl.debug(tl.loc('RefreshingAzSession'));
303+
await this.loginAzureRM(connectedService);
304+
} catch (error) {
305+
tl.warning(tl.loc('FailedToRefreshAzSession', error));
306+
}
307+
}, AZ_SESSION_REFRESH_INTERVAL_MS);
308+
309+
return () => clearInterval(intervalId);
310+
}
279311
}
280312

281313
tl.setResourcePath(path.join(__dirname, "task.json"));

Tasks/AzureCLIV2/task.json

+15-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"version": {
2121
"Major": 2,
2222
"Minor": 241,
23-
"Patch": 0
23+
"Patch": 2
2424
},
2525
"minimumAgentVersion": "2.0.0",
2626
"instanceNameFormat": "Azure CLI $(scriptPath)",
@@ -180,7 +180,16 @@
180180
"required": false,
181181
"helpMarkDown": "If this is set to true, az login command will output to the task. Setting it to false will suppress the az login output",
182182
"groupName": "advanced"
183-
}
183+
},
184+
{
185+
"name": "keepAzSessionActive",
186+
"type": "boolean",
187+
"label": "[Experimental] Keep Azure CLI session active",
188+
"defaultValue": "false",
189+
"required": false,
190+
"helpMarkDown": "When enabled, this task will continuously sign into Azure to avoid AADSTS700024 errors when requesting access tokens beyond the IdToken expiry date. Note that this feature is EXPERIMENTAL, may not work in all scenarios and you are using it without any guarantees. Valid only for service connections using the Workload Identity Federation authentication scheme.",
191+
"groupName": "advanced"
192+
}
184193
],
185194
"execution": {
186195
"Node10": {
@@ -211,6 +220,9 @@
211220
"GlobalCliConfigAgentVersionWarning": "For agent version < 2.115.0, only global Azure CLI configuration can be used",
212221
"UnacceptedScriptLocationValue": "%s is not a valid value for task input 'Script Location' (scriptLocation in YAML). Value can either be'inlineScript' or 'scriptPath'",
213222
"ExpiredServicePrincipalMessageWithLink": "Secret expired, update service connection at %s See https://aka.ms/azdo-rm-workload-identity-conversion to learn more about conversion to secret-less service connections.",
214-
"ProxyConfig":"az tool is configured to use %s as proxy server"
223+
"ProxyConfig":"az tool is configured to use %s as proxy server",
224+
"FailedToRefreshAzSession": "The following error occurred while trying to refresh az-cli session: %s",
225+
"RefreshingAzSession": "Attempting to refresh az-cli session...",
226+
"KeepingAzSessionActiveUnsupportedScheme": "The 'keepAzSessionActive' input might be used only for workload identity federation ARM service connection. The referenced service endpoint auth scheme was unexpected: %s. Change the scheme or remove 'keepAzSessionActive' input."
215227
}
216228
}

Tasks/AzureCLIV2/task.loc.json

+14-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"version": {
2121
"Major": 2,
2222
"Minor": 241,
23-
"Patch": 0
23+
"Patch": 2
2424
},
2525
"minimumAgentVersion": "2.0.0",
2626
"instanceNameFormat": "ms-resource:loc.instanceNameFormat",
@@ -180,6 +180,15 @@
180180
"required": false,
181181
"helpMarkDown": "ms-resource:loc.input.help.visibleAzLogin",
182182
"groupName": "advanced"
183+
},
184+
{
185+
"name": "keepAzSessionActive",
186+
"type": "boolean",
187+
"label": "ms-resource:loc.input.label.keepAzSessionActive",
188+
"defaultValue": "false",
189+
"required": false,
190+
"helpMarkDown": "ms-resource:loc.input.help.keepAzSessionActive",
191+
"groupName": "advanced"
183192
}
184193
],
185194
"execution": {
@@ -211,6 +220,9 @@
211220
"GlobalCliConfigAgentVersionWarning": "ms-resource:loc.messages.GlobalCliConfigAgentVersionWarning",
212221
"UnacceptedScriptLocationValue": "ms-resource:loc.messages.UnacceptedScriptLocationValue",
213222
"ExpiredServicePrincipalMessageWithLink": "ms-resource:loc.messages.ExpiredServicePrincipalMessageWithLink",
214-
"ProxyConfig": "ms-resource:loc.messages.ProxyConfig"
223+
"ProxyConfig": "ms-resource:loc.messages.ProxyConfig",
224+
"FailedToRefreshAzSession": "ms-resource:loc.messages.FailedToRefreshAzSession",
225+
"RefreshingAzSession": "ms-resource:loc.messages.RefreshingAzSession",
226+
"KeepingAzSessionActiveUnsupportedScheme": "ms-resource:loc.messages.KeepingAzSessionActiveUnsupportedScheme"
215227
}
216228
}

_generated/AzureCLIV2.versionmap.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Default|2.241.0
2-
Node20_229_2|2.241.1
1+
Default|2.241.2
2+
Node20_229_2|2.241.3

_generated/AzureCLIV2/Strings/resources.resjson/en-US/resources.resjson

+6-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
"loc.input.help.powerShellIgnoreLASTEXITCODE": "If this is false, the line `if ((Test-Path -LiteralPath variable:\\LASTEXITCODE)) { exit $LASTEXITCODE }` is appended to the end of your script. This will cause the last exit code from an external command to be propagated as the exit code of powershell. Otherwise the line is not appended to the end of your script.",
3232
"loc.input.label.visibleAzLogin": "az login output visibility",
3333
"loc.input.help.visibleAzLogin": "If this is set to true, az login command will output to the task. Setting it to false will suppress the az login output",
34+
"loc.input.label.keepAzSessionActive": "[Experimental] Keep Azure CLI session active",
35+
"loc.input.help.keepAzSessionActive": "When enabled, this task will continuously sign into Azure to avoid AADSTS700024 errors when requesting access tokens beyond the IdToken expiry date. Note that this feature is EXPERIMENTAL, may not work in all scenarios and you are using it without any guarantees. Valid only for service connections using the Workload Identity Federation authentication scheme.",
3436
"loc.messages.ScriptReturnCode": "Script exited with return code: %d",
3537
"loc.messages.ScriptFailed": "Script failed with error: %s",
3638
"loc.messages.ScriptFailedStdErr": "Script has output to stderr. Failing as failOnStdErr is set to true.",
@@ -49,5 +51,8 @@
4951
"loc.messages.GlobalCliConfigAgentVersionWarning": "For agent version < 2.115.0, only global Azure CLI configuration can be used",
5052
"loc.messages.UnacceptedScriptLocationValue": "%s is not a valid value for task input 'Script Location' (scriptLocation in YAML). Value can either be'inlineScript' or 'scriptPath'",
5153
"loc.messages.ExpiredServicePrincipalMessageWithLink": "Secret expired, update service connection at %s See https://aka.ms/azdo-rm-workload-identity-conversion to learn more about conversion to secret-less service connections.",
52-
"loc.messages.ProxyConfig": "az tool is configured to use %s as proxy server"
54+
"loc.messages.ProxyConfig": "az tool is configured to use %s as proxy server",
55+
"loc.messages.FailedToRefreshAzSession": "The following error occurred while trying to refresh az-cli session: %s",
56+
"loc.messages.RefreshingAzSession": "Attempting to refresh az-cli session...",
57+
"loc.messages.KeepingAzSessionActiveUnsupportedScheme": "The 'keepAzSessionActive' input might be used only for workload identity federation ARM service connection. The referenced service endpoint auth scheme was unexpected: %s. Change the scheme or remove 'keepAzSessionActive' input."
5358
}

_generated/AzureCLIV2/azureclitask.ts

+34-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getHandlerFromToken, WebApi } from "azure-devops-node-api";
88
import { ITaskApi } from "azure-devops-node-api/TaskApi";
99

1010
const FAIL_ON_STDERR: string = "FAIL_ON_STDERR";
11+
const AZ_SESSION_REFRESH_INTERVAL_MS: number = 480000; // 8 minutes, 2 minutes before IdToken expiry date
1112

1213
export class azureclitask {
1314

@@ -41,8 +42,23 @@ export class azureclitask {
4142
this.setConfigDirectory();
4243
this.setAzureCloudBasedOnServiceEndpoint();
4344
var connectedService: string = tl.getInput("connectedServiceNameARM", true);
45+
const authorizationScheme = tl.getEndpointAuthorizationScheme(connectedService, true).toLowerCase();
46+
4447
await this.loginAzureRM(connectedService);
4548

49+
var keepAzSessionActive: boolean = tl.getBoolInput('keepAzSessionActive', false);
50+
var stopRefreshingSession: () => void = () => {};
51+
if (keepAzSessionActive) {
52+
// This is a tactical workaround to keep the session active for the duration of the task to avoid AADSTS700024 errors.
53+
// This is a temporary solution until the az cli provides a way to refresh the session.
54+
if (authorizationScheme !== 'workloadidentityfederation') {
55+
const errorMessage = tl.loc('KeepingAzSessionActiveUnsupportedScheme', authorizationScheme);
56+
tl.error(errorMessage);
57+
throw errorMessage;
58+
}
59+
stopRefreshingSession = this.keepRefreshingAzSession(connectedService);
60+
}
61+
4662
let errLinesCount: number = 0;
4763
let aggregatedErrorLines: string[] = [];
4864
tool.on('errline', (errorLine: string) => {
@@ -52,8 +68,7 @@ export class azureclitask {
5268
errLinesCount++;
5369
});
5470

55-
var addSpnToEnvironment: boolean = tl.getBoolInput('addSpnToEnvironment', false);
56-
var authorizationScheme = tl.getEndpointAuthorizationScheme(connectedService, true).toLowerCase();
71+
const addSpnToEnvironment: boolean = tl.getBoolInput('addSpnToEnvironment', false);
5772
if (!!addSpnToEnvironment && authorizationScheme == 'serviceprincipal') {
5873
exitCode = await tool.exec({
5974
failOnStdErr: false,
@@ -92,6 +107,10 @@ export class azureclitask {
92107
}
93108
}
94109
finally {
110+
if (keepAzSessionActive) {
111+
stopRefreshingSession();
112+
}
113+
95114
if (scriptType) {
96115
await scriptType.cleanUp();
97116
}
@@ -276,6 +295,19 @@ export class azureclitask {
276295

277296
return response.oidcToken;
278297
}
298+
299+
private static keepRefreshingAzSession(connectedService: string): () => void {
300+
const intervalId = setInterval(async () => {
301+
try {
302+
tl.debug(tl.loc('RefreshingAzSession'));
303+
await this.loginAzureRM(connectedService);
304+
} catch (error) {
305+
tl.warning(tl.loc('FailedToRefreshAzSession', error));
306+
}
307+
}, AZ_SESSION_REFRESH_INTERVAL_MS);
308+
309+
return () => clearInterval(intervalId);
310+
}
279311
}
280312

281313
tl.setResourcePath(path.join(__dirname, "task.json"));

_generated/AzureCLIV2/task.json

+16-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"version": {
2121
"Major": 2,
2222
"Minor": 241,
23-
"Patch": 0
23+
"Patch": 2
2424
},
2525
"minimumAgentVersion": "2.0.0",
2626
"instanceNameFormat": "Azure CLI $(scriptPath)",
@@ -180,6 +180,15 @@
180180
"required": false,
181181
"helpMarkDown": "If this is set to true, az login command will output to the task. Setting it to false will suppress the az login output",
182182
"groupName": "advanced"
183+
},
184+
{
185+
"name": "keepAzSessionActive",
186+
"type": "boolean",
187+
"label": "[Experimental] Keep Azure CLI session active",
188+
"defaultValue": "false",
189+
"required": false,
190+
"helpMarkDown": "When enabled, this task will continuously sign into Azure to avoid AADSTS700024 errors when requesting access tokens beyond the IdToken expiry date. Note that this feature is EXPERIMENTAL, may not work in all scenarios and you are using it without any guarantees. Valid only for service connections using the Workload Identity Federation authentication scheme.",
191+
"groupName": "advanced"
183192
}
184193
],
185194
"execution": {
@@ -211,10 +220,13 @@
211220
"GlobalCliConfigAgentVersionWarning": "For agent version < 2.115.0, only global Azure CLI configuration can be used",
212221
"UnacceptedScriptLocationValue": "%s is not a valid value for task input 'Script Location' (scriptLocation in YAML). Value can either be'inlineScript' or 'scriptPath'",
213222
"ExpiredServicePrincipalMessageWithLink": "Secret expired, update service connection at\u00A0%s See\u00A0https://aka.ms/azdo-rm-workload-identity-conversion to learn more about conversion to secret-less service connections.",
214-
"ProxyConfig": "az tool is configured to use %s as proxy server"
223+
"ProxyConfig": "az tool is configured to use %s as proxy server",
224+
"FailedToRefreshAzSession": "The following error occurred while trying to refresh az-cli session: %s",
225+
"RefreshingAzSession": "Attempting to refresh az-cli session...",
226+
"KeepingAzSessionActiveUnsupportedScheme": "The 'keepAzSessionActive' input might be used only for workload identity federation ARM service connection. The referenced service endpoint auth scheme was unexpected: %s. Change the scheme or remove 'keepAzSessionActive' input."
215227
},
216228
"_buildConfigMapping": {
217-
"Default": "2.241.0",
218-
"Node20_229_2": "2.241.1"
229+
"Default": "2.241.2",
230+
"Node20_229_2": "2.241.3"
219231
}
220232
}

_generated/AzureCLIV2/task.loc.json

+16-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"version": {
2121
"Major": 2,
2222
"Minor": 241,
23-
"Patch": 0
23+
"Patch": 2
2424
},
2525
"minimumAgentVersion": "2.0.0",
2626
"instanceNameFormat": "ms-resource:loc.instanceNameFormat",
@@ -180,6 +180,15 @@
180180
"required": false,
181181
"helpMarkDown": "ms-resource:loc.input.help.visibleAzLogin",
182182
"groupName": "advanced"
183+
},
184+
{
185+
"name": "keepAzSessionActive",
186+
"type": "boolean",
187+
"label": "ms-resource:loc.input.label.keepAzSessionActive",
188+
"defaultValue": "false",
189+
"required": false,
190+
"helpMarkDown": "ms-resource:loc.input.help.keepAzSessionActive",
191+
"groupName": "advanced"
183192
}
184193
],
185194
"execution": {
@@ -211,10 +220,13 @@
211220
"GlobalCliConfigAgentVersionWarning": "ms-resource:loc.messages.GlobalCliConfigAgentVersionWarning",
212221
"UnacceptedScriptLocationValue": "ms-resource:loc.messages.UnacceptedScriptLocationValue",
213222
"ExpiredServicePrincipalMessageWithLink": "ms-resource:loc.messages.ExpiredServicePrincipalMessageWithLink",
214-
"ProxyConfig": "ms-resource:loc.messages.ProxyConfig"
223+
"ProxyConfig": "ms-resource:loc.messages.ProxyConfig",
224+
"FailedToRefreshAzSession": "ms-resource:loc.messages.FailedToRefreshAzSession",
225+
"RefreshingAzSession": "ms-resource:loc.messages.RefreshingAzSession",
226+
"KeepingAzSessionActiveUnsupportedScheme": "ms-resource:loc.messages.KeepingAzSessionActiveUnsupportedScheme"
215227
},
216228
"_buildConfigMapping": {
217-
"Default": "2.241.0",
218-
"Node20_229_2": "2.241.1"
229+
"Default": "2.241.2",
230+
"Node20_229_2": "2.241.3"
219231
}
220232
}

0 commit comments

Comments
 (0)