Skip to content

Add support for querying encryption status from the AzureDiskEncryptionForLinux extension #2849

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Sep 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
09bf816
Remove hardcoded default extension names
May 27, 2016
f489d96
Set ExtensionName property when getting extension status
May 28, 2016
3666987
Fix extension name assignment in Disable cmdlet
May 30, 2016
ec6424f
Use the Name property for extension type
May 30, 2016
ae53598
Merge branch 'release-1.5.0' into dev
Jun 2, 2016
b2cf886
Remove duplicate assignment
Jun 2, 2016
dfc53c8
Added ProgressMessage field for disk encryption status
Jun 2, 2016
2cc44ac
Display EncryptionProgress in cmdlet output
Jun 2, 2016
d7f17e4
Fix label name
Jun 2, 2016
6626475
Merge branch 'dev' of github.com:Azure/azure-powershell into dev
Jun 3, 2016
c079b3c
Merge branch 'dev' of github.com:Azure/azure-powershell into dev
Jun 21, 2016
de50276
Allow OS disk encryption on Linux
Jun 22, 2016
f91016f
If extension name is provided, use it when getting the status
Jun 22, 2016
f5451e9
Merge branch 'dev' of github.com:Azure/azure-powershell into dev
Jun 30, 2016
3be1bc1
Merge branch 'dev' of github.com:Azure/azure-powershell into dev
Aug 10, 2016
930cadb
Use QueryEncryptionStatus operation for Linux VMs
Aug 19, 2016
e825b29
Parse the encryption status from JSON in substatus field for Linux VMs
Aug 22, 2016
79f789a
Merge remote-tracking branch 'upstream/dev' into dev
Aug 28, 2016
6b0bf42
Use Unknown for encryption status for issue Azure/WALinuxAgent#388
Aug 29, 2016
d4d5774
Capitalize "Windows"
Aug 30, 2016
aaa84f3
Always use the default name for extension type parameter
Aug 30, 2016
d1b4c7b
Use default extension name for type in Disable cmdlet
Aug 30, 2016
7209864
Use default extension name when disabling on Linux VM
Aug 30, 2016
e7231d8
Merge remote-tracking branch 'upstream/dev' into dev
Aug 31, 2016
8c284b4
Do null-checks on context and context.SubsStatuses
Aug 31, 2016
727639c
Show message to user if no extension status is available
Aug 31, 2016
def485a
Add disable encryption volume check for Linux VMs
Aug 31, 2016
a9b53c4
Merge remote-tracking branch 'upstream/dev' into dev
Aug 31, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public static class AzureDiskEncryptionExtensionConstants
public const string aadClientSecretParameterSet = "AAD Client Secret Parameters";
public const string enableEncryptionOperation = "EnableEncryption";
public const string disableEncryptionOperation = "DisableEncryption";
public const string queryEncryptionStatusOperation = "QueryEncryptionStatus";
public const string encryptionResultOsKey = "os";
public const string encryptionResultDataKey = "data";
public const string aadClientIDKey = "AADClientID";
public const string aadClientSecretKey = "AADClientSecret";
public const string aadClientCertThumbprintKey = "AADClientCertThumbprint";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public AzureDiskEncryptionExtensionContext(PSVirtualMachineExtension psExt)
ProtectedSettings = psExt.ProtectedSettings;
ProvisioningState = psExt.ProvisioningState;
Statuses = psExt.Statuses;
SubStatuses = psExt.SubStatuses;

InitializeAzureDiskEncryptionMembers(psExt);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,20 @@ public override void ExecuteCmdlet()

currentOSType = virtualMachineResponse.StorageProfile.OsDisk.OsType;

if (OperatingSystemTypes.Linux.Equals(currentOSType) &&
!AzureDiskEncryptionExtensionContext.VolumeTypeData.Equals(VolumeType, StringComparison.InvariantCultureIgnoreCase))
{
ThrowTerminatingError(
new ErrorRecord(
new ArgumentException(
string.Format(
CultureInfo.CurrentUICulture,
"Disabling encryption is only allowed on Data volumes for Linux VMs.")),
"InvalidType",
ErrorCategory.NotImplemented,
null));
}

