Skip to content

Commit bb158f4

Browse files
authored
[Key Vault] Add CAE support & update System.ClientModel dependency version (Azure#46013)
* Add flag and enable CAE to AuthorizeRequestInternal * Enable CAE for AuthorizeRequestOnChallenge * Add flag in SecretClientOption and SecretClient * Revert "Add flag in SecretClientOption and SecretClient" This reverts commit 02a805d. * Enable CAE by default * Removing unused parameter * Remove saving the claims in the cache * Update Changelog * Update changelogs * Simplify error checking logic Addressing comment in PR * Add test for base64 claims * Override Process function to handle the first CAE Challenge after a scope challenge * Add tests * Separate credential and client transports and assert for a 401. * Nest rety inside challenge if block * Add test for claims in token * Fix CI by removing extra test case parameter * Nit changes to tests * Simplify tests * removing unnecessary mock responses * Refactor tests to test CAE in all projects * Make tests non parallelizable * Add setup method to CAE tests * Test for tokens obtained from cae challenges * Fix test / CI * Update dependency for System.ClientModel * Apply suggestions
1 parent 991f2e6 commit bb158f4

File tree

13 files changed

+795
-3
lines changed

13 files changed

+795
-3
lines changed

eng/Packages.Data.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@
354354
<PackageReference Update="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
355355
<PackageReference Update="Portable.BouncyCastle" Version="1.9.0" />
356356
<PackageReference Update="PublicApiGenerator" Version="10.0.1" />
357-
<PackageReference Update="System.ClientModel" Version="1.2.0" />
357+
<PackageReference Update="System.ClientModel" Version="1.2.1" />
358358
<PackageReference Update="System.Diagnostics.TraceSource" Version="4.3.0" />
359359
<PackageReference Update="System.IO.Compression" Version="4.3.0" />
360360
<PackageReference Update="System.IO.Pipelines" Version="4.5.1" />

sdk/keyvault/Azure.Security.KeyVault.Administration/CHANGELOG.md

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

77
- Added support for service API version `7.6-preview.1`.
88
- Added new methods `StartPreRestoreAsync`, `StartPreRestore`, `StartPreBackupAsync`, and `StartPreBackupAsync` to the `KeyVaultBackupClient`.
9+
- Support for Continuous Access Evaluation (CAE).
910

1011
### Breaking Changes
1112

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Azure.Core.TestFramework;
8+
using Azure.Security.KeyVault.Tests;
9+
using NUnit.Framework;
10+
11+
namespace Azure.Security.KeyVault.Administration.Tests
12+
{
13+
[NonParallelizable]
14+
internal class ContinuousAccessEvaluationTests : ContinuousAccessEvaluationTestsBase
15+
{
16+
[SetUp]
17+
public void Setup()
18+
{
19+
ChallengeBasedAuthenticationPolicy.ClearCache();
20+
}
21+
22+
[Test]
23+
[TestCase(@"Bearer realm="""", authorization_uri=""https://login.microsoftonline.com/common/oauth2/authorize"", error=""insufficient_claims"", claims=""eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwidmFsdWUiOiIxNzI2MDc3NTk1In0sInhtc19jYWVlcnJvciI6eyJ2YWx1ZSI6IjEwMDEyIn19fQ==""", """{"access_token":{"nbf":{"essential":true,"value":"1726077595"},"xms_caeerror":{"value":"10012"}}}""")]
24+
public async Task VerifyCaeClaims(string challenge, string expectedClaims)
25+
{
26+
int callCount = 0;
27+
28+
MockResponse response = new MockResponse(200);
29+
30+
MockTransport transport = GetMockTransportWithCaeChallenges(numberOfCaeChallenges: 1, final200response: response);
31+
32+
var credential = new TokenCredentialStub((r, c) =>
33+
{
34+
if (callCount == 0)
35+
{
36+
// The first challenge should not have any claims.
37+
Assert.IsNull(r.Claims);
38+
}
39+
else if (callCount == 1)
40+
{
41+
Assert.AreEqual(expectedClaims, r.Claims);
42+
}
43+
else
44+
{
45+
Assert.Fail("unexpected token request");
46+
}
47+
Interlocked.Increment(ref callCount);
48+
Assert.AreEqual(true, r.IsCaeEnabled);
49+
50+
return new(callCount.ToString(), DateTimeOffset.Now.AddHours(2));
51+
}, true);
52+
53+
KeyVaultBackupClient client = new(
54+
VaultUri,
55+
credential,
56+
new KeyVaultAdministrationClientOptions()
57+
{
58+
Transport = transport,
59+
});
60+
61+
try
62+
{
63+
KeyVaultBackupOperation operation = await client.StartBackupAsync(VaultUri);
64+
}
65+
catch (RequestFailedException ex)
66+
{
67+
Assert.AreEqual(200, ex.Status);
68+
return;
69+
}
70+
catch (Exception ex)
71+
{
72+
Assert.Fail($"Expected RequestFailedException, but got {ex.GetType()}");
73+
return;
74+
}
75+
}
76+
77+
[Test]
78+
public void ThrowsWithTwoConsecutiveCaeChallenges()
79+
{
80+
MockTransport keyVaultTransport = GetMockTransportWithCaeChallenges(numberOfCaeChallenges: 2);
81+
82+
MockTransport credentialTransport = GetMockCredentialTransport(2);
83+
84+
KeyVaultBackupClient client = new(
85+
VaultUri,
86+
new MockCredential(credentialTransport),
87+
new KeyVaultAdministrationClientOptions()
88+
{
89+
Transport = keyVaultTransport,
90+
});
91+
92+
try
93+
{
94+
var operation = client.StartBackup(VaultUri);
95+
}
96+
catch (RequestFailedException ex)
97+
{
98+
Assert.AreEqual(401, ex.Status);
99+
return;
100+
}
101+
catch (Exception ex)
102+
{
103+
Assert.Fail($"Expected RequestFailedException, but got {ex.GetType()}");
104+
return;
105+
}
106+
Assert.Fail("Expected RequestFailedException, but no exception was thrown.");
107+
}
108+
}
109+
}

sdk/keyvault/Azure.Security.KeyVault.Certificates/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 4.7.0-beta.1 (Unreleased)
44

55
### Features Added
6+
- Support for Continuous Access Evaluation (CAE).
67

78
### Breaking Changes
89

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Azure.Core.TestFramework;
8+
using Azure.Security.KeyVault.Tests;
9+
using NUnit.Framework;
10+
11+
namespace Azure.Security.KeyVault.Certificates.Tests
12+
{
13+
[NonParallelizable]
14+
internal class ContinuousAccessEvaluationTests : ContinuousAccessEvaluationTestsBase
15+
{
16+
[SetUp]
17+
public void Setup()
18+
{
19+
ChallengeBasedAuthenticationPolicy.ClearCache();
20+
}
21+
22+
[Test]
23+
[TestCase(@"Bearer realm="""", authorization_uri=""https://login.microsoftonline.com/common/oauth2/authorize"", error=""insufficient_claims"", claims=""eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwidmFsdWUiOiIxNzI2MDc3NTk1In0sInhtc19jYWVlcnJvciI6eyJ2YWx1ZSI6IjEwMDEyIn19fQ==""", """{"access_token":{"nbf":{"essential":true,"value":"1726077595"},"xms_caeerror":{"value":"10012"}}}""")]
24+
public async Task VerifyCaeClaims(string challenge, string expectedClaims)
25+
{
26+
int callCount = 0;
27+
28+
MockResponse responseWithSecret = new MockResponse(200)
29+
.WithContent(@"{
30+
""id"": ""https://foo.vault.azure.net/certificates/1/foo"",
31+
""cer"": ""Zm9v"",
32+
""attributes"": {
33+
},
34+
""pending"": {
35+
""id"": ""foo""
36+
}
37+
}");
38+
39+
MockTransport transport = GetMockTransportWithCaeChallenges(numberOfCaeChallenges: 1, final200response: responseWithSecret);
40+
41+
var credential = new TokenCredentialStub((r, c) =>
42+
{
43+
if (callCount == 0)
44+
{
45+
// The first challenge should not have any claims.
46+
Assert.IsNull(r.Claims);
47+
}
48+
else if (callCount == 1)
49+
{
50+
Assert.AreEqual(expectedClaims, r.Claims);
51+
}
52+
else
53+
{
54+
Assert.Fail("unexpected token request");
55+
}
56+
Interlocked.Increment(ref callCount);
57+
Assert.AreEqual(true, r.IsCaeEnabled);
58+
59+
return new(callCount.ToString(), DateTimeOffset.Now.AddHours(2));
60+
}, true);
61+
62+
CertificateClient client = new(
63+
VaultUri,
64+
credential,
65+
new CertificateClientOptions()
66+
{
67+
Transport = transport,
68+
});
69+
70+
Response<KeyVaultCertificateWithPolicy> response = await client.GetCertificateAsync("certificate");
71+
Assert.AreEqual(200, response.GetRawResponse().Status);
72+
}
73+
74+
[Test]
75+
public void ThrowsWithTwoConsecutiveCaeChallenges()
76+
{
77+
MockTransport keyVaultTransport = GetMockTransportWithCaeChallenges(numberOfCaeChallenges: 2);
78+
79+
MockTransport credentialTransport = GetMockCredentialTransport(2);
80+
81+
CertificateClient client = new(
82+
VaultUri,
83+
new MockCredential(credentialTransport),
84+
new CertificateClientOptions()
85+
{
86+
Transport = keyVaultTransport,
87+
});
88+
89+
try
90+
{
91+
client.GetCertificate("certificate");
92+
}
93+
catch (RequestFailedException ex)
94+
{
95+
Assert.AreEqual(401, ex.Status);
96+
return;
97+
}
98+
catch (Exception ex)
99+
{
100+
Assert.Fail($"Expected RequestFailedException, but got {ex.GetType()}");
101+
return;
102+
}
103+
Assert.Fail("Expected RequestFailedException, but no exception was thrown.");
104+
}
105+
}
106+
}

sdk/keyvault/Azure.Security.KeyVault.Keys/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 4.7.0-beta.1 (Unreleased)
44

55
### Features Added
6+
- Support for Continuous Access Evaluation (CAE).
67

78
### Breaking Changes
89

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Azure.Core.TestFramework;
8+
using Azure.Security.KeyVault.Tests;
9+
using NUnit.Framework;
10+
11+
namespace Azure.Security.KeyVault.Keys.Tests
12+
{
13+
[NonParallelizable]
14+
internal class ContinuousAccessEvaluationTests : ContinuousAccessEvaluationTestsBase
15+
{
16+
[SetUp]
17+
public void Setup()
18+
{
19+
ChallengeBasedAuthenticationPolicy.ClearCache();
20+
}
21+
22+
[Test]
23+
[TestCase(@"Bearer realm="""", authorization_uri=""https://login.microsoftonline.com/common/oauth2/authorize"", error=""insufficient_claims"", claims=""eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwidmFsdWUiOiIxNzI2MDc3NTk1In0sInhtc19jYWVlcnJvciI6eyJ2YWx1ZSI6IjEwMDEyIn19fQ==""", """{"access_token":{"nbf":{"essential":true,"value":"1726077595"},"xms_caeerror":{"value":"10012"}}}""")]
24+
public async Task VerifyCaeClaims(string challenge, string expectedClaims)
25+
{
26+
int callCount = 0;
27+
28+
MockResponse responseWithKey = new MockResponse(200)
29+
.WithContent(@"{
30+
""key"": {
31+
""kid"": ""https://heathskeyvault.vault.azure.net/keys/625710934/ef3685592e1c4e839206aaa10f0f058e"",
32+
""kty"": ""RSA"",
33+
""key_ops"": [
34+
""encrypt"",
35+
""decrypt"",
36+
""sign"",
37+
""verify"",
38+
""wrapKey"",
39+
""unwrapKey""
40+
],
41+
""n"": ""foo"",
42+
""e"": ""AQAB""
43+
},
44+
""attributes"": {
45+
""enabled"": true,
46+
""created"": 1613807137,
47+
""updated"": 1613807137,
48+
""recoveryLevel"": ""Recoverable\u002BPurgeable"",
49+
""recoverableDays"": 90
50+
}
51+
}");
52+
53+
MockTransport transport = GetMockTransportWithCaeChallenges(numberOfCaeChallenges: 1, final200response: responseWithKey);
54+
55+
var credential = new TokenCredentialStub((r, c) =>
56+
{
57+
if (callCount == 0)
58+
{
59+
// The first challenge should not have any claims.
60+
Assert.IsNull(r.Claims);
61+
}
62+
else if (callCount == 1)
63+
{
64+
Assert.AreEqual(expectedClaims, r.Claims);
65+
}
66+
else
67+
{
68+
Assert.Fail("unexpected token request");
69+
}
70+
Interlocked.Increment(ref callCount);
71+
Assert.AreEqual(true, r.IsCaeEnabled);
72+
73+
return new(callCount.ToString(), DateTimeOffset.Now.AddHours(2));
74+
}, true);
75+
76+
KeyClient client = new(
77+
VaultUri,
78+
credential,
79+
new KeyClientOptions()
80+
{
81+
Transport = transport,
82+
});
83+
84+
Response<KeyVaultKey> response = await client.GetKeyAsync("key");
85+
Assert.AreEqual(200, response.GetRawResponse().Status);
86+
}
87+
88+
[Test]
89+
public void ThrowsWithTwoConsecutiveCaeChallenges()
90+
{
91+
MockTransport keyVaultTransport = GetMockTransportWithCaeChallenges(numberOfCaeChallenges: 2);
92+
93+
MockTransport credentialTransport = GetMockCredentialTransport(2);
94+
95+
KeyClient client = new(
96+
VaultUri,
97+
new MockCredential(credentialTransport),
98+
new KeyClientOptions()
99+
{
100+
Transport = keyVaultTransport,
101+
});
102+
103+
try
104+
{
105+
client.GetKey("key");
106+
}
107+
catch (RequestFailedException ex)
108+
{
109+
Assert.AreEqual(401, ex.Status);
110+
return;
111+
}
112+
catch (Exception ex)
113+
{
114+
Assert.Fail($"Expected RequestFailedException, but got {ex.GetType()}");
115+
return;
116+
}
117+
Assert.Fail("Expected RequestFailedException, but no exception was thrown.");
118+
}
119+
}
120+
}

sdk/keyvault/Azure.Security.KeyVault.Secrets/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 4.7.0-beta.1 (Unreleased)
44

55
### Features Added
6+
- Support for Continuous Access Evaluation (CAE).
67

78
### Breaking Changes
89

sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/ChallengeBasedAuthenticationPolicyTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,18 @@ public async Task ReauthenticatesWhenTenantChanged()
199199
Assert.AreEqual("secret-value", response.Value.Value);
200200
}
201201

