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

[Key Vault] Add CAE support #46013

Merged
merged 30 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
71190d0
Add flag and enable CAE to AuthorizeRequestInternal
JonathanCrd Sep 17, 2024
36643e2
Enable CAE for AuthorizeRequestOnChallenge
JonathanCrd Sep 17, 2024
02a805d
Add flag in SecretClientOption and SecretClient
JonathanCrd Sep 17, 2024
8bd442a
Revert "Add flag in SecretClientOption and SecretClient"
JonathanCrd Sep 17, 2024
c6a65da
Enable CAE by default
JonathanCrd Sep 17, 2024
8b4e44c
Removing unused parameter
JonathanCrd Sep 17, 2024
a8772d5
Remove saving the claims in the cache
JonathanCrd Sep 17, 2024
ee0b2c6
Update Changelog
JonathanCrd Sep 17, 2024
f645368
Update changelogs
JonathanCrd Sep 18, 2024
1a74490
Simplify error checking logic
JonathanCrd Sep 18, 2024
5df4002
Add test for base64 claims
JonathanCrd Sep 23, 2024
2451396
Override Process function to handle the first CAE Challenge after a s…
JonathanCrd Sep 25, 2024
1f9a73c
Add tests
JonathanCrd Sep 25, 2024
daa03ef
Separate credential and client transports and assert for a 401.
JonathanCrd Sep 25, 2024
f454e4d
Nest rety inside challenge if block
JonathanCrd Sep 27, 2024
5b09202
Merge remote-tracking branch 'upstream/main' into Enable-CAE-for-KeyV…
JonathanCrd Sep 30, 2024
de6d54d
Add test for claims in token
JonathanCrd Oct 1, 2024
72d98ef
Fix CI by removing extra test case parameter
JonathanCrd Oct 1, 2024
46909fe
Nit changes to tests
JonathanCrd Oct 3, 2024
15a5ab7
Simplify tests
JonathanCrd Oct 3, 2024
ee196ec
removing unnecessary mock responses
JonathanCrd Oct 3, 2024
0c33973
Refactor tests to test CAE in all projects
JonathanCrd Oct 7, 2024
a0de67f
Make tests non parallelizable
JonathanCrd Oct 7, 2024
0ffee52
Add setup method to CAE tests
JonathanCrd Oct 8, 2024
f03fe3b
Test for tokens obtained from cae challenges
JonathanCrd Oct 10, 2024
a9657ef
Merge remote-tracking branch 'upstream/main' into Enable-CAE-for-KeyV…
JonathanCrd Oct 10, 2024
8bd6cfc
Fix test / CI
JonathanCrd Oct 10, 2024
d3e535d
Merge remote-tracking branch 'upstream/main' into Enable-CAE-for-KeyV…
JonathanCrd Oct 10, 2024
8544ff6
Update dependency for System.ClientModel
JonathanCrd Oct 10, 2024
6ac9c91
Apply suggestions
JonathanCrd Oct 10, 2024
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
2 changes: 1 addition & 1 deletion eng/Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@
<PackageReference Update="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
<PackageReference Update="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Update="PublicApiGenerator" Version="10.0.1" />
<PackageReference Update="System.ClientModel" Version="1.2.0" />
<PackageReference Update="System.ClientModel" Version="1.2.1" />
<PackageReference Update="System.Diagnostics.TraceSource" Version="4.3.0" />
<PackageReference Update="System.IO.Compression" Version="4.3.0" />
<PackageReference Update="System.IO.Pipelines" Version="4.5.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

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

### Breaking Changes

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core.TestFramework;
using Azure.Security.KeyVault.Tests;
using NUnit.Framework;

