Skip to content

Commit c70630c

Browse files
Afroz Mohammedafroz429
authored andcommitted
added sensitive data redaction for powershell host
1 parent 9451283 commit c70630c

File tree

6 files changed

+211
-3
lines changed

6 files changed

+211
-3
lines changed

generator/AWSPSGeneratorLib/FormatConfig/ConfigModel.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ internal HashSet<string> TypeInclusionSet
7171
}
7272

7373
/// <summary>
74-
/// Specific types to exlude from the emitted formats. This list will be
74+
/// Specific types to exclude from the emitted formats. This list will be
7575
/// automatically extended to include any types found in custom format files.
7676
/// </summary>
7777
[XmlArray]
@@ -245,16 +245,21 @@ public class ColumnConfig
245245
public HeaderAlignment HeaderAlignment { get; set; }
246246
[XmlAttribute]
247247
public int HeaderOrder { get; set; }
248+
[XmlIgnore]
249+
public bool IsSensitive { get; set; }
248250

249251
[XmlAttribute]
250252
public string ScriptBlock { get; set; }
251253
[XmlAttribute]
252254
public string PropertyName { get; set; }
253255

256+
257+
254258
public ColumnConfig()
255259
{
256260
ScriptBlock = null;
257261
PropertyName = null;
262+
IsSensitive = false;
258263

259264
HeaderLabel = null;
260265
HeaderWidth = 0;
@@ -268,6 +273,7 @@ public void Merge(ColumnConfig other)
268273
HeaderWidth = other.HeaderWidth != 0 ? other.HeaderWidth : this.HeaderWidth;
269274
HeaderAlignment = other.HeaderAlignment != HeaderAlignment.None ? other.HeaderAlignment : this.HeaderAlignment;
270275
HeaderOrder = other.HeaderOrder != 0 ? other.HeaderOrder : this.HeaderOrder;
276+
IsSensitive = other.IsSensitive || this.IsSensitive;
271277

272278
ScriptBlock = other.ScriptBlock ?? this.ScriptBlock;
273279
PropertyName = other.PropertyName ?? this.PropertyName;

generator/AWSPSGeneratorLib/Generators/FormatGenerator.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public class FormatGenerator : Generator
3232
private ConfigModelCollection ConfigCollection { get; set; }
3333

3434
private const string AwsToolsPrefix = "AWS.Tools.";
35+
36+
private const string SensitiveDataRedactionMessage = "*** sensitive data redacted from host ***";
3537
#endregion
3638

3739

@@ -175,7 +177,8 @@ private void GenerateView(ConfigModel config, XmlWriter writer, Type type)
175177
existingColumn.Merge(new ColumnConfig
176178
{
177179
HeaderLabel = name,
178-
PropertyName = name
180+
PropertyName = name,
181+
IsSensitive = property.IsSensitive()
179182
});
180183

181184
newConfig.Columns.Add(existingColumn);
@@ -322,7 +325,16 @@ private void GenerateView_NonReflective(ConfigModel config, XmlWriter writer, Ty
322325
{
323326
writer.WriteStartElement(isTableView ? "TableColumnItem" : "ListItem");
324327
{
325-
if (!string.IsNullOrEmpty(column.PropertyName))
328+
if (column.IsSensitive)
329+
{
330+
if (!isTableView)
331+
writer.WriteElementString("Label", column.PropertyName);
332+
333+
string scriptBlockValue =
334+
$"if((Test-Path variable:AWSPowerShell_Show_Sensitive_Data) -and $false.Equals((Get-Variable AWSPowerShell_Show_Sensitive_Data).Value)){{'{SensitiveDataRedactionMessage}'}} else{{$_.{column.PropertyName}}}";
335+
writer.WriteElementString("ScriptBlock", scriptBlockValue);
336+
}
337+
else if (!string.IsNullOrEmpty(column.PropertyName))
326338
writer.WriteElementString("PropertyName", column.PropertyName);
327339
else if (!string.IsNullOrEmpty(column.ScriptBlock))
328340
writer.WriteElementString("ScriptBlock", column.ScriptBlock);

generator/AWSPSGeneratorLib/Utils/Extensions.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Text;
55
using System.Xml.Serialization;
66
using System.IO;
7+
using System.Reflection;
78

89
namespace AWSPowerShellGenerator.Utils
910
{
@@ -45,5 +46,27 @@ public static string GetTypeFullCodeName(this Type t)
4546

4647
return typeName + "<" + args + ">";
4748
}
49+
50+
public static bool IsSensitive(this PropertyInfo propertyInfo)
51+
{
52+
dynamic awsPropertyAttribute = propertyInfo
53+
.GetCustomAttributes().SingleOrDefault(attribute => attribute.GetType().FullName == "Amazon.Runtime.Internal.AWSPropertyAttribute");
54+
55+
return awsPropertyAttribute != null && awsPropertyAttribute.Sensitive;
56+
}
57+
58+
/// <summary>
59+
/// Checks if the type contains any sensitive data by going recursively over all the internal properties
60+
/// </summary>
61+
public static bool ContainsSensitiveData(this Type type, HashSet<Type> visitedTypes = null)
62+
{
63+
visitedTypes ??= [];
64+
65+
if (!visitedTypes.Add(type))
66+
return false;
67+
68+
return type.GetProperties().Any(childProperty => IsSensitive(childProperty) || ContainsSensitiveData(childProperty.PropertyType, visitedTypes));
69+
}
70+
4871
}
4972
}

modules/AWSPowerShell/Common/CommonCmdlets.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,4 +526,80 @@ protected override void ProcessRecord()
526526
}
527527
}
528528
}
529+
530+
/// <summary>
531+
/// Controls the display of sensitive information in the PowerShell console output.
532+
/// When set to false (default), sensitive data is masked in the console display.
533+
/// When set to true, sensitive data is shown in plain text.
534+
/// Note: This setting only affects console display - stored variables retain the original unmasked data regardless of this setting.
535+
/// This cmdlet sets a shell variable AWSPowerShell_Show_Sensitive_Data using the scope.
536+
/// </summary>
537+
[Cmdlet("Set", "AWSSensitiveDataConfiguration")]
538+
[AWSCmdlet("Controls the display of sensitive information in the PowerShell console output. When set to true, sensitive data is shown in plain text in the console output.")]
539+
[OutputType("None")]
540+
public class SetAWSSensitiveDataConfigurationCmdlet : PSCmdlet
541+
{
542+
#region Parameter ShowSensitiveData
543+
544+
/// <summary>
545+
/// Controls whether sensitive data appears in PowerShell console output.
546+
/// When set to true, displays un-redacted sensitive data in the console.
547+
/// When set to false (default), automatically masks sensitive data in console output.
548+
/// </summary>
549+
[Parameter(ValueFromPipelineByPropertyName = true, Mandatory = true)]
550+
public bool ShowSensitiveData { get; set; }
551+
552+
#endregion
553+
554+
#region Parameter Scope
555+
/// <summary>
556+
/// <para>
557+
/// Sets the scope of the shell variable AWSPowerShell_Show_Sensitive_Data.
558+
/// For details about variables scopes, see https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes.
559+
/// </para>
560+
/// </summary>
561+
[Parameter(ValueFromPipelineByPropertyName = true)]
562+
public VariableScope Scope { get; set; }
563+
#endregion
564+
565+
protected override void ProcessRecord()
566+
{
567+
base.ProcessRecord();
568+
WriteVerbose($"Setting AWSShowSensitiveData to {ShowSensitiveData}");
569+
string scope = MyInvocation.BoundParameters.ContainsKey("Scope") ? Scope.ToString() + ":" : "";
570+
571+
this.SessionState.PSVariable.Set(scope+ SessionKeys.AWSShowSensitiveData, ShowSensitiveData);
572+
}
573+
}
574+
575+
/// <summary>
576+
/// Returns the current configuration value that controls how sensitive data is displayed in the PowerShell console.
577+
/// This cmdlet returns a Boolean value indicating whether sensitive data is shown or redacted in console output.
578+
/// </summary>
579+
[Cmdlet("Get", "AWSSensitiveDataConfiguration")]
580+
[AWSCmdlet("Gets the current configuration settings for sensitive data display in PowerShell output.")]
581+
[OutputType("PSObject")]
582+
public class GetAWSSensitiveDataConfigurationCmdlet : PSCmdlet
583+
{
584+
protected override void ProcessRecord()
585+
{
586+
base.ProcessRecord();
587+
var showSensitiveData = this.SessionState.PSVariable.Get(SessionKeys.AWSShowSensitiveData);
588+
var result = new PSObject();
589+
590+
// in v4 default ShowSensitiveData is true
591+
const bool defaultShowSensitiveData = true;
592+
593+
var noteProperty = new PSNoteProperty("ShowSensitiveData", defaultShowSensitiveData);
594+
595+
if (showSensitiveData != null)
596+
{
597+
noteProperty.Value = (bool)showSensitiveData.Value;
598+
599+
}
600+
601+
result.Properties.Add(noteProperty);
602+
WriteObject(result);
603+
}
604+
}
529605
}