202+
[Test]
203+
public void GetClaimsFromChallengeHeaders()
204+
{
205+
MockResponse response401WithClaims = new MockResponse(401)
206+
.WithHeader("WWW-Authenticate", @"Bearer realm="""", authorization_uri=""https://login.microsoftonline.com/common/oauth2/authorize"", error=""insufficient_claims"", claims=""eyJhY2Nlc3NfdG9rZW4iOnsiYWNycyI6eyJlc3NlbnRpYWwiOnRydWUsInZhbHVlIjoiY3AxIn19fQ==""");
207+
Assert.AreEqual(ChallengeBasedAuthenticationPolicy.getDecodedClaimsParameter("insufficient_claims", response401WithClaims), @"{""access_token"":{""acrs"":{""essential"":true,""value"":""cp1""}}}");
208+
209+
MockResponse response401 = new MockResponse(401)
210+
.WithHeader("WWW-Authenticate", @"Bearer authorization=""https://login.windows.net/de763a21-49f7-4b08-a8e1-52c8fbc103b4"", resource=""https://vault.azure.net""");
211+
Assert.IsNull(ChallengeBasedAuthenticationPolicy.getDecodedClaimsParameter(null, response401));
212+
}
213+
202214
private class MockTransportBuilder
203215
{
204216
private const string AuthorizationHeader = "Authorization";

0 commit comments

Comments
 (0)