Skip to content
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

[CosmosDB] Bug Fixes and Enhancements #11505

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/Accounts/Accounts/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Additional information about change #1
-->
## Upcoming Release
* Updated Azure PowerShell survey URL in `Resolve-AzError` [#11507]

## Version 1.7.4
* Fixed `Get-AzTenant`/`Get-AzDefault`/`Set-AzDefault` throw NullReferenceException when not login [#10292]
Expand Down
4 changes: 3 additions & 1 deletion src/Accounts/Accounts/Common/AzureProfileConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ public static class AzureProfileConstants

public const string AzureSurveyUrl = "https://aka.ms/azpssurvey?Q_CHL=FEEDBACK";

public static readonly string AzurePowerShellFeedbackMessage = string.Format(Resources.AzurePowerShellFeedback, AzureProfileConstants.AzureSurveyUrl);
public const string AzureSurveyUrlForError = "https://aka.ms/azpssurvey?Q_CHL=ERROR";

public static readonly string AzurePowerShellFeedbackMessage = string.Format(Resources.AzurePowerShellFeedback, AzureProfileConstants.AzureSurveyUrlForError);

public static readonly string AzurePowerShellFeedbackQuestion = string.Format(Resources.SendFeedbackOpenLinkAutomatically, AzureProfileConstants.AzureSurveyUrl);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ function Test-GetAttestationPolicy
Assert-NotNull $attestationCreated.Status

$getPolicy = Get-AzAttestationPolicy -Name $attestationProviderName -ResourceGroupName $rgName.ResourceGroupName -Tee $teeType
Assert-NotNull $getPolicy
Assert-NotNull $getPolicy.Jwt
Assert-NotNull $getPolicy.Text
Assert-AreEqual "none" $getPolicy.Algorithm
Assert-True { $getPolicy.JwtLength -gt 0 }
Assert-True { $getPolicy.TextLength -gt 0 }
}

finally
Expand Down Expand Up @@ -93,7 +97,8 @@ function Test-SetAttestationPolicy
$attestationProviderName = getAssetName
$location = "East US"
$teeType = "SgxEnclave"
$policyDocument = "eyJhbGciOiJub25lIn0.eyJBdHRlc3RhdGlvblBvbGljeSI6ICJ7XHJcbiAgICBcIiR2ZXJzaW9uXCI6IDEsXHJcbiAgICBcIiRhbGxvdy1kZWJ1Z2dhYmxlXCIgOiB0cnVlLFxyXG4gICAgXCIkY2xhaW1zXCI6W1xyXG4gICAgICAgIFwiaXMtZGVidWdnYWJsZVwiICxcclxuICAgICAgICBcInNneC1tcnNpZ25lclwiLFxyXG4gICAgICAgIFwic2d4LW1yZW5jbGF2ZVwiLFxyXG4gICAgICAgIFwicHJvZHVjdC1pZFwiLFxyXG4gICAgICAgIFwic3ZuXCIsXHJcbiAgICAgICAgXCJ0ZWVcIixcclxuICAgICAgICBcIk5vdERlYnVnZ2FibGVcIlxyXG4gICAgXSxcclxuICAgIFwiTm90RGVidWdnYWJsZVwiOiB7XCJ5ZXNcIjp7XCIkaXMtZGVidWdnYWJsZVwiOnRydWUsIFwiJG1hbmRhdG9yeVwiOnRydWUsIFwiJHZpc2libGVcIjpmYWxzZX19LFxyXG4gICAgXCJpcy1kZWJ1Z2dhYmxlXCIgOiBcIiRpcy1kZWJ1Z2dhYmxlXCIsXHJcbiAgICBcInNneC1tcnNpZ25lclwiIDogXCIkc2d4LW1yc2lnbmVyXCIsXHJcbiAgICBcInNneC1tcmVuY2xhdmVcIiA6IFwiJHNneC1tcmVuY2xhdmVcIixcclxuICAgIFwicHJvZHVjdC1pZFwiIDogXCIkcHJvZHVjdC1pZFwiLFxyXG4gICAgXCJzdm5cIiA6IFwiJHN2blwiLFxyXG4gICAgXCJ0ZWVcIiA6IFwiJHRlZVwiXHJcbn0ifQ."
$policyJwt = "eyJhbGciOiJub25lIn0.eyJBdHRlc3RhdGlvblBvbGljeSI6ICJ7XHJcbiAgICBcIiR2ZXJzaW9uXCI6IDEsXHJcbiAgICBcIiRhbGxvdy1kZWJ1Z2dhYmxlXCIgOiB0cnVlLFxyXG4gICAgXCIkY2xhaW1zXCI6W1xyXG4gICAgICAgIFwiaXMtZGVidWdnYWJsZVwiICxcclxuICAgICAgICBcInNneC1tcnNpZ25lclwiLFxyXG4gICAgICAgIFwic2d4LW1yZW5jbGF2ZVwiLFxyXG4gICAgICAgIFwicHJvZHVjdC1pZFwiLFxyXG4gICAgICAgIFwic3ZuXCIsXHJcbiAgICAgICAgXCJ0ZWVcIixcclxuICAgICAgICBcIk5vdERlYnVnZ2FibGVcIlxyXG4gICAgXSxcclxuICAgIFwiTm90RGVidWdnYWJsZVwiOiB7XCJ5ZXNcIjp7XCIkaXMtZGVidWdnYWJsZVwiOnRydWUsIFwiJG1hbmRhdG9yeVwiOnRydWUsIFwiJHZpc2libGVcIjpmYWxzZX19LFxyXG4gICAgXCJpcy1kZWJ1Z2dhYmxlXCIgOiBcIiRpcy1kZWJ1Z2dhYmxlXCIsXHJcbiAgICBcInNneC1tcnNpZ25lclwiIDogXCIkc2d4LW1yc2lnbmVyXCIsXHJcbiAgICBcInNneC1tcmVuY2xhdmVcIiA6IFwiJHNneC1tcmVuY2xhdmVcIixcclxuICAgIFwicHJvZHVjdC1pZFwiIDogXCIkcHJvZHVjdC1pZFwiLFxyXG4gICAgXCJzdm5cIiA6IFwiJHN2blwiLFxyXG4gICAgXCJ0ZWVcIiA6IFwiJHRlZVwiXHJcbn0ifQ."
$policyText = 'version= 1.0;authorizationrules{c:[type=="$is-debuggable"] => permit();};issuancerules{c:[type=="$is-debuggable"] => issue(type="is-debuggable", value=c.value);c:[type=="$sgx-mrsigner"] => issue(type="sgx-mrsigner", value=c.value);c:[type=="$sgx-mrenclave"] => issue(type="sgx-mrenclave", value=c.value);c:[type=="$product-id"] => issue(type="product-id", value=c.value);c:[type=="$svn"] => issue(type="svn", value=c.value);c:[type=="$tee"] => issue(type="tee", value=c.value);c:[type=="$tee-future"] => issue(type="tee-future", value=c.value);};'

# Prevent this script from inadvertantly running in Record or Playback modes
try
Expand Down Expand Up @@ -121,7 +126,10 @@ function Test-SetAttestationPolicy
Assert-NotNull $attestationCreated.Status

# NOTE: Set-AzAttestionPolicy does not work in recording/playback mode because the recorded JWT token expires and then fails validation
$setPolicyResponse = Set-AzAttestationPolicy -Name $attestationProviderName -ResourceGroupName $rgName.ResourceGroupName -Tee $teeType -Policy $policyDocument -PassThru
$setPolicyResponse = Set-AzAttestationPolicy -Name $attestationProviderName -ResourceGroupName $rgName.ResourceGroupName -Tee $teeType -Policy $policyJwt -PolicyFormat Jwt -PassThru
Assert-AreEqual $setPolicyResponse $true

$setPolicyResponse = Set-AzAttestationPolicy -Name $attestationProviderName -ResourceGroupName $rgName.ResourceGroupName -Tee $teeType -Policy $policyText -PassThru
Assert-AreEqual $setPolicyResponse $true
}

