Skip to content

Commit b810c74

Browse files
authored
Added support for text based policies (#11502)
1 parent ca93419 commit b810c74

12 files changed

+284
-44
lines changed

src/Attestation/Attestation.Test/ScenarioTests/AttstationPolicyTests.ps1

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ function Test-GetAttestationPolicy
3737
Assert-NotNull $attestationCreated.Status
3838

3939
$getPolicy = Get-AzAttestationPolicy -Name $attestationProviderName -ResourceGroupName $rgName.ResourceGroupName -Tee $teeType
40-
Assert-NotNull $getPolicy
40+
Assert-NotNull $getPolicy.Jwt
41+
Assert-NotNull $getPolicy.Text
42+
Assert-AreEqual "none" $getPolicy.Algorithm
43+
Assert-True { $getPolicy.JwtLength -gt 0 }
44+
Assert-True { $getPolicy.TextLength -gt 0 }
4145
}
4246

4347
finally
@@ -93,7 +97,8 @@ function Test-SetAttestationPolicy
9397
$attestationProviderName = getAssetName
9498
$location = "East US"
9599
$teeType = "SgxEnclave"
96-
$policyDocument = "eyJhbGciOiJub25lIn0.eyJBdHRlc3RhdGlvblBvbGljeSI6ICJ7XHJcbiAgICBcIiR2ZXJzaW9uXCI6IDEsXHJcbiAgICBcIiRhbGxvdy1kZWJ1Z2dhYmxlXCIgOiB0cnVlLFxyXG4gICAgXCIkY2xhaW1zXCI6W1xyXG4gICAgICAgIFwiaXMtZGVidWdnYWJsZVwiICxcclxuICAgICAgICBcInNneC1tcnNpZ25lclwiLFxyXG4gICAgICAgIFwic2d4LW1yZW5jbGF2ZVwiLFxyXG4gICAgICAgIFwicHJvZHVjdC1pZFwiLFxyXG4gICAgICAgIFwic3ZuXCIsXHJcbiAgICAgICAgXCJ0ZWVcIixcclxuICAgICAgICBcIk5vdERlYnVnZ2FibGVcIlxyXG4gICAgXSxcclxuICAgIFwiTm90RGVidWdnYWJsZVwiOiB7XCJ5ZXNcIjp7XCIkaXMtZGVidWdnYWJsZVwiOnRydWUsIFwiJG1hbmRhdG9yeVwiOnRydWUsIFwiJHZpc2libGVcIjpmYWxzZX19LFxyXG4gICAgXCJpcy1kZWJ1Z2dhYmxlXCIgOiBcIiRpcy1kZWJ1Z2dhYmxlXCIsXHJcbiAgICBcInNneC1tcnNpZ25lclwiIDogXCIkc2d4LW1yc2lnbmVyXCIsXHJcbiAgICBcInNneC1tcmVuY2xhdmVcIiA6IFwiJHNneC1tcmVuY2xhdmVcIixcclxuICAgIFwicHJvZHVjdC1pZFwiIDogXCIkcHJvZHVjdC1pZFwiLFxyXG4gICAgXCJzdm5cIiA6IFwiJHN2blwiLFxyXG4gICAgXCJ0ZWVcIiA6IFwiJHRlZVwiXHJcbn0ifQ."
100+
$policyJwt = "eyJhbGciOiJub25lIn0.eyJBdHRlc3RhdGlvblBvbGljeSI6ICJ7XHJcbiAgICBcIiR2ZXJzaW9uXCI6IDEsXHJcbiAgICBcIiRhbGxvdy1kZWJ1Z2dhYmxlXCIgOiB0cnVlLFxyXG4gICAgXCIkY2xhaW1zXCI6W1xyXG4gICAgICAgIFwiaXMtZGVidWdnYWJsZVwiICxcclxuICAgICAgICBcInNneC1tcnNpZ25lclwiLFxyXG4gICAgICAgIFwic2d4LW1yZW5jbGF2ZVwiLFxyXG4gICAgICAgIFwicHJvZHVjdC1pZFwiLFxyXG4gICAgICAgIFwic3ZuXCIsXHJcbiAgICAgICAgXCJ0ZWVcIixcclxuICAgICAgICBcIk5vdERlYnVnZ2FibGVcIlxyXG4gICAgXSxcclxuICAgIFwiTm90RGVidWdnYWJsZVwiOiB7XCJ5ZXNcIjp7XCIkaXMtZGVidWdnYWJsZVwiOnRydWUsIFwiJG1hbmRhdG9yeVwiOnRydWUsIFwiJHZpc2libGVcIjpmYWxzZX19LFxyXG4gICAgXCJpcy1kZWJ1Z2dhYmxlXCIgOiBcIiRpcy1kZWJ1Z2dhYmxlXCIsXHJcbiAgICBcInNneC1tcnNpZ25lclwiIDogXCIkc2d4LW1yc2lnbmVyXCIsXHJcbiAgICBcInNneC1tcmVuY2xhdmVcIiA6IFwiJHNneC1tcmVuY2xhdmVcIixcclxuICAgIFwicHJvZHVjdC1pZFwiIDogXCIkcHJvZHVjdC1pZFwiLFxyXG4gICAgXCJzdm5cIiA6IFwiJHN2blwiLFxyXG4gICAgXCJ0ZWVcIiA6IFwiJHRlZVwiXHJcbn0ifQ."
101+
$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);};'
97102

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