modules/AWSPowerShell/Common/CredentialsArguments.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,7 @@ internal static class SessionKeys
799799
public const string AWSCredentialsVariableName = "StoredAWSCredentials";
800800
public const string AWSRegionVariableName = "StoredAWSRegion";
801801
public const string AWSCallHistoryName = "AWSHistory";
802+
public const string AWSShowSensitiveData = "AWSPowerShell_Show_Sensitive_Data";
802803
public const string AWSProxyVariableName = "AWSProxy";
803804
}
804805

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
BeforeAll {
2+
. (Join-Path (Join-Path (Get-Location) "Include") "TestIncludes.ps1")
3+
. (Join-Path (Join-Path (Get-Location) "Include") "TestHelper.ps1")
4+
. (Join-Path (Join-Path (Get-Location) "Include") "ServiceTestHelper.ps1")
5+
$helper = New-Object ServiceTestHelper
6+
$helper.BeforeAll()
7+
$script:region = 'us-west-2'
8+
$script:secretName = 'integrationtests-sensitive-data-redaction-' + [DateTime]::Now.ToFileTime()
9+
$script:secretValue = 'testvalue'
10+
$script:redactedValue = [Regex]::Escape('*** sensitive data redacted from host ***')
11+
$null = New-SECSecret -Name $script:secretName -SecretString $script:secretValue -Region $script:region }
12+
13+
AfterAll {
14+
$helper.AfterAll()
15+
$deletedSecret = Remove-SECSecret -SecretId $script:secretName -DeleteWithNoRecovery $true -Region $script:region -Force
16+
}
17+
18+
Describe -Tag "Smoke" "AWSSensitiveDataConfiguration Default" {
19+
20+
Context "Sensitive data redaction defaults" {
21+
BeforeAll {
22+
$secret = Get-SECSecretValue -SecretId $script:secretName -Region $script:region
23+
}
24+
It "Sensitive data not redacted when dereferenced" {
25+
$secret.SecretString | Should -BeExactly $script:secretValue
26+
}
27+
28+
It "Sensitive data not redacted in the host by default" {
29+
$secretInHost = ($secret | Out-String -Stream).Where({ $_ -like "SecretString*" })[0]
30+
$secretInHost | Should -MatchExactly $script:secretValue
31+
}
32+
33+
It "Null Sensitive data not redacted in the host" {
34+
$secretInHost = ($secret | Out-String -Stream).Where({ $_ -like "SecretBinary*" })[0]
35+
$secretInHost | Should -Not -MatchExactly $script:redactedValue
36+
}
37+
38+
It 'Get-AWSSensitiveDataConfiguration ShowSensitiveData default should be $true' {
39+
(Get-AWSSensitiveDataConfiguration).ShowSensitiveData | Should -BeExactly $true
40+
}
41+
}
42+
Context "Sensitive data redaction ShowSensitiveData" {
43+
BeforeAll {
44+
Set-AWSSensitiveDataConfiguration -ShowSensitiveData $true
45+
$secret = Get-SECSecretValue -SecretId $script:secretName -Region $script:region
46+
}
47+
48+
It "Sensitive data not redacted when dereferenced" {
49+
$secret.SecretString | Should -BeExactly $script:secretValue
50+
}
51+
52+
It "Sensitive data not redacted in the host" {
53+
$secretInHost = ($secret | Out-String -Stream).Where({ $_ -like "SecretString*" })[0]
54+
$secretInHost | Should -MatchExactly $script:secretValue
55+
}
56+
57+
It "Null Sensitive data not redacted in the host" {
58+
$secretInHost = ($secret | Out-String -Stream).Where({ $_ -like "SecretBinary*" })[0]
59+
$secretInHost | Should -Not -MatchExactly $script:redactedValue
60+
}
61+
62+
It 'Get-AWSSensitiveDataConfiguration ShowSensitiveData should be $true' {
63+
(Get-AWSSensitiveDataConfiguration).ShowSensitiveData | Should -BeExactly $true
64+
}
65+
}
66+
Context 'Sensitive data redaction ShowSensitiveData to $false' {
67+
BeforeAll {
68+
Set-AWSSensitiveDataConfiguration -ShowSensitiveData $false
69+
$secret = Get-SECSecretValue -SecretId $script:secretName -Region $script:region
70+
}
71+
72+
It "Sensitive data not redacted when dereferenced" {
73+
$secret.SecretString | Should -BeExactly $script:secretValue
74+
}
75+
76+
It "Sensitive data redacted in the host" {
77+
$secretInHost = ($secret | Out-String -Stream).Where({ $_ -like "SecretString*" })[0]
78+
$secretInHost | Should -MatchExactly $script:redactedValue
79+
}
80+
81+
It "Null Sensitive data redacted in the host" {
82+
$secretInHost = ($secret | Out-String -Stream).Where({ $_ -like "SecretBinary*" })[0]
83+
$secretInHost | Should -MatchExactly $script:redactedValue
84+
}
85+
86+
It 'Get-AWSSensitiveDataConfiguration ShowSensitiveData should be $false' {
87+
(Get-AWSSensitiveDataConfiguration).ShowSensitiveData | Should -BeExactly $false
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)