Expand Down
1 change: 1 addition & 0 deletions src/Attestation/Attestation/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@


## Upcoming Release
* Added text based policy support to policy cmdlets

## Version 0.1.6
* Improved error messages for server response codes 400 and 401
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace Microsoft.Azure.Commands.Attestation
/// Get AttestationPolicy.
/// </summary>
[Cmdlet("Get", ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "AttestationPolicy", SupportsShouldProcess = true)]
[OutputType(typeof(String))]
[OutputType(typeof(PSPolicy))]
public class GetAzureAttestationPolicy : AttestationDataServiceCmdletBase
{
#region Input Parameter Definitions
Expand Down Expand Up @@ -79,7 +79,7 @@ public class GetAzureAttestationPolicy : AttestationDataServiceCmdletBase
public override void ExecuteCmdlet()
{
String policy = AttestationDataPlaneClient.GetPolicy(Name, ResourceGroupName, ResourceId, Tee);
WriteObject(policy);
WriteObject(new PSPolicy(policy));
}
}
}
22 changes: 18 additions & 4 deletions src/Attestation/Attestation/Commands/SetAzureAttestationPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,33 @@ public class SetAzureAttestationPolicy : AttestationDataServiceCmdletBase
/// </summary>
[Parameter(Mandatory = true,
HelpMessage =
"Specifies a type of Trusted Execution Environment. We support four types of environment: SgxEnclave, OpenEnclave, CyResComponent and VBSEnclave."
"Specifies a type of Trusted Execution Environment. Four types of environment are supported: SgxEnclave, OpenEnclave, CyResComponent and VBSEnclave."
)]
[PSArgumentCompleter("SgxEnclave", "OpenEnclave", "CyResComponent", "VBSEnclave")]
[ValidateNotNullOrEmpty]
public string Tee { get; set; }