123128
# NOTE: Set-AzAttestionPolicy does not work in recording/playback mode because the recorded JWT token expires and then fails validation
124-
$setPolicyResponse = Set-AzAttestationPolicy -Name $attestationProviderName -ResourceGroupName $rgName.ResourceGroupName -Tee $teeType -Policy $policyDocument -PassThru
129+
$setPolicyResponse = Set-AzAttestationPolicy -Name $attestationProviderName -ResourceGroupName $rgName.ResourceGroupName -Tee $teeType -Policy $policyJwt -PolicyFormat Jwt -PassThru
130+
Assert-AreEqual $setPolicyResponse $true
131+
132+
$setPolicyResponse = Set-AzAttestationPolicy -Name $attestationProviderName -ResourceGroupName $rgName.ResourceGroupName -Tee $teeType -Policy $policyText -PassThru
125133
Assert-AreEqual $setPolicyResponse $true
126134
}
127135

src/Attestation/Attestation/ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121

2222
## Upcoming Release
23+
* Added text based policy support to policy cmdlets
2324

2425
## Version 0.1.6
2526
* Improved error messages for server response codes 400 and 401

src/Attestation/Attestation/Commands/GetAzureAttestationPolicy.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ namespace Microsoft.Azure.Commands.Attestation
2323
/// Get AttestationPolicy.
2424
/// </summary>
2525
[Cmdlet("Get", ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "AttestationPolicy", SupportsShouldProcess = true)]
26-
[OutputType(typeof(String))]
26+
[OutputType(typeof(PSPolicy))]
2727
public class GetAzureAttestationPolicy : AttestationDataServiceCmdletBase
2828
{
2929
#region Input Parameter Definitions
@@ -79,7 +79,7 @@ public class GetAzureAttestationPolicy : AttestationDataServiceCmdletBase
7979
public override void ExecuteCmdlet()
8080
{
8181
String policy = AttestationDataPlaneClient.GetPolicy(Name, ResourceGroupName, ResourceId, Tee);
82-
WriteObject(policy);
82+
WriteObject(new PSPolicy(policy));
8383
}
8484
}
8585
}

src/Attestation/Attestation/Commands/SetAzureAttestationPolicy.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,33 @@ public class SetAzureAttestationPolicy : AttestationDataServiceCmdletBase
6868
/// </summary>
6969
[Parameter(Mandatory = true,
7070
HelpMessage =
71-
"Specifies a type of Trusted Execution Environment. We support four types of environment: SgxEnclave, OpenEnclave, CyResComponent and VBSEnclave."
71+
"Specifies a type of Trusted Execution Environment. Four types of environment are supported: SgxEnclave, OpenEnclave, CyResComponent and VBSEnclave."
7272
)]
7373
[PSArgumentCompleter("SgxEnclave", "OpenEnclave", "CyResComponent", "VBSEnclave")]
7474
[ValidateNotNullOrEmpty]
7575
public string Tee { get; set; }
7676

7777
/// <summary>
78-
/// JSON Web Token
78+
/// Policy document
7979
/// </summary>
8080
[Parameter(Mandatory = true,
8181
HelpMessage =
82-
"Specifies the JSON Web Token describing the policy document to set."
82+
"Specifies the policy document to set. The policy format can be either Text or JSON Web Token (JWT)."
8383
)]
8484
[ValidateNotNullOrEmpty]
8585
public string Policy { get; set; }
8686

