Skip to content

Commit 6c8997c

Browse files
committed
Identity fix powershell bugs (#41266)
1 parent d9d46fd commit 6c8997c

File tree

3 files changed

+67
-37
lines changed

3 files changed

+67
-37
lines changed

sdk/identity/azure-identity/CHANGELOG.md

+9-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## 1.13.2 (2024-08-02)
44

5+
### Bugs Fixed
6+
- 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) (previously shipped in beta)
7+
58
### Other Changes
69

710
#### Dependency Updates
@@ -10,11 +13,15 @@
1013
- Upgraded `azure-core-http-netty` from `1.15.2` to version `1.15.3`.
1114
- Upgraded `azure-json` from `1.1.0` to version `1.2.0`.
1215

16+
## 1.14.0-beta.1 (2024-07-24)
17+
18+
### Bugs Fixed
19+
- 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)
20+
1321
## 1.13.1 (2024-07-16)
1422

1523
### Features Added
16-
- Added support in `EnvironmentCredential` (and thus `DefaultAzureCredential` when it chooses `EnvironmentCredential`) for using subject name / issuer authentication with client certificates by setting `AZURE_CLIENT_SEND_CERTIFICATE_CHAIN` to `1` or `true`. [#40013](https://github.com/Azure/azure-sdk-for-java/issues/40013)
17-
24+
- Added support in `EnvironmentCredential` (and thus `DefaultAzureCredential` when it chooses `EnvironmentCredential`) for using subject name / issuer authentication with client certificates by setting `AZURE_CLIENT_SEND_CERTIFICATE_CHAIN` to `1` or `true`. [#40013](https://github.com/Azure/azure-sdk-for-java/issues/40013)
1825
### Bugs Fixed
1926
- Fixed certificate type detection, which fixes using a PFX certificate without a password. [#37210](https://github.com/Azure/azure-sdk-for-java/issues/37210)
2027
- Fix `PowershellCredential` issue when user had a profile [#41030](https://github.com/Azure/azure-sdk-for-java/pull/41030)

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

+52-33
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import java.io.IOException;
4545
import java.net.HttpURLConnection;
4646
import java.net.MalformedURLException;
47+
4748
import java.net.Proxy;
4849
import java.net.Proxy.Type;
4950
import java.net.URI;
@@ -485,45 +486,63 @@ private Mono<AccessToken> getAccessTokenFromPowerShell(TokenRequestContext reque
485486
throw LOGGER.logExceptionAsError(ex);
486487
}
487488
return Mono.defer(() -> {
488-
String azAccountsCommand = "Import-Module Az.Accounts -MinimumVersion 2.2.0 -PassThru";
489-
return powershellManager.runCommand(azAccountsCommand).flatMap(output -> {
490-
if (output.contains("The specified module 'Az.Accounts' with version '2.2.0' was not loaded "
491-
+ "because no valid module file")) {
489+
String sep = System.lineSeparator();
490+
491+
String command = "$ErrorActionPreference = 'Stop'" + sep
492+
+ "[version]$minimumVersion = '2.2.0'" + sep
493+
+ "" + sep
494+
+ "$m = Import-Module Az.Accounts -MinimumVersion $minimumVersion -PassThru -ErrorAction SilentlyContinue" + sep
495+
+ "" + sep
496+
+ "if (! $m) {" + sep
497+
+ " Write-Output 'VersionTooOld'" + sep
498+
+ " exit" + sep
499+
+ "}" + sep
500+
+ "" + sep
501+
+ "$useSecureString = $m.Version -ge [version]'2.17.0'" + sep
502+
+ "" + sep
503+
+ "$params = @{" + sep
504+
+ " 'WarningAction'='Ignore'" + sep
505+
+ " 'ResourceUrl'='" + scope + "'" + sep
506+
+ "}" + sep
507+
+ "" + sep
508+
+ "if ($useSecureString) {" + sep
509+
+ " $params['AsSecureString'] = $true" + sep
510+
+ "}" + sep
511+
+ "" + sep
512+
+ "$token = Get-AzAccessToken @params" + sep
513+
+ "$customToken = New-Object -TypeName psobject" + sep
514+
+ "" + sep
515+
+ "$customToken | Add-Member -MemberType NoteProperty -Name Token -Value ($useSecureString -eq $true ? (ConvertFrom-SecureString -AsPlainText $token.Token) : $token.Token)" + sep
516+
+ "$customToken | Add-Member -MemberType NoteProperty -Name ExpiresOn -Value $token.ExpiresOn" + sep
517+
+ "" + sep
518+
+ "return $customToken | ConvertTo-Json";
519+
return powershellManager.runCommand(command).flatMap(output -> {
520+
if (output.contains("VersionTooOld")) {
492521
return Mono.error(LoggingUtil.logCredentialUnavailableException(LOGGER, options,
493522
new CredentialUnavailableException("Az.Account module with version >= 2.2.0 is not installed. "
494-
+ "It needs to be installed to use Azure PowerShell "
495-
+ "Credential.")));
523+
+ "It needs to be installed to use Azure PowerShell "
524+
+ "Credential.")));
496525
}
497526

498-
LOGGER.verbose("Az.accounts module was found installed.");
499-
String command = "Get-AzAccessToken -ResourceUrl '"
500-
+ scope
501-
+ "' | ConvertTo-Json";
502-
LOGGER.verbose("Azure Powershell Authentication => Executing the command `{}` in Azure "
503-
+ "Powershell to retrieve the Access Token.", command);
527+
if (output.contains("Run Connect-AzAccount to login")) {
528+
return Mono.error(LoggingUtil.logCredentialUnavailableException(LOGGER, options,
529+
new CredentialUnavailableException(
530+
"Run Connect-AzAccount to login to Azure account in PowerShell.")));
531+
}
504532

505-
return powershellManager.runCommand(command).flatMap(out -> {
506-
if (out.contains("Run Connect-AzAccount to login")) {
507-
return Mono.error(LoggingUtil.logCredentialUnavailableException(LOGGER, options,
508-
new CredentialUnavailableException(
509-
"Run Connect-AzAccount to login to Azure account in PowerShell.")));
510-
}
511533

512-
try {
513-
LOGGER.verbose("Azure Powershell Authentication => Attempting to deserialize the "
514-
+ "received response from Azure Powershell.");
515-
Map<String, String> objectMap = SERIALIZER_ADAPTER.deserialize(out, Map.class,
516-
SerializerEncoding.JSON);
517-
String accessToken = objectMap.get("Token");
518-
String time = objectMap.get("ExpiresOn");
519-
OffsetDateTime expiresOn = OffsetDateTime.parse(time).withOffsetSameInstant(ZoneOffset.UTC);
520-
return Mono.just(new AccessToken(accessToken, expiresOn));
521-
} catch (IOException e) {
522-
return Mono.error(LoggingUtil.logCredentialUnavailableException(LOGGER, options,
523-
new CredentialUnavailableException(
524-
"Encountered error when deserializing response from Azure Power Shell.", e)));
525-
}
526-
});
534+
try {
535+
Map<String, String> objectMap = SERIALIZER_ADAPTER.deserialize(output, Map.class,
536+
SerializerEncoding.JSON);
537+
String accessToken = objectMap.get("Token");
538+
String time = objectMap.get("ExpiresOn");
539+
OffsetDateTime expiresOn = OffsetDateTime.parse(time).withOffsetSameInstant(ZoneOffset.UTC);
540+
return Mono.just(new AccessToken(accessToken, expiresOn));
541+
} catch (IOException e) {
542+
return Mono.error(LoggingUtil.logCredentialUnavailableException(LOGGER, options,
543+
new CredentialUnavailableException(
544+
"Encountered error when deserializing response from Azure Power Shell.", e)));
545+
}
527546
});
528547
});
529548
}

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

+6-2
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)