/// <summary>
/// JSON Web Token
/// Policy document
/// </summary>
[Parameter(Mandatory = true,
HelpMessage =
"Specifies the JSON Web Token describing the policy document to set."
"Specifies the policy document to set. The policy format can be either Text or JSON Web Token (JWT)."
)]
[ValidateNotNullOrEmpty]
public string Policy { get; set; }

/// <summary>
/// Format of the policy document
/// </summary>
[Parameter(Mandatory = false,
HelpMessage =
"Specifies the format for the policy, either Text or JWT (JSON Web Token). The default policy format is Text."
)]
[PSArgumentCompleter(TextPolicyFormat, JwtPolicyFormat)]
[PSDefaultValue(Value = TextPolicyFormat)]
public string PolicyFormat { get; set; }

[Parameter(Mandatory = false,
HelpMessage = "This Cmdlet does not return an object by default. If this switch is specified, it returns true if successful.")]
public SwitchParameter PassThru { get; set; }
Expand All @@ -94,12 +105,15 @@ public override void ExecuteCmdlet()
{
if (ShouldProcess(Name, "SetAttestationPolicy"))
{
AttestationDataPlaneClient.SetPolicy(Name, ResourceGroupName, ResourceId, Tee, Policy);
AttestationDataPlaneClient.SetPolicy(Name, ResourceGroupName, ResourceId, Tee, Policy, PolicyFormat);
if (PassThru)
{
WriteObject(true);
}
}
}

internal const string JwtPolicyFormat = "JWT";
internal const string TextPolicyFormat = "Text";
}
}
47 changes: 40 additions & 7 deletions src/Attestation/Attestation/Models/AttestationDataServiceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,29 @@ public AttestationDataServiceClient(IAuthenticationFactory authFactory, IAzureCo
_attestationControlPlaneClient = AzureSession.Instance.ClientFactory.CreateArmClient<Management.Attestation.AttestationManagementClient>(context, AzureEnvironment.Endpoint.ResourceManager);
}