87+
/// <summary>
88+
/// Format of the policy document
89+
/// </summary>
90+
[Parameter(Mandatory = false,
91+
HelpMessage =
92+
"Specifies the format for the policy, either Text or JWT (JSON Web Token). The default policy format is Text."
93+
)]
94+
[PSArgumentCompleter(TextPolicyFormat, JwtPolicyFormat)]
95+
[PSDefaultValue(Value = TextPolicyFormat)]
96+
public string PolicyFormat { get; set; }
97+
8798
[Parameter(Mandatory = false,
8899
HelpMessage = "This Cmdlet does not return an object by default. If this switch is specified, it returns true if successful.")]
89100
public SwitchParameter PassThru { get; set; }
@@ -94,12 +105,15 @@ public override void ExecuteCmdlet()
94105
{
95106
if (ShouldProcess(Name, "SetAttestationPolicy"))
96107
{
97-
AttestationDataPlaneClient.SetPolicy(Name, ResourceGroupName, ResourceId, Tee, Policy);
108+
AttestationDataPlaneClient.SetPolicy(Name, ResourceGroupName, ResourceId, Tee, Policy, PolicyFormat);
98109
if (PassThru)
99110
{
100111
WriteObject(true);
101112
}
102113
}
103114
}
115+
116+
internal const string JwtPolicyFormat = "JWT";
117+
internal const string TextPolicyFormat = "Text";
104118
}
105119
}

src/Attestation/Attestation/Models/AttestationDataServiceClient.cs

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,29 @@ public AttestationDataServiceClient(IAuthenticationFactory authFactory, IAzureCo
4848
_attestationControlPlaneClient = AzureSession.Instance.ClientFactory.CreateArmClient<Management.Attestation.AttestationManagementClient>(context, AzureEnvironment.Endpoint.ResourceManager);
4949
}
5050

51-
public void SetPolicy(string name, string resourceGroupName, string resourceId, string tee, string policyJwt)
51+
public void SetPolicy(string name, string resourceGroupName, string resourceId, string tee, string userSpecifiedPolicy, string policyFormat)
5252
{
5353
ValidateCommonParameters(ref name, ref resourceGroupName, resourceId);
5454
if (string.IsNullOrEmpty(tee))
5555
throw new ArgumentNullException(nameof(tee));
56-
if (string.IsNullOrEmpty(policyJwt))
57-
throw new ArgumentNullException(nameof(policyJwt));
56+
if (string.IsNullOrEmpty(userSpecifiedPolicy))
57+
throw new ArgumentNullException(nameof(userSpecifiedPolicy));
5858

59-
// Step #1 - Ask service to prepare to set policy
59+
// Step #1 - Convert text policy to JWT if necessary
60+
var processedPolicy = GenerateJwtPolicyIfNeeded(policyFormat, userSpecifiedPolicy);
61+
62+
// Step #2 - Ask service to prepare to set policy
6063
AzureOperationResponse<object> serviceCallResult = RefreshUriCacheAndRetryOnFailure(name, resourceGroupName, (tenantUri) =>
61-
_attestationDataPlaneClient.Policy.PrepareToSetWithHttpMessagesAsync(tenantUri, tee, policyJwt).Result);
64+
_attestationDataPlaneClient.Policy.PrepareToSetWithHttpMessagesAsync(tenantUri, tee, processedPolicy).Result);
6265
ThrowOn4xxErrors(serviceCallResult);
6366

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

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

134137
#region Private helper methods
135138

