Skip to content

Commit

Permalink
ACL with Microsoft Entra token support (#378)
Browse files Browse the repository at this point in the history
* - Added option to allow AAD auth with ACL
- Currently ACL auth by default does username and password validation with ACL entries. We compose it with an IAuthenticator instance to inject authentication behavior of username and  password and then just validate the permissions against the ACL list of the user. This approach is more favorable as it minimizes changes and avoids redundant code needed to combine behaviors of AclAuthenticator and AADAuthenticator. Rather than inheriting these behavior, we compose AclAuthenticator with an IAuthenticator.

Testing:
There were no AAD tests. Have added a basic test to validate AAD + ACL and that it works with cluster auth.

* formatting fixes

* - Added AADValidateUsername flag
- Refactor to AclWithAad and AclWithPassword hierarchies
- fix naming convention for private member _validateUsername
- add comments and fix tests

* fomratting fixes

* fix logger parameter positioning

* fix parameter ordering for logger

* add default value

* add base type assertions

* add default value  - failing test due to this.

* - Fix comments to account for groupId
- Add support for groupId check and corresponding test

* Refactor code to separeate Authentication settings into seprate files and move test constructor for IssuerSigningTokenProvider to test code

---------

Co-authored-by: Lukas Maas <[email protected]>
  • Loading branch information
msft-paddy14 and lmaas authored May 21, 2024
1 parent 5a65fd7 commit 5c4041e
Show file tree
Hide file tree
Showing 22 changed files with 669 additions and 269 deletions.
10 changes: 8 additions & 2 deletions libs/host/Configuration/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
using System.Security.Cryptography.X509Certificates;
using CommandLine;
using Garnet.server;
using Garnet.server.Auth;
using Garnet.server.Auth.Aad;
using Garnet.server.Auth.Settings;
using Garnet.server.TLS;
using Microsoft.Extensions.Logging;
using Tsavorite.core;
Expand Down Expand Up @@ -167,6 +167,9 @@ internal sealed class Options
[Option("aad-authorized-app-ids", Required = false, Separator = ',', HelpText = "The authorized client app Ids for AAD authentication. Should be a comma separated string.")]
public string AuthorizedAadApplicationIds { get; set; }

[Option("aad-validate-acl-username", Required = false, Separator = ',', HelpText = "Only valid for AclWithAAD mode. Validates username - expected to be OID of client app or a valid group's object id of which the client is part of.")]
public bool? AadValidateUsername { get; set; }

[OptionValidation]
[Option("aof", Required = false, HelpText = "Enable write ahead logging (append-only file).")]
public bool? EnableAOF { get; set; }
Expand Down Expand Up @@ -623,7 +626,10 @@ private IAuthenticationSettings GetAuthenticationSettings(ILogger logger = null)
case GarnetAuthenticationMode.Aad:
return new AadAuthenticationSettings(AuthorizedAadApplicationIds?.Split(','), AadAudiences?.Split(','), AadIssuers?.Split(','), IssuerSigningTokenProvider.Create(AadAuthority, logger));
case GarnetAuthenticationMode.ACL:
return new AclAuthenticationSettings(AclFile, Password);
return new AclAuthenticationPasswordSettings(AclFile, Password);
case GarnetAuthenticationMode.AclWithAad:
var aadAuthSettings = new AadAuthenticationSettings(AuthorizedAadApplicationIds?.Split(','), AadAudiences?.Split(','), AadIssuers?.Split(','), IssuerSigningTokenProvider.Create(AadAuthority, logger), AadValidateUsername.GetValueOrDefault());
return new AclAuthenticationAadSettings(AclFile, Password, aadAuthSettings);
default:
logger?.LogError("Unsupported authentication mode: {mode}", AuthenticationMode);
throw new Exception($"Authentication mode {AuthenticationMode} is not supported.");
Expand Down
3 changes: 3 additions & 0 deletions libs/host/defaults.conf
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@
/* The authorized client app Ids for AAD authentication. Should be a comma separated string. */
"AuthorizedAadApplicationIds" : null,

/* Whether to validate username as ObjectId or a valid Group objectId if present in claims - meant to be used with ACL setup. */
"AadValidateUsername": false,

/* Enable write ahead logging (append-only file). */
"EnableAOF" : false,

Expand Down
10 changes: 7 additions & 3 deletions libs/server/Auth/Aad/IssuerSigningTokenProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,19 @@ public class IssuerSigningTokenProvider : IDisposable

private readonly ILogger _logger;

private IssuerSigningTokenProvider(string authority, IReadOnlyCollection<SecurityKey> signingTokens, ILogger logger)
protected IssuerSigningTokenProvider(string authority, IReadOnlyCollection<SecurityKey> signingTokens, bool refreshTokens = true, ILogger logger = null)
{
_authority = authority;
_refreshTimer = new Timer(RefreshSigningTokens, null, TimeSpan.Zero, TimeSpan.FromDays(1));
if (refreshTokens)
{
_refreshTimer = new Timer(RefreshSigningTokens, null, TimeSpan.Zero, TimeSpan.FromDays(1));
}
_signingTokens = signingTokens;

_logger = logger;
}


private void RefreshSigningTokens(object _)
{
try
Expand Down Expand Up @@ -103,7 +107,7 @@ public static IssuerSigningTokenProvider Create(string authority, ILogger logger
}

var signingTokens = RetrieveSigningTokens(authority);
return new IssuerSigningTokenProvider(authority, signingTokens, logger);
return new IssuerSigningTokenProvider(authority, signingTokens, refreshTokens: true, logger);
}
}
}
236 changes: 0 additions & 236 deletions libs/server/Auth/AuthenticationSettings.cs

This file was deleted.

19 changes: 9 additions & 10 deletions libs/server/Auth/GarnetACLAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@

namespace Garnet.server.Auth
{
class GarnetACLAuthenticator : IGarnetAuthenticator
abstract class GarnetACLAuthenticator : IGarnetAuthenticator
{
/// <summary>
/// The Access Control List to authenticate users against
/// </summary>
readonly AccessControlList _acl;
protected readonly AccessControlList _acl;

/// <summary>
/// Logger to use to output log messages to
/// </summary>
readonly ILogger _logger;
protected readonly ILogger _logger;

/// <summary>
/// If authenticated, contains a reference to the authenticated user. Otherwise null.
/// </summary>
User _user = null;
protected User _user = null;

/// <summary>
/// Initializes a new ACLAuthenticator instance.
Expand Down Expand Up @@ -65,14 +65,11 @@ public bool Authenticate(ReadOnlySpan<byte> password, ReadOnlySpan<byte> usernam
// Check if user exists and set default user if username is unspecified
string uname = Encoding.ASCII.GetString(username);
User user = string.IsNullOrEmpty(uname) ? _acl.GetDefaultUser() : _acl.GetUser(uname);

// Try to authenticate user
ACLPassword passwordHash = ACLPassword.ACLPasswordFromString(Encoding.ASCII.GetString(password));
if (user.IsEnabled && user.ValidatePassword(passwordHash))
if (user == null)
{
_user = user;
successful = true;
return false;
}
successful = AuthenticateInternal(user, username, password);
}
catch (Exception ex)
{
Expand All @@ -83,6 +80,8 @@ public bool Authenticate(ReadOnlySpan<byte> password, ReadOnlySpan<byte> usernam
return successful;
}

protected abstract bool AuthenticateInternal(User user, ReadOnlySpan<byte> username, ReadOnlySpan<byte> password);

/// <summary>
/// Returns the currently authorized user.
/// </summary>
Expand Down
Loading

0 comments on commit 5c4041e

Please sign in to comment.