public void SetPolicy(string name, string resourceGroupName, string resourceId, string tee, string policyJwt)
public void SetPolicy(string name, string resourceGroupName, string resourceId, string tee, string userSpecifiedPolicy, string policyFormat)
{
ValidateCommonParameters(ref name, ref resourceGroupName, resourceId);
if (string.IsNullOrEmpty(tee))
throw new ArgumentNullException(nameof(tee));
if (string.IsNullOrEmpty(policyJwt))
throw new ArgumentNullException(nameof(policyJwt));
if (string.IsNullOrEmpty(userSpecifiedPolicy))
throw new ArgumentNullException(nameof(userSpecifiedPolicy));

// Step #1 - Ask service to prepare to set policy
// Step #1 - Convert text policy to JWT if necessary
var processedPolicy = GenerateJwtPolicyIfNeeded(policyFormat, userSpecifiedPolicy);

// Step #2 - Ask service to prepare to set policy
AzureOperationResponse<object> serviceCallResult = RefreshUriCacheAndRetryOnFailure(name, resourceGroupName, (tenantUri) =>
_attestationDataPlaneClient.Policy.PrepareToSetWithHttpMessagesAsync(tenantUri, tee, policyJwt).Result);
_attestationDataPlaneClient.Policy.PrepareToSetWithHttpMessagesAsync(tenantUri, tee, processedPolicy).Result);
ThrowOn4xxErrors(serviceCallResult);

// Step #2 - Validate service response locally
// Step #3 - Validate service response locally
string policyUpdateJwt = serviceCallResult.Body.ToString();
var validatedToken = PolicyValidationHelper.ValidateAttestationServiceToken(name, DataPlaneUriLookup[(name, resourceGroupName)], policyUpdateJwt);
if (!validatedToken.IsValid)
throw new ArgumentException("policyJwt is not valid");