139+
private string GenerateJwtPolicyIfNeeded(string policyFormat, string userSpecifiedPolicy)
140+
{
141+
var processedPolicy = string.Empty;
142+
if (string.IsNullOrEmpty(policyFormat) ||
143+
SetAzureAttestationPolicy.TextPolicyFormat.Equals(policyFormat, StringComparison.InvariantCultureIgnoreCase))
144+
{
145+
processedPolicy = this.GenerateJwtPolicy(userSpecifiedPolicy);
146+
}
147+
else if (SetAzureAttestationPolicy.JwtPolicyFormat.Equals(policyFormat, StringComparison.InvariantCultureIgnoreCase))
148+
{
149+
processedPolicy = userSpecifiedPolicy;
150+
}
151+
else
152+
{
153+
throw new ArgumentException(nameof(policyFormat));
154+
}
155+
156+
return processedPolicy;
157+
}
158+
159+
private string GenerateJwtPolicy(string textPolicy)
160+
{
161+
var header = Base64Url.EncodeString("{\"alg\":\"none\"}");
162+
var encodedPolicy = Base64Url.EncodeString(textPolicy);
163+
var bodyText = "{\"AttestationPolicy\": \"" + encodedPolicy + "\"}";
164+
var body = Base64Url.EncodeString(bodyText);
165+
166+
return $"{header}.{body}.";
167+
}
168+
136169
private void ValidateCommonParameters(ref string name, ref string resourceGroupName, string resourceId)
137170
{
138171
if (!string.IsNullOrEmpty(resourceId))

src/Attestation/Attestation/Models/Base64Url.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,40 @@
1313
// ----------------------------------------------------------------------------------
1414

1515
using System;
16+
using System.Text;
1617

1718
namespace Microsoft.Azure.Commands.Attestation.Models
1819
{
1920
public static class Base64Url
2021
{
22+
/// <summary>Encode a string as a Base64URL encoded string.</summary>
23+
/// <param name="bytes">String input buffer.</param>
24+
/// <returns>The UTF8 bytes for the string, encoded as a Base64URL string.</returns>
25+
public static string EncodeString(string value)
26+
{
27+
return EncodeBytes(UTF8Encoding.UTF8.GetBytes(value));
28+
}
29+
2130
/// <summary>Encode a byte array as a Base64URL encoded string.</summary>
2231
/// <param name="bytes">Raw byte input buffer.</param>
2332
/// <returns>The bytes, encoded as a Base64URL string.</returns>
24-
public static string Encode(byte[] bytes)
33+
public static string EncodeBytes(byte[] bytes)
2534
{
2635
return Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_');
2736
}
2837

38+
/// <summary> Converts a Base64URL encoded string to a string</summary>
39+
/// <param name="encoded">The Base64Url encoded string containing UTF8 bytes for a string</param>
40+
/// <returns>The string represented by the Base64URL encoded string</returns>
41+
public static string DecodeString(string encoded)
42+
{
43+
return UTF8Encoding.UTF8.GetString(DecodeBytes(encoded));
44+
}
45+
2946
/// <summary>Converts a Base64URL encoded string to a byte array</summary>
3047
/// <param name="encoded">The Base64Url encoded string</param>
3148
/// <returns>The byte array represented by the Base64URL encoded string</returns>
32-
public static byte[] Decode(string encoded)
49+
public static byte[] DecodeBytes(string encoded)
3350
{
3451
encoded = encoded.Replace('-', '+').Replace('_', '/');
3552
encoded = FixPadding(encoded);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using Newtonsoft.Json.Linq;
16+
17+
namespace Microsoft.Azure.Commands.Attestation.Models
18+
{
19+
internal class JoseHelper
20+
{
21+
public static JObject ExtractJosePart(string jwt, int partIndex)
22+
{
23+
string[] joseParts = jwt.Split('.');
24+
var decodedPart = Base64Url.DecodeString(joseParts[partIndex]);
25+
JObject jsonPart = JObject.Parse(decodedPart);
26+
return jsonPart;
27+
}
28+
public static JToken ExtractJosePartField(string jwt, int partIndex, string fieldName)
29+
{
30+
var part = ExtractJosePart(jwt, partIndex);
31+
return part[fieldName];
32+
}
33+
}
34+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using System;
16+
17+
namespace Microsoft.Azure.Commands.Attestation.Models
18+
{
19+
public class PSPolicy
20+
{
21+
public PSPolicy(string jwt)
22+
{
23+
Jwt = jwt;
24+
JwtLength = Jwt?.Length ?? 0;
25+
Text = ExtractPolicyText(Jwt);
26+
TextLength = Text?.Length ?? 0;
27+
Algorithm = ExtractAlgorithm(Jwt);
28+
}
29+
30+
public string Text { get; }
31+
32+
public int TextLength { get; }
33+
34+
public string Jwt { get; }
35+
36+
public int JwtLength { get; }
37+
38+
public string Algorithm { get; protected set; }
39+
40+
private static string ExtractAlgorithm(string jwt)
41+
{
42+
var algorithm = string.Empty;
43+
if (!string.IsNullOrEmpty(jwt))
44+
{
45+
try
46+
{
47+
algorithm = JoseHelper.ExtractJosePartField(jwt, 0, "alg").ToString();
48+
}
49+
catch (Exception)
50+
{
51+
// Ignore on purpose
52+
}
53+
}
54+
return algorithm;
55+
}
56+
57+
private static string ExtractPolicyText(string jwt)
58+
{
59+
string parsedPolicy = string.Empty;
60+
61+
if (!string.IsNullOrEmpty(jwt))
62+
{
63+
try
64+
{
65+
parsedPolicy = JoseHelper.ExtractJosePartField(jwt, 1, "AttestationPolicy").ToString();
66+
67+
// Policy is optionally double base64 URL encoded. We will attempt
68+
// to base64 URL decode a second time -- if this throws an exception,
69+
// that's OK -- we should just use value as it stands now.
70+
var doubleDecodedPolicy = Base64Url.DecodeString(parsedPolicy);
71+
parsedPolicy = doubleDecodedPolicy;
72+
}
73+
catch (Exception)
74+
{
75+
// Ignore on purpose
76+
}
77+
}
78+
79+
return parsedPolicy;
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)