Skip to content

Commit 14f4469

Browse files
billwertg2vinaychristothes
authored
Identity fix powershell bugs (#41266)
* fix ordering of arguments for Windows * Pass through Out-String to strip control codes (color, etc) * Change call to Get-AzAccessToken to parse secure string and ignore warnings * fix command building on Linux * add a live test * Redo how we send the command to powershell, include a version check for older versions of Az.Accounts. * checkstyle * Update ci.yml * roll back ci.yml change * make appconfiguration use this version for testing * Update sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java Co-authored-by: Christopher Scott <[email protected]> * roll back test * update changelog --------- Co-authored-by: Vinay Gera <[email protected]> Co-authored-by: Christopher Scott <[email protected]>
1 parent d0b03d2 commit 14f4469

File tree

5 files changed

+61
-36
lines changed

5 files changed

+61
-36
lines changed

eng/versioning/version_client.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ io.clientcore:http-stress;1.0.0-beta.1;1.0.0-beta.1
482482
# note: The unreleased dependencies will not be manipulated with the automatic PR creation code.
483483
# In the pom, the version update tag after the version should name the unreleased package and the dependency version:
484484
# <!-- {x-version-update;unreleased_com.azure:azure-core;dependency} -->
485+
unreleased_com.azure:azure-identity;1.14.0-beta.1
485486

486487
# Released Beta dependencies: Copy the entry from above, prepend "beta_", remove the current
487488
# version and set the version to the released beta. Released beta dependencies are only valid

sdk/appconfiguration/azure-data-appconfiguration/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
<dependency>
116116
<groupId>com.azure</groupId>
117117
<artifactId>azure-identity</artifactId>
118-
<version>1.13.1</version> <!-- {x-version-update;com.azure:azure-identity;dependency} -->
118+
<version>1.14.0-beta.1</version> <!-- {x-version-update;unreleased_com.azure:azure-identity;dependency} -->
119119
<scope>test</scope>
120120
</dependency>
121121
</dependencies>

sdk/identity/azure-identity/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
### Breaking Changes
88

99
### Bugs Fixed
10+
- Fixed bugs in `AzurePowerShellCredential` - Fixed break on Windows related to ordering of parameters, and fixed [#41234](https://github.com/Azure/azure-sdk-for-java/issues/41234)
1011

1112
### Other Changes
1213

sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.io.IOException;
4646
import java.net.HttpURLConnection;
4747
import java.net.MalformedURLException;
48+
4849
import java.net.Proxy;
4950
import java.net.Proxy.Type;
5051
import java.net.URI;
@@ -500,45 +501,63 @@ private Mono<AccessToken> getAccessTokenFromPowerShell(TokenRequestContext reque
500501
throw LOGGER.logExceptionAsError(ex);
501502
}
502503
return Mono.defer(() -> {
503-
String azAccountsCommand = "Import-Module Az.Accounts -MinimumVersion 2.2.0 -PassThru";
504-
return powershellManager.runCommand(azAccountsCommand).flatMap(output -> {
505-
if (output.contains("The specified module 'Az.Accounts' with version '2.2.0' was not loaded "
506-
+ "because no valid module file")) {
504+
String sep = System.lineSeparator();
505+
506+
String command = "$ErrorActionPreference = 'Stop'" + sep
507+
+ "[version]$minimumVersion = '2.2.0'" + sep
508+
+ "" + sep
509+
+ "$m = Import-Module Az.Accounts -MinimumVersion $minimumVersion -PassThru -ErrorAction SilentlyContinue" + sep
510+
+ "" + sep
511+
+ "if (! $m) {" + sep
512+
+ " Write-Output 'VersionTooOld'" + sep
513+
+ " exit" + sep
514+
+ "}" + sep
515+
+ "" + sep
516+
+ "$useSecureString = $m.Version -ge [version]'2.17.0'" + sep
517+
+ "" + sep
518+
+ "$params = @{" + sep
519+
+ " 'WarningAction'='Ignore'" + sep
520+
+ " 'ResourceUrl'='" + scope + "'" + sep
521+
+ "}" + sep
522+
+ "" + sep
523+
+ "if ($useSecureString) {" + sep
524+
+ " $params['AsSecureString'] = $true" + sep
525+
+ "}" + sep
526+
+ "" + sep
527+
+ "$token = Get-AzAccessToken @params" + sep
528+
+ "$customToken = New-Object -TypeName psobject" + sep
529+
+ "" + sep
530+
+ "$customToken | Add-Member -MemberType NoteProperty -Name Token -Value ($useSecureString -eq $true ? (ConvertFrom-SecureString -AsPlainText $token.Token) : $token.Token)" + sep
531+
+ "$customToken | Add-Member -MemberType NoteProperty -Name ExpiresOn -Value $token.ExpiresOn" + sep
532+
+ "" + sep
533+
+ "return $customToken | ConvertTo-Json";
534+
return powershellManager.runCommand(command).flatMap(output -> {
535+
if (output.contains("VersionTooOld")) {
507536
return Mono.error(LoggingUtil.logCredentialUnavailableException(LOGGER, options,
508537
new CredentialUnavailableException("Az.Account module with version >= 2.2.0 is not installed. "
509-
+ "It needs to be installed to use Azure PowerShell "
510-
+ "Credential.")));
538+
+ "It needs to be installed to use Azure PowerShell "
539+
+ "Credential.")));
511540
}
512541

513-
LOGGER.verbose("Az.accounts module was found installed.");
514-
String command = "Get-AzAccessToken -ResourceUrl '"
515-
+ scope
516-
+ "' | ConvertTo-Json";
517-
LOGGER.verbose("Azure Powershell Authentication => Executing the command `{}` in Azure "
518-
+ "Powershell to retrieve the Access Token.", command);
542+
if (output.contains("Run Connect-AzAccount to login")) {
543+
return Mono.error(LoggingUtil.logCredentialUnavailableException(LOGGER, options,
544+
new CredentialUnavailableException(
545+
"Run Connect-AzAccount to login to Azure account in PowerShell.")));
546+
}
519547

520-
return powershellManager.runCommand(command).flatMap(out -> {
521-
if (out.contains("Run Connect-AzAccount to login")) {
522-
return Mono.error(LoggingUtil.logCredentialUnavailableException(LOGGER, options,
523-
new CredentialUnavailableException(
524-
"Run Connect-AzAccount to login to Azure account in PowerShell.")));
525-
}
526548

527-
try {
528-
LOGGER.verbose("Azure Powershell Authentication => Attempting to deserialize the "
529-
+ "received response from Azure Powershell.");
530-
Map<String, String> objectMap = SERIALIZER_ADAPTER.deserialize(out, Map.class,
531-
SerializerEncoding.JSON);
532-
String accessToken = objectMap.get("Token");
533-
String time = objectMap.get("ExpiresOn");
534-
OffsetDateTime expiresOn = OffsetDateTime.parse(time).withOffsetSameInstant(ZoneOffset.UTC);
535-
return Mono.just(new AccessToken(accessToken, expiresOn));
536-
} catch (IOException e) {
537-
return Mono.error(LoggingUtil.logCredentialUnavailableException(LOGGER, options,
538-
new CredentialUnavailableException(
539-
"Encountered error when deserializing response from Azure Power Shell.", e)));
540-
}
541-
});
549+
try {
550+
Map<String, String> objectMap = SERIALIZER_ADAPTER.deserialize(output, Map.class,
551+
SerializerEncoding.JSON);
552+
String accessToken = objectMap.get("Token");
553+
String time = objectMap.get("ExpiresOn");
554+
OffsetDateTime expiresOn = OffsetDateTime.parse(time).withOffsetSameInstant(ZoneOffset.UTC);
555+
return Mono.just(new AccessToken(accessToken, expiresOn));
556+
} catch (IOException e) {
557+
return Mono.error(LoggingUtil.logCredentialUnavailableException(LOGGER, options,
558+
new CredentialUnavailableException(
559+
"Encountered error when deserializing response from Azure Power Shell.", e)));
560+
}
542561
});
543562
});
544563
}

sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/PowershellManager.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.io.BufferedReader;
1212
import java.io.IOException;
1313
import java.io.InputStreamReader;
14+
import java.nio.charset.StandardCharsets;
1415
import java.util.concurrent.TimeUnit;
1516

1617
public class PowershellManager {
@@ -33,6 +34,7 @@ public Mono<String> runCommand(String input) {
3334
try {
3435
String[] command = getCommandLine(input);
3536

37+
3638
ProcessBuilder processBuilder = new ProcessBuilder(command);
3739
processBuilder.redirectErrorStream(true);
3840
Process process = processBuilder.start();
@@ -53,8 +55,10 @@ public Mono<String> runCommand(String input) {
5355
}
5456

5557
String[] getCommandLine(String input) {
58+
String base64Input = java.util.Base64.getEncoder().encodeToString(input.getBytes(StandardCharsets.UTF_16LE));
59+
5660
return Platform.isWindows()
57-
? new String[]{powershellPath, "-Command", "-NoProfile", input}
58-
: new String[]{"/bin/bash", "-c", String.format("%s -NoProfile -Command '%s'", powershellPath, input)};
61+
? new String[]{powershellPath, "-NoProfile", "-EncodedCommand", base64Input}
62+
: new String[]{"/bin/bash", "-c", String.format("%s -NoProfile -EncodedCommand '%s'", powershellPath, base64Input)};
5963
}
6064
}

0 commit comments

Comments
 (0)