namespace Azure.Security.KeyVault.Administration.Tests
{
[NonParallelizable]
internal class ContinuousAccessEvaluationTests : ContinuousAccessEvaluationTestsBase
{
[SetUp]
public void Setup()
{
ChallengeBasedAuthenticationPolicy.ClearCache();
}

[Test]
[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"}}}""")]
public async Task VerifyCaeClaims(string challenge, string expectedClaims)
{
int callCount = 0;

MockResponse response = new MockResponse(200);

MockTransport transport = GetMockTransportWithCaeChallenges(numberOfCaeChallenges: 1, final200response: response);

var credential = new TokenCredentialStub((r, c) =>
{
if (callCount == 0)
{
// The first challenge should not have any claims.
Assert.IsNull(r.Claims);
}
else if (callCount == 1)
{
Assert.AreEqual(expectedClaims, r.Claims);
}
JonathanCrd marked this conversation as resolved.
Show resolved Hide resolved
Interlocked.Increment(ref callCount);
Assert.AreEqual(true, r.IsCaeEnabled);

return new(callCount.ToString(), DateTimeOffset.Now.AddHours(2));
JonathanCrd marked this conversation as resolved.
Show resolved Hide resolved
}, true);

KeyVaultBackupClient client = new(
VaultUri,
credential,
new KeyVaultAdministrationClientOptions()
{
Transport = transport,
});

try
{
KeyVaultBackupOperation operation = await client.StartBackupAsync(VaultUri);
}
catch (RequestFailedException ex)
{
Assert.AreEqual(200, ex.Status);
JonathanCrd marked this conversation as resolved.
Show resolved Hide resolved
return;
}
catch (Exception ex)
{
Assert.Fail($"Expected RequestFailedException, but got {ex.GetType()}");
return;
}
}

[Test]
public void ThrowsWithTwoConsecutiveCaeChallenges()
{
MockTransport keyVaultTransport = GetMockTransportWithCaeChallenges(numberOfCaeChallenges: 2);

MockTransport credentialTransport = GetMockCredentialTransport(2);

KeyVaultBackupClient client = new(
VaultUri,
new MockCredential(credentialTransport),
new KeyVaultAdministrationClientOptions()
{
Transport = keyVaultTransport,
});

try
{
var operation = client.StartBackup(VaultUri);
}
catch (RequestFailedException ex)
{
Assert.AreEqual(401, ex.Status);
return;
}
catch (Exception ex)
{
Assert.Fail($"Expected RequestFailedException, but got {ex.GetType()}");
return;
}
Assert.Fail("Expected RequestFailedException, but no exception was thrown.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 4.7.0-beta.1 (Unreleased)

### Features Added
- Support for Continuous Access Evaluation (CAE).

### Breaking Changes

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core.TestFramework;
using Azure.Security.KeyVault.Tests;
using NUnit.Framework;

namespace Azure.Security.KeyVault.Certificates.Tests
{
[NonParallelizable]
internal class ContinuousAccessEvaluationTests : ContinuousAccessEvaluationTestsBase
{
[SetUp]
public void Setup()
{
ChallengeBasedAuthenticationPolicy.ClearCache();
}

[Test]
[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"}}}""")]
public async Task VerifyCaeClaims(string challenge, string expectedClaims)
{
int callCount = 0;

MockResponse responseWithSecret = new MockResponse(200)
.WithContent(@"{
""id"": ""https://foo.vault.azure.net/certificates/1/foo"",
""cer"": ""Zm9v"",
""attributes"": {
},
""pending"": {
""id"": ""foo""
}
}");

MockTransport transport = GetMockTransportWithCaeChallenges(numberOfCaeChallenges: 1, final200response: responseWithSecret);

var credential = new TokenCredentialStub((r, c) =>
{
if (callCount == 0)
{
// The first challenge should not have any claims.
Assert.IsNull(r.Claims);
}
else if (callCount == 1)
{
Assert.AreEqual(expectedClaims, r.Claims);
}
JonathanCrd marked this conversation as resolved.
Show resolved Hide resolved
Interlocked.Increment(ref callCount);
Assert.AreEqual(true, r.IsCaeEnabled);

return new(callCount.ToString(), DateTimeOffset.Now.AddHours(2));
}, true);

CertificateClient client = new(
VaultUri,
credential,
new CertificateClientOptions()
{
Transport = transport,
});

Response<KeyVaultCertificateWithPolicy> response = await client.GetCertificateAsync("certificate");
Assert.AreEqual(200, response.GetRawResponse().Status);
}

[Test]
public void ThrowsWithTwoConsecutiveCaeChallenges()
{
MockTransport keyVaultTransport = GetMockTransportWithCaeChallenges(numberOfCaeChallenges: 2);

MockTransport credentialTransport = GetMockCredentialTransport(2);

CertificateClient client = new(
VaultUri,
new MockCredential(credentialTransport),
new CertificateClientOptions()
{
Transport = keyVaultTransport,
});

try
{
client.GetCertificate("certificate");
}
catch (RequestFailedException ex)
{
Assert.AreEqual(401, ex.Status);
return;
}
catch (Exception ex)
{
Assert.Fail($"Expected RequestFailedException, but got {ex.GetType()}");
return;
}
Assert.Fail("Expected RequestFailedException, but no exception was thrown.");
}
}
}
1 change: 1 addition & 0 deletions sdk/keyvault/Azure.Security.KeyVault.Keys/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 4.7.0-beta.1 (Unreleased)

### Features Added
- Support for Continuous Access Evaluation (CAE).

### Breaking Changes

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core.TestFramework;
using Azure.Security.KeyVault.Tests;
using NUnit.Framework;

namespace Azure.Security.KeyVault.Keys.Tests
{
[NonParallelizable]
internal class ContinuousAccessEvaluationTests : ContinuousAccessEvaluationTestsBase
{
[SetUp]
public void Setup()
{
ChallengeBasedAuthenticationPolicy.ClearCache();
}

[Test]
[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"}}}""")]
public async Task VerifyCaeClaims(string challenge, string expectedClaims)
{
int callCount = 0;

MockResponse responseWithKey = new MockResponse(200)
.WithContent(@"{
""key"": {
""kid"": ""https://heathskeyvault.vault.azure.net/keys/625710934/ef3685592e1c4e839206aaa10f0f058e"",
""kty"": ""RSA"",
""key_ops"": [
""encrypt"",
""decrypt"",
""sign"",
""verify"",
""wrapKey"",
""unwrapKey""
],
""n"": ""foo"",
""e"": ""AQAB""
},
""attributes"": {
""enabled"": true,
""created"": 1613807137,
""updated"": 1613807137,
""recoveryLevel"": ""Recoverable\u002BPurgeable"",
""recoverableDays"": 90
}
}");

MockTransport transport = GetMockTransportWithCaeChallenges(numberOfCaeChallenges: 1, final200response: responseWithKey);

var credential = new TokenCredentialStub((r, c) =>
{
if (callCount == 0)
{
// The first challenge should not have any claims.
Assert.IsNull(r.Claims);
}
else if (callCount == 1)
{
Assert.AreEqual(expectedClaims, r.Claims);
}
Interlocked.Increment(ref callCount);
Assert.AreEqual(true, r.IsCaeEnabled);

return new(callCount.ToString(), DateTimeOffset.Now.AddHours(2));
}, true);

KeyClient client = new(
VaultUri,
credential,
new KeyClientOptions()
{
Transport = transport,
});

Response<KeyVaultKey> response = await client.GetKeyAsync("key");
Assert.AreEqual(200, response.GetRawResponse().Status);
}

[Test]
public void ThrowsWithTwoConsecutiveCaeChallenges()
{
MockTransport keyVaultTransport = GetMockTransportWithCaeChallenges(numberOfCaeChallenges: 2);

MockTransport credentialTransport = GetMockCredentialTransport(2);

KeyClient client = new(
VaultUri,
new MockCredential(credentialTransport),
new KeyClientOptions()
{
Transport = keyVaultTransport,
});

try
{
client.GetKey("key");
}
catch (RequestFailedException ex)
{
Assert.AreEqual(401, ex.Status);
return;
}
catch (Exception ex)
{
Assert.Fail($"Expected RequestFailedException, but got {ex.GetType()}");
return;
}
Assert.Fail("Expected RequestFailedException, but no exception was thrown.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 4.7.0-beta.1 (Unreleased)

### Features Added
- Support for Continuous Access Evaluation (CAE).

### Breaking Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,18 @@ public async Task ReauthenticatesWhenTenantChanged()
Assert.AreEqual("secret-value", response.Value.Value);
}

[Test]
JonathanCrd marked this conversation as resolved.
Show resolved Hide resolved
public void GetClaimsFromChallengeHeaders()
{
MockResponse response401WithClaims = new MockResponse(401)
.WithHeader("WWW-Authenticate", @"Bearer realm="""", authorization_uri=""https://login.microsoftonline.com/common/oauth2/authorize"", error=""insufficient_claims"", claims=""eyJhY2Nlc3NfdG9rZW4iOnsiYWNycyI6eyJlc3NlbnRpYWwiOnRydWUsInZhbHVlIjoiY3AxIn19fQ==""");
Assert.AreEqual(ChallengeBasedAuthenticationPolicy.getDecodedClaimsParameter("insufficient_claims", response401WithClaims), @"{""access_token"":{""acrs"":{""essential"":true,""value"":""cp1""}}}");

MockResponse response401 = new MockResponse(401)
.WithHeader("WWW-Authenticate", @"Bearer authorization=""https://login.windows.net/de763a21-49f7-4b08-a8e1-52c8fbc103b4"", resource=""https://vault.azure.net""");
Assert.IsNull(ChallengeBasedAuthenticationPolicy.getDecodedClaimsParameter(null, response401));
}

private class MockTransportBuilder
{
private const string AuthorizationHeader = "Authorization";
Expand Down
Loading
Loading