diff --git a/src/KeyVault/KeyVault.Test/PesterTests/Secret.Tests.ps1 b/src/KeyVault/KeyVault.Test/PesterTests/Secret.Tests.ps1 new file mode 100644 index 000000000000..a10b643046f1 --- /dev/null +++ b/src/KeyVault/KeyVault.Test/PesterTests/Secret.Tests.ps1 @@ -0,0 +1,19 @@ +$vaultName = 'yemingkv23' + + +. ../Scripts/Common.ps1 +$secretName = Get-SecretName +$secretText = 'dummy text' +$secretTextV2 = 'dummy text 2' +Set-AzKeyVaultSecret -VaultName $vaultName -Name $secretName -SecretValue (ConvertTo-SecureString $secretText -AsPlainText -Force) +Set-AzKeyVaultSecret -VaultName $vaultName -Name $secretName -SecretValue (ConvertTo-SecureString $secretTextV2 -AsPlainText -Force) + +Describe "Get secret" { + It "should write secrets in plain text if -AsPlainText" { + Get-AzKeyVaultSecret -VaultName $vaultName -Name $secretName -AsPlainText | Should -BeExactly $secretTextV2 + + $versions = Get-AzKeyVaultSecret -VaultName $vaultName -Name $secretName -IncludeVersions + Get-AzKeyVaultSecret -VaultName $vaultName -Name $secretName -Version $versions[0].Version -AsPlainText | Should -BeExactly $secretTextV2 + Get-AzKeyVaultSecret -VaultName $vaultName -Name $secretName -Version $versions[1].Version -AsPlainText | Should -BeExactly $secretText + } +} \ No newline at end of file diff --git a/src/KeyVault/KeyVault/ChangeLog.md b/src/KeyVault/KeyVault/ChangeLog.md index 077c604c9722..d6a5b17e5c24 100644 --- a/src/KeyVault/KeyVault/ChangeLog.md +++ b/src/KeyVault/KeyVault/ChangeLog.md @@ -18,6 +18,7 @@ - Additional information about change #1 --> ## Upcoming Release +* Added a new parameter `-AsPlainText` to `Get-AzKeyVaultSecret` to directly return the secret in plain text * Supported selective restore a key from a managed HSM full backup [#13526] * Added missing return objects of `Get-Secret` in SecretManagement module diff --git a/src/KeyVault/KeyVault/Commands/GetAzureKeyVaultSecret.cs b/src/KeyVault/KeyVault/Commands/GetAzureKeyVaultSecret.cs index 70c8bf7d8615..56be95b160cf 100644 --- a/src/KeyVault/KeyVault/Commands/GetAzureKeyVaultSecret.cs +++ b/src/KeyVault/KeyVault/Commands/GetAzureKeyVaultSecret.cs @@ -20,11 +20,13 @@ using System.Collections.Generic; using System.Linq; using System.Management.Automation; +using System.Runtime.InteropServices; +using System.Security; namespace Microsoft.Azure.Commands.KeyVault { [Cmdlet("Get", ResourceManager.Common.AzureRMConstants.AzurePrefix + "KeyVaultSecret", DefaultParameterSetName = ByVaultNameParameterSet)] - [OutputType(typeof(PSKeyVaultSecretIdentityItem), typeof(PSKeyVaultSecret), typeof(PSDeletedKeyVaultSecretIdentityItem), typeof(PSDeletedKeyVaultSecret))] + [OutputType(typeof(PSKeyVaultSecretIdentityItem), typeof(PSKeyVaultSecret), typeof(PSDeletedKeyVaultSecretIdentityItem), typeof(PSDeletedKeyVaultSecret), typeof(string))] public class GetAzureKeyVaultSecret : KeyVaultCmdletBase { #region Parameter Set Names @@ -190,6 +192,13 @@ public class GetAzureKeyVaultSecret : KeyVaultCmdletBase HelpMessage = "Specifies whether to show the previously deleted secrets in the output.")] public SwitchParameter InRemovedState { get; set; } + [Parameter(Mandatory = false, ParameterSetName = BySecretNameParameterSet, HelpMessage = "When set, the cmdlet will convert secret in secure string to the decrypted plaintext string as output.")] + [Parameter(Mandatory = false, ParameterSetName = ByVaultNameParameterSet)] + [Parameter(Mandatory = false, ParameterSetName = InputObjectBySecretNameParameterSet)] + [Parameter(Mandatory = false, ParameterSetName = InputObjectByVaultNameParameterSet)] + [Parameter(Mandatory = false, ParameterSetName = ResourceIdBySecretNameParameterSet)] + [Parameter(Mandatory = false, ParameterSetName = ResourceIdByVaultNameParameterSet)] + public SwitchParameter AsPlainText { get; set; } #endregion public override void ExecuteCmdlet() @@ -209,7 +218,7 @@ public override void ExecuteCmdlet() if (!string.IsNullOrEmpty(Version)) { secret = DataServiceClient.GetSecret(VaultName, Name, Version); - WriteObject(secret); + WriteSecret(secret); } else if (IncludeVersions) { @@ -241,7 +250,7 @@ public override void ExecuteCmdlet() else { secret = DataServiceClient.GetSecret(VaultName, Name, string.Empty); - WriteObject(secret); + WriteSecret(secret); } } } @@ -259,7 +268,7 @@ private void GetAndWriteSecrets(string vaultName, string name) => { VaultName = vaultName, NextLink = null - }, + }, (options) => KVSubResourceWildcardFilter(name, DataServiceClient.GetSecrets(options))); private void GetAndWriteSecretVersions(string vaultName, string name, string currentSecretVersion) => @@ -268,7 +277,32 @@ private void GetAndWriteSecretVersions(string vaultName, string name, string cur VaultName = vaultName, Name = name, NextLink = null - }, + }, (options) => DataServiceClient.GetSecretVersions(options).Where(s => s.Version != currentSecretVersion)); + + private void WriteSecret(PSKeyVaultSecret secret) + { + if (AsPlainText) + { + WriteObject(ConvertFromSecureString(secret.SecretValue)); + } + else + { + WriteObject(secret); + } + } + + private string ConvertFromSecureString(SecureString secretValue) + { + var ssPtr = Marshal.SecureStringToBSTR(secretValue); + try + { + return Marshal.PtrToStringBSTR(ssPtr); + } + finally + { + Marshal.ZeroFreeBSTR(ssPtr); + } + } } } diff --git a/src/KeyVault/KeyVault/Commands/RemoveAzureKeyVaultSecret.cs b/src/KeyVault/KeyVault/Commands/RemoveAzureKeyVaultSecret.cs index bc23dc9f3c0b..cb99aa5cfd46 100644 --- a/src/KeyVault/KeyVault/Commands/RemoveAzureKeyVaultSecret.cs +++ b/src/KeyVault/KeyVault/Commands/RemoveAzureKeyVaultSecret.cs @@ -22,9 +22,6 @@ namespace Microsoft.Azure.Commands.KeyVault { - [GenericBreakingChange("If you have soft-delete protection enabled on this key vault, this secret will be moved to the soft deleted state. " + - "You will not be able to create a secret with the same name within this key vault until the secret has been purged from the soft-deleted state. Please see the following documentation for additional guidance. " + - "https://docs.microsoft.com/en-us/azure/key-vault/general/soft-delete-overview")] [Cmdlet("Remove", ResourceManager.Common.AzureRMConstants.AzurePrefix + "KeyVaultSecret",SupportsShouldProcess = true,DefaultParameterSetName = ByVaultNameParameterSet)] [OutputType(typeof(PSDeletedKeyVaultSecret))] public class RemoveAzureKeyVaultSecret : KeyVaultCmdletBase diff --git a/src/KeyVault/KeyVault/help/Backup-AzKeyVault.md b/src/KeyVault/KeyVault/help/Backup-AzKeyVault.md index 7a98d919e350..00e54bce589c 100644 --- a/src/KeyVault/KeyVault/help/Backup-AzKeyVault.md +++ b/src/KeyVault/KeyVault/help/Backup-AzKeyVault.md @@ -96,7 +96,7 @@ Aliases: Required: True Position: Named Default value: None -Accept pipeline input: False +Accept pipeline input: True (ByValue) Accept wildcard characters: False ``` diff --git a/src/KeyVault/KeyVault/help/Get-AzKeyVaultCertificate.md b/src/KeyVault/KeyVault/help/Get-AzKeyVaultCertificate.md index b3c913af3f53..671a363caf85 100644 --- a/src/KeyVault/KeyVault/help/Get-AzKeyVaultCertificate.md +++ b/src/KeyVault/KeyVault/help/Get-AzKeyVaultCertificate.md @@ -108,15 +108,8 @@ This command gets the certificate named TestCert01 from the key vault named Cont ```powershell $cert = Get-AzKeyVaultCertificate -VaultName "ContosoKV01" -Name "TestCert01" -$secret = Get-AzKeyVaultSecret -VaultName $vaultName -Name $cert.Name -$secretValueText = ''; -$ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secret.SecretValue) -try { - $secretValueText = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr) -} finally { - [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr) -} -$secretByte = [Convert]::FromBase64String($secretValueText) +$secret = Get-AzKeyVaultSecret -VaultName $vaultName -Name $cert.Name -AsPlainText +$secretByte = [Convert]::FromBase64String($secret) $x509Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($secretByte, "", "Exportable,PersistKeySet") $type = [System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx $pfxFileByte = $x509Cert.Export($type, $password) diff --git a/src/KeyVault/KeyVault/help/Get-AzKeyVaultSecret.md b/src/KeyVault/KeyVault/help/Get-AzKeyVaultSecret.md index 79e4844aaa2e..ce497b6d4de1 100644 --- a/src/KeyVault/KeyVault/help/Get-AzKeyVaultSecret.md +++ b/src/KeyVault/KeyVault/help/Get-AzKeyVaultSecret.md @@ -15,13 +15,13 @@ Gets the secrets in a key vault. ### ByVaultName (Default) ``` -Get-AzKeyVaultSecret [-VaultName] [[-Name] ] [-InRemovedState] +Get-AzKeyVaultSecret [-VaultName] [[-Name] ] [-InRemovedState] [-AsPlainText] [-DefaultProfile ] [] ``` ### BySecretName ``` -Get-AzKeyVaultSecret [-VaultName] [-Name] [-Version] +Get-AzKeyVaultSecret [-VaultName] [-Name] [-Version] [-AsPlainText] [-DefaultProfile ] [] ``` @@ -33,13 +33,13 @@ Get-AzKeyVaultSecret [-VaultName] [-Name] [-IncludeVersions] ### ByInputObjectVaultName ``` -Get-AzKeyVaultSecret [-InputObject] [[-Name] ] [-InRemovedState] +Get-AzKeyVaultSecret [-InputObject] [[-Name] ] [-InRemovedState] [-AsPlainText] [-DefaultProfile ] [] ``` ### ByInputObjectSecretName ``` -Get-AzKeyVaultSecret [-InputObject] [-Name] [-Version] +Get-AzKeyVaultSecret [-InputObject] [-Name] [-Version] [-AsPlainText] [-DefaultProfile ] [] ``` @@ -51,13 +51,13 @@ Get-AzKeyVaultSecret [-InputObject] [-Name] [-IncludeVersi ### ByResourceIdVaultName ``` -Get-AzKeyVaultSecret [-ResourceId] [[-Name] ] [-InRemovedState] +Get-AzKeyVaultSecret [-ResourceId] [[-Name] ] [-InRemovedState] [-AsPlainText] [-DefaultProfile ] [] ``` ### ByResourceIdSecretName ``` -Get-AzKeyVaultSecret [-ResourceId] [-Name] [-Version] +Get-AzKeyVaultSecret [-ResourceId] [-Name] [-Version] [-AsPlainText] [-DefaultProfile ] [] ``` @@ -175,27 +175,10 @@ This command gets a specific version of the secret named secret1 in the key vaul ### Example 5: Get the plain text value of the current version of a specific secret ```powershell -PS C:\> $secret = Get-AzKeyVaultSecret -VaultName 'Contoso' -Name 'ITSecret' - -# Method 1: requires PowerShell >= 7.0 -PS C:\> $secretInPlainText = $secret.SecretValue | ConvertFrom-SecureString -AsPlainText - -# Method 2: works on older PowerShell versions -PS C:\> $secretValueText = ''; -PS C:\> $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secret.SecretValue) -PS C:\> try { - $secretInPlainText = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr) -} finally { - [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr) -} - -# Method 3: works in ConstrainedLanguage mode -$secretInPlainText = [pscredential]::new("DoesntMatter", $secret.SecretValue).GetNetworkCredential().Password +PS C:\> $secretText = Get-AzKeyVaultSecret -VaultName 'Contoso' -Name 'ITSecret' -AsPlainText ``` -These commands get the current version of a secret named ITSecret, and then displays the plain text value of that secret. - -(Note: use method 3 if you are working in PowerShell [ConstrainedLanguage mode](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_language_modes?view=powershell-7.1#constrained-language-constrained-language), for example, on secure/privileged access workstations.) +The cmdlet returns the secret as a string when `-AsPlainText` is applied. ### Example 6: Get all the secrets that have been deleted but not purged for this key vault. ```powershell @@ -285,6 +268,21 @@ This command gets the current versions of all secrets in the key vault named Con ## PARAMETERS +### -AsPlainText +When set, the cmdlet will convert secret in secure string to the decrypted plaintext string as output. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: ByVaultName, BySecretName, ByInputObjectVaultName, ByInputObjectSecretName, ByResourceIdVaultName, ByResourceIdSecretName +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -DefaultProfile The credentials, account, tenant, and subscription used for communication with azure