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

Use RBAC support from DirectoryServices.Protocols for Role claim resolution on Linux for Negotiate #25075

Merged
merged 19 commits into from
Aug 25, 2020

Conversation

JunTaoLuo
Copy link
Contributor

@JunTaoLuo JunTaoLuo commented Aug 20, 2020

Fixes #12938.

@JunTaoLuo JunTaoLuo force-pushed the johluo/linuxrbac-api branch from ec46a4e to 8bc6eab Compare August 20, 2020 16:06
@JunTaoLuo JunTaoLuo force-pushed the johluo/linuxrbac-api branch from 8c9f2a4 to e38ee28 Compare August 21, 2020 21:23
@JunTaoLuo JunTaoLuo marked this pull request as ready for review August 23, 2020 23:41
@JunTaoLuo JunTaoLuo requested a review from dougbu as a code owner August 23, 2020 23:41
@JunTaoLuo JunTaoLuo added the api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews label Aug 23, 2020
@ghost
Copy link

ghost commented Aug 23, 2020

Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:

  • The PR contains changes to the reference-assembly that describe the API change. Or, you have included a snippet of reference-assembly-style code that illustrates the API change.
  • The PR describes the impact to users, both positive (useful new APIs) and negative (breaking changes).
  • Someone is assigned to "champion" this change in the meeting, and they understand the impact and design of the change.

@JunTaoLuo JunTaoLuo added the area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer label Aug 23, 2020
@JunTaoLuo JunTaoLuo added this to the 5.0.0-rc1 milestone Aug 23, 2020
@JunTaoLuo JunTaoLuo self-assigned this Aug 23, 2020
@JunTaoLuo
Copy link
Contributor Author

JunTaoLuo commented Aug 24, 2020

New APIs on existing types:

Microsoft.AspNetCore.Authentication.Negotiate.NegotiateEvents:

    /// <summary>
    /// Specifies events which the <see cref="NegotiateHandler"/> invokes to enable developer control over the authentication process.
    /// </summary>
    public class NegotiateEvents
    {
        /// <summary>
        /// Invoked after the authentication before ClaimsIdentity is populated with claims retrieved through the LDAP connection.
        /// This event is invoked when <see cref="LdapOptions.EnableLdapRoleClaimResolution"/> is set to true on <see cref="LdapOptions"/>.
        /// </summary>
        public Func<AuthenticatedContext, Task> OnRetrieveLdapClaims { get; set; } = context => Task.CompletedTask;

        /// <summary>
        /// Invoked after the authentication before ClaimsIdentity is populated with claims retrieved through the LDAP connection.
        /// </summary>
        public virtual Task RetrieveLdapClaims(AuthenticatedContext context) => OnRetrieveLdapClaims(context);
    }

Microsoft.AspNetCore.Authentication.Negotiate.NegotiateOptions:

        /// <summary>
        /// Configuration settings for LDAP connections used to retrieve Role claims.
        /// This is only used on Linux systems.
        /// </summary>
        public LdapOptions LdapOptions { get; } = new LdapOptions();

        /// <summary>
        /// Checks that the options are valid for a specific scheme
        /// </summary>
        /// <param name="scheme">The scheme being validated.</param>
        public override void Validate(string scheme)
        {
            Validate();
        }

        /// <summary>
        /// Check that the options are valid.  Should throw an exception if things are not ok.
        /// </summary>
        public override void Validate()
        {
            base.Validate();
            LdapOptions.Validate();
        }