// Step #3 - Ask service to set policy
// Step #4 - Ask service to set policy
serviceCallResult = RefreshUriCacheAndRetryOnFailure(name, resourceGroupName, (tenantUri) =>
_attestationDataPlaneClient.Policy.SetWithHttpMessagesAsync(tenantUri, tee, policyUpdateJwt).Result);
ThrowOn4xxErrors(serviceCallResult);
Expand Down Expand Up @@ -133,6 +136,36 @@ public string RemovePolicySigner(string name, string resourceGroupName, string r

#region Private helper methods

private string GenerateJwtPolicyIfNeeded(string policyFormat, string userSpecifiedPolicy)
{
var processedPolicy = string.Empty;
if (string.IsNullOrEmpty(policyFormat) ||
SetAzureAttestationPolicy.TextPolicyFormat.Equals(policyFormat, StringComparison.InvariantCultureIgnoreCase))
{
processedPolicy = this.GenerateJwtPolicy(userSpecifiedPolicy);
}
else if (SetAzureAttestationPolicy.JwtPolicyFormat.Equals(policyFormat, StringComparison.InvariantCultureIgnoreCase))
{
processedPolicy = userSpecifiedPolicy;
}
else
{
throw new ArgumentException(nameof(policyFormat));
}

return processedPolicy;
}

private string GenerateJwtPolicy(string textPolicy)
{
var header = Base64Url.EncodeString("{\"alg\":\"none\"}");
var encodedPolicy = Base64Url.EncodeString(textPolicy);
var bodyText = "{\"AttestationPolicy\": \"" + encodedPolicy + "\"}";
var body = Base64Url.EncodeString(bodyText);

return $"{header}.{body}.";
}

private void ValidateCommonParameters(ref string name, ref string resourceGroupName, string resourceId)
{
if (!string.IsNullOrEmpty(resourceId))
Expand Down
21 changes: 19 additions & 2 deletions src/Attestation/Attestation/Models/Base64Url.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,40 @@
// ----------------------------------------------------------------------------------

using System;
using System.Text;

namespace Microsoft.Azure.Commands.Attestation.Models
{
public static class Base64Url
{
/// <summary>Encode a string as a Base64URL encoded string.</summary>
/// <param name="bytes">String input buffer.</param>
/// <returns>The UTF8 bytes for the string, encoded as a Base64URL string.</returns>
public static string EncodeString(string value)
{
return EncodeBytes(UTF8Encoding.UTF8.GetBytes(value));
}

/// <summary>Encode a byte array as a Base64URL encoded string.</summary>
/// <param name="bytes">Raw byte input buffer.</param>
/// <returns>The bytes, encoded as a Base64URL string.</returns>
public static string Encode(byte[] bytes)
public static string EncodeBytes(byte[] bytes)
{
return Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}

/// <summary> Converts a Base64URL encoded string to a string</summary>
/// <param name="encoded">The Base64Url encoded string containing UTF8 bytes for a string</param>
/// <returns>The string represented by the Base64URL encoded string</returns>
public static string DecodeString(string encoded)
{
return UTF8Encoding.UTF8.GetString(DecodeBytes(encoded));
}

/// <summary>Converts a Base64URL encoded string to a byte array</summary>
/// <param name="encoded">The Base64Url encoded string</param>
/// <returns>The byte array represented by the Base64URL encoded string</returns>
public static byte[] Decode(string encoded)
public static byte[] DecodeBytes(string encoded)
{
encoded = encoded.Replace('-', '+').Replace('_', '/');
encoded = FixPadding(encoded);
Expand Down
34 changes: 34 additions & 0 deletions src/Attestation/Attestation/Models/JoseHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// ----------------------------------------------------------------------------------
//
// Copyright Microsoft Corporation
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------------------------------------------------------------

using Newtonsoft.Json.Linq;

namespace Microsoft.Azure.Commands.Attestation.Models
{
internal class JoseHelper
{
public static JObject ExtractJosePart(string jwt, int partIndex)
{
string[] joseParts = jwt.Split('.');
var decodedPart = Base64Url.DecodeString(joseParts[partIndex]);
JObject jsonPart = JObject.Parse(decodedPart);
return jsonPart;
}
public static JToken ExtractJosePartField(string jwt, int partIndex, string fieldName)
{
var part = ExtractJosePart(jwt, partIndex);
return part[fieldName];
}
}
}
82 changes: 82 additions & 0 deletions src/Attestation/Attestation/Models/PSPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// ----------------------------------------------------------------------------------
//
// Copyright Microsoft Corporation
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------------------------------------------------------------

using System;

namespace Microsoft.Azure.Commands.Attestation.Models
{
public class PSPolicy
{
public PSPolicy(string jwt)
{
Jwt = jwt;
JwtLength = Jwt?.Length ?? 0;
Text = ExtractPolicyText(Jwt);
TextLength = Text?.Length ?? 0;
Algorithm = ExtractAlgorithm(Jwt);
}

public string Text { get; }

public int TextLength { get; }

public string Jwt { get; }

public int JwtLength { get; }

public string Algorithm { get; protected set; }

private static string ExtractAlgorithm(string jwt)
{
var algorithm = string.Empty;
if (!string.IsNullOrEmpty(jwt))
{
try
{
algorithm = JoseHelper.ExtractJosePartField(jwt, 0, "alg").ToString();
}
catch (Exception)
{
// Ignore on purpose
}
}
return algorithm;
}

private static string ExtractPolicyText(string jwt)
{
string parsedPolicy = string.Empty;

if (!string.IsNullOrEmpty(jwt))
{
try
{
parsedPolicy = JoseHelper.ExtractJosePartField(jwt, 1, "AttestationPolicy").ToString();

// Policy is optionally double base64 URL encoded. We will attempt
// to base64 URL decode a second time -- if this throws an exception,
// that's OK -- we should just use value as it stands now.
var doubleDecodedPolicy = Base64Url.DecodeString(parsedPolicy);
parsedPolicy = doubleDecodedPolicy;
}
catch (Exception)
{
// Ignore on purpose
}
}

return parsedPolicy;
}
}
}
Loading