if (this.ShouldProcess(VMName, Properties.Resources.DisableDiskEncryptionAction)
&& (this.Force.IsPresent ||
this.ShouldContinue(Properties.Resources.DisableAzureDiskEncryptionConfirmation, Properties.Resources.DisableAzureDiskEncryptionCaption)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
using Microsoft.Azure.Commands.Compute.Models;
using Microsoft.Azure.Management.Compute;
using Microsoft.Azure.Management.Compute.Models;
using Microsoft.Rest.Azure;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Management.Automation;

Expand Down Expand Up @@ -45,6 +49,140 @@ public class GetAzureDiskEncryptionStatusCommand : VirtualMachineExtensionBaseCm
[ValidateNotNullOrEmpty]
public string VMName { get; set; }

[Alias("ExtensionName")]
[Parameter(
Mandatory = false,
Position = 2,
ValueFromPipelineByPropertyName = true,
HelpMessage = "The extension name. If this parameter is not specified, default values used are AzureDiskEncryption for Windows VMs and AzureDiskEncryptionForLinux for Linux VMs")]
[ValidateNotNullOrEmpty]
public string Name { get; set; }

private VirtualMachineExtension GetVmExtensionParameters(VirtualMachine vmParameters, OSType currentOSType)
{
Hashtable publicSettings = new Hashtable();
Hashtable protectedSettings = new Hashtable();

publicSettings.Add(AzureDiskEncryptionExtensionConstants.encryptionOperationKey, AzureDiskEncryptionExtensionConstants.queryEncryptionStatusOperation);
publicSettings.Add(AzureDiskEncryptionExtensionConstants.sequenceVersionKey, Guid.NewGuid().ToString());

if (vmParameters == null)
{
ThrowTerminatingError(new ErrorRecord(new ApplicationException(string.Format(CultureInfo.CurrentUICulture, "Get-AzureDiskEncryptionExtension can enable encryption only on a VM that was already created ")),
"InvalidResult",
ErrorCategory.InvalidResult,
null));
}

VirtualMachineExtension vmExtensionParameters = null;

if (OSType.Windows.Equals(currentOSType))
{
this.Name = this.Name ?? AzureDiskEncryptionExtensionContext.ExtensionDefaultName;
vmExtensionParameters = new VirtualMachineExtension
{
Location = vmParameters.Location,
Publisher = AzureDiskEncryptionExtensionContext.ExtensionDefaultPublisher,
VirtualMachineExtensionType = AzureDiskEncryptionExtensionContext.ExtensionDefaultName,
TypeHandlerVersion = AzureDiskEncryptionExtensionContext.ExtensionDefaultVersion,
Settings = publicSettings,
ProtectedSettings = protectedSettings
};
}
else if (OSType.Linux.Equals(currentOSType))
{
this.Name = this.Name ?? AzureDiskEncryptionExtensionContext.LinuxExtensionDefaultName;
vmExtensionParameters = new VirtualMachineExtension
{
Location = vmParameters.Location,
Publisher = AzureDiskEncryptionExtensionContext.LinuxExtensionDefaultPublisher,
VirtualMachineExtensionType = AzureDiskEncryptionExtensionContext.LinuxExtensionDefaultName,
TypeHandlerVersion = AzureDiskEncryptionExtensionContext.LinuxExtensionDefaultVersion,
Settings = publicSettings,
ProtectedSettings = protectedSettings
};
}

return vmExtensionParameters;
}

private string GetExtensionStatusMessage(OSType currentOSType, bool returnSubstatusMessage=false)
{
AzureOperationResponse<VirtualMachineExtension> extensionResult = this.VirtualMachineExtensionClient.GetWithInstanceView(this.ResourceGroupName, this.VMName, this.Name);
if (extensionResult == null)
{
ThrowTerminatingError(new ErrorRecord(new ApplicationFailedException(string.Format(CultureInfo.CurrentUICulture, "Failed to retrieve extension status")),
"InvalidResult",
ErrorCategory.InvalidResult,
null));
}

PSVirtualMachineExtension returnedExtension = extensionResult.ToPSVirtualMachineExtension(
this.ResourceGroupName, this.VMName);

if ((returnedExtension == null) ||
(string.IsNullOrWhiteSpace(returnedExtension.Publisher)) ||
(string.IsNullOrWhiteSpace(returnedExtension.ExtensionType)))
{
ThrowTerminatingError(new ErrorRecord(new ApplicationFailedException(string.Format(CultureInfo.CurrentUICulture, "Missing extension publisher and type info")),
"InvalidResult",
ErrorCategory.InvalidResult,
null));
}
bool publisherMatch = false;
if (OSType.Linux.Equals(currentOSType))
{
if (returnedExtension.Publisher.Equals(AzureDiskEncryptionExtensionContext.LinuxExtensionDefaultPublisher, StringComparison.InvariantCultureIgnoreCase) &&
returnedExtension.ExtensionType.Equals(AzureDiskEncryptionExtensionContext.LinuxExtensionDefaultName, StringComparison.InvariantCultureIgnoreCase))
{
publisherMatch = true;
}
}
else if (OSType.Windows.Equals(currentOSType))
{
if (returnedExtension.Publisher.Equals(AzureDiskEncryptionExtensionContext.ExtensionDefaultPublisher, StringComparison.InvariantCultureIgnoreCase) &&
returnedExtension.ExtensionType.Equals(AzureDiskEncryptionExtensionContext.ExtensionDefaultName, StringComparison.InvariantCultureIgnoreCase))
{
publisherMatch = true;
}
}
if (publisherMatch)
{
AzureDiskEncryptionExtensionContext context = new AzureDiskEncryptionExtensionContext(returnedExtension);
if ((context == null) ||
(context.Statuses == null) ||
(context.Statuses.Count < 1) ||
(string.IsNullOrWhiteSpace(context.Statuses[0].Message)))
{
throw new KeyNotFoundException(string.Format(CultureInfo.CurrentUICulture, "Invalid extension status"));
}

if (returnSubstatusMessage)
{
if((context == null) ||
(context.SubStatuses == null) ||
(context.SubStatuses.Count < 1))
{
throw new KeyNotFoundException(string.Format(CultureInfo.CurrentUICulture, "Invalid extension substatus"));
}
else
{
return context.SubStatuses[0].Message;
}
}

return context.Statuses[0].Message;
}
else
{
ThrowTerminatingError(new ErrorRecord(new ApplicationFailedException(string.Format(CultureInfo.CurrentUICulture, "Extension publisher and type mismatched")),
"InvalidResult",
ErrorCategory.InvalidResult,
null));
}
return null;
}

private OSType GetOSType(VirtualMachine vmParameters)
{
if (vmParameters == null || vmParameters.StorageProfile == null || vmParameters.StorageProfile.OsDisk == null)
Expand Down Expand Up @@ -209,17 +347,72 @@ public override void ExecuteCmdlet()
EncryptionStatus osVolumeEncrypted = IsOsVolumeEncrypted(vmParameters);
DiskEncryptionSettings osVolumeEncryptionSettings = GetOsVolumeEncryptionSettings(vmParameters);
EncryptionStatus dataVolumesEncrypted = AreDataVolumesEncrypted(vmParameters);
AzureDiskEncryptionStatusContext encryptionStatus = null;
string progressMessage = null;

OSType osType = GetOSType(vmParameters);
switch (osType)
{
case OSType.Windows:
case OSType.Linux:
AzureDiskEncryptionStatusContext encryptionStatus = new AzureDiskEncryptionStatusContext
try
{
progressMessage = GetExtensionStatusMessage(osType);
}
catch(KeyNotFoundException)
{
progressMessage = string.Format(CultureInfo.CurrentUICulture, "Extension status not available on the VM");
}

encryptionStatus = new AzureDiskEncryptionStatusContext
{
OsVolumeEncrypted = osVolumeEncrypted,
DataVolumesEncrypted = dataVolumesEncrypted,
OsVolumeEncryptionSettings = osVolumeEncryptionSettings
OsVolumeEncryptionSettings = osVolumeEncryptionSettings,
ProgressMessage = progressMessage
};
WriteObject(encryptionStatus);
break;
case OSType.Linux:
VirtualMachine virtualMachineResponse = this.ComputeClient.ComputeManagementClient.VirtualMachines.GetWithInstanceView(
this.ResourceGroupName, VMName).Body;
VirtualMachineExtension parameters = GetVmExtensionParameters(virtualMachineResponse, osType);

this.VirtualMachineExtensionClient.CreateOrUpdateWithHttpMessagesAsync(
this.ResourceGroupName,
this.VMName,
this.Name,
parameters).GetAwaiter().GetResult();

Dictionary<string, string> encryptionStatusParsed = null;
try
{
string encryptionStatusJson = GetExtensionStatusMessage(osType, returnSubstatusMessage: true);
encryptionStatusParsed = JsonConvert.DeserializeObject<Dictionary<string, string>>(encryptionStatusJson);
}
catch(KeyNotFoundException)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this catch too narrow? In the case of invalid input, it seems there might be other exceptions that could be thrown by this block that we might want to handle in the same way.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the exception that we throw ourselves in GetExtensionStatusMessage:

                if (returnSubstatusMessage)
                {
                    if(context.SubStatuses != null)
                    {
                        return context.SubStatuses[0].Message;
                    }
                    else
                    {
                        throw new KeyNotFoundException(string.Format(CultureInfo.CurrentUICulture, "Invalid extension substatus"));
                    }
                }

For other exceptions, we probably want to fail like we would've before this substatus change.

{
encryptionStatusParsed = new Dictionary<string, string>()
{
{ AzureDiskEncryptionExtensionConstants.encryptionResultOsKey, EncryptionStatus.Unknown.ToString() },
{ AzureDiskEncryptionExtensionConstants.encryptionResultDataKey, EncryptionStatus.Unknown.ToString() }
};
}

try
{
progressMessage = GetExtensionStatusMessage(osType);
}
catch(KeyNotFoundException)
{
progressMessage = string.Format(CultureInfo.CurrentUICulture, "Extension status not available on the VM");
}

encryptionStatus = new AzureDiskEncryptionStatusContext
{
OsVolumeEncrypted = (EncryptionStatus)Enum.Parse(typeof(EncryptionStatus), encryptionStatusParsed[AzureDiskEncryptionExtensionConstants.encryptionResultOsKey]),
DataVolumesEncrypted = (EncryptionStatus)Enum.Parse(typeof(EncryptionStatus), encryptionStatusParsed[AzureDiskEncryptionExtensionConstants.encryptionResultDataKey]),
OsVolumeEncryptionSettings = osVolumeEncryptionSettings,
ProgressMessage = progressMessage
};
WriteObject(encryptionStatus);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,20 +430,6 @@ public override void ExecuteCmdlet()

currentOSType = virtualMachineResponse.StorageProfile.OsDisk.OsType;

if (OperatingSystemTypes.Linux.Equals(currentOSType) &&
!AzureDiskEncryptionExtensionContext.VolumeTypeData.Equals(VolumeType, StringComparison.InvariantCultureIgnoreCase))
{
ThrowTerminatingError(
new ErrorRecord(
new ArgumentException(
string.Format(
CultureInfo.CurrentUICulture,
"Enabling encryption is only allowed on Data volumes for Linux VMs.")),
"InvalidType",
ErrorCategory.NotImplemented,
null));
}

if (OperatingSystemTypes.Linux.Equals(currentOSType))
{
CreateVMBackupForLinx();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,10 @@
<Label>OsVolumeEncryptionSettings</Label>
<PropertyName>OsVolumeEncryptionSettings</PropertyName>
</ListItem>
<ListItem>
<Label>ProgressMessage</Label>
<PropertyName>ProgressMessage</PropertyName>
</ListItem>
</ListItems>
</ListEntry>
</ListEntries>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ enum EncryptionStatus
{
Encrypted,
NotEncrypted,
NotMounted,
EncryptionInProgress,
VMRestartPending,
Unknown
}

Expand All @@ -29,5 +32,6 @@ class AzureDiskEncryptionStatusContext
public EncryptionStatus OsVolumeEncrypted { get; set; }
public DiskEncryptionSettings OsVolumeEncryptionSettings { get; set; }
public EncryptionStatus DataVolumesEncrypted { get; set; }
public string ProgressMessage { get; set; }
}
}