New types:
Microsoft.AspNetCore.Authentication.Negotiate.LdapOptions

    /// <summary>
    /// Options class for configuring LDAP connections on Linux
    /// </summary>
    public class LdapOptions
    {
        /// <summary>
        /// Configure whether LDAP connection should be used to resolve role claims.
        /// This is mainly used on Linux.
        /// </summary>
        public bool EnableLdapRoleClaimResolution { get; set; }

        /// <summary>
        /// The domain to use for the LDAP connection. This is a mandatory setting.
        /// </summary>
        /// <example>
        /// DOMAIN.com
        /// </example>
        public string Domain { get; set; }

        /// <summary>
        /// The machine account name to use when opening the LDAP connection.
        /// If this is not provided, the machine wide credentials of the
        /// domain joined machine will be used.
        /// </summary>
        public string MachineAccountName { get; set; }

        /// <summary>
        /// The machine account password to use when opening the LDAP connection.
        /// This must be provided if a <see cref="MachineAccountName"/> is provided.
        /// </summary>
        public string MachineAccountPassword { get; set; }

        /// <summary>
        /// This option indicates whether nested groups should be examined when
        /// resolving Roles. The default is true.
        /// </summary>
        public bool ResolveNestedGroups { get; set; } = true;

        /// <summary>
        /// The <see cref="LdapConnection"/> to be used to retrieve role claims.
        /// If no explicit connection is provided, an LDAP connection will be
        /// automatically created based on the <see cref="Domain"/>,
        /// <see cref="MachineAccountName"/> and <see cref="MachineAccountPassword"/>
        /// options. If provided, this connection will be used and the
        /// <see cref="Domain"/>, <see cref="MachineAccountName"/> and
        /// <see cref="MachineAccountPassword"/>  options will not be used to create
        /// the <see cref="LdapConnection"/>.
        /// </summary>
        public LdapConnection LdapConnection { get; set; }

        public void Validate()
        {
            if (EnableLdapRoleClaimResolution)
            {
                if (string.IsNullOrEmpty(Domain))
                {
                    throw new ArgumentException($"{nameof(EnableLdapRoleClaimResolution)} is set to true but {nameof(Domain)} is not set.");
                }

                if (string.IsNullOrEmpty(MachineAccountName) && !string.IsNullOrEmpty(MachineAccountPassword))
                {
                    throw new ArgumentException($"{nameof(MachineAccountPassword)} should only be specified when {nameof(MachineAccountName)} is configured.");
                }
            }
        }
    }

Usage:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
                .AddNegotiate(options =>
                {
                    
                    var ldapOptions = options.LdapOptions;

                    // Mandatory settings
                    ldapOptions.EnableLdapRoleClaimResolution = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
                    ldapOptions.Domain = "DOMAIN.com";

                    // Optional settings
                    ldapOptions.MachineAccountName = "machineName";
                    ldapOptions.MachineAccountPassword = "PassW0rd";
                    ldapOptions.ResolveNestedGroups = true;

                    var di = new LdapDirectoryIdentifier(server: "DOMAIN.com", fullyQualifiedDnsHostName: true, connectionless: false);
                    var credentials = new NetworkCredential("[email protected]", "PassW0rd");
                    ldapOptions.LdapConnection = new LdapConnection(di, credentials);
                });
        }

@Tratcher Tratcher self-requested a review August 24, 2020 17:17
@pranavkm
Copy link
Contributor

pranavkm commented Aug 24, 2020

- public bool ResolveNestedGroups { get; set; } = true;
+ public bool SuppressResolveNestedGroups { get; set; }

- public LdapOptions LdapOptions { get; } = new LdapOptions();
+ internal LdapSettings LdapSettings { get; } = new LdapSettings();

@pranavkm pranavkm added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Aug 24, 2020
@pranavkm
Copy link
Contributor

NegotiateOptions.EnableLdap(string domainName);
NegotiateOptions.EnableLdap(Action<LdapSettings> ldapSettings);

.AddNegotiate(options =>
{
  options.EnableLdap("mydomain");
//  OR
 options.EnableLdap(ldapSettings => ...);
});

@JunTaoLuo
Copy link
Contributor Author

I'm going to add a few unit tests for validation, etc.

@JunTaoLuo
Copy link
Contributor Author

It's tricky to test anything that actually uses uses an LdapConnection since it's not possible to mock. I'm going to leave the tests as is.

Fix a unit test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-approved API was approved in API review, it can be implemented area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants