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

Optional and Configurable LDAP User/Session Management Support and Reworked Pluggable Auth Driver Interface #9750

Merged
merged 52 commits into from
Nov 3, 2023

Conversation

CL-Andrew
Copy link
Collaborator

@CL-Andrew CL-Andrew commented Jul 8, 2023

Overview

This PR expands on the core authentication implementation by abstracting user management calls to a new interface, allowing for plugable authentication drivers.

In addition, this introduces a new 'LDAP' authentication method that allows user sessions to be created utilizing an upstream remote identity provider, authenticating and authorizing users based on the proxied login credentials. This allows for user management of the multiple instances of the Core Node to be handled in one place, and a low friction way of changing and managing roles. Removing or adding any role of user for node management when using the ldap authentication module removes the need to connect to and update each running instance individually.

Key Changes

App SessionORM is Now Interface

Previously, the top level application struct stored an initialized sessions/orm where functions defined in the original type ORM interface { were made directly in various places throughout the codebase (router controllers, graphql, shell.go)
This has since been updated such that sessions contains folders of modules that implement a new "sessions.AuthenticationProvider interface". This new interface is the same shape as the previous one, with a one new TestPassword method. Tests and app initialization call sites have been updated to conditionally create the auth driver based on a new constant string type 'AuthenticationProviderName'

New Config Section

The WebServer config block now has a new field to define which authentication system should be used for user sessions and management. AuthenticationMethod = 'local' also defaults to local (the existing local users and sessions table implementation).

When AuthenticationMethod = 'local' is set to LDAP, the new optional [WebServer.LDAP] block of config is scanned.
This is forward compatible as the field defaults to 'local', which when set does not require LDAP fields to be present and defined. Config fields are expanded on below.

New DB Tables

In order to provide a more robust, low friction user experience, two new tables were created as part of the LDAP user management implementation to handle and store short lived sessions. Originally the implementation maintained a validated login session in the memory store, and validated the user against the upstream identity service every request. This introduced high cost latency to every single request made through the Operator UI. As many identity providers have defined rate limiting, this approach was implemented to support configurable local sessions as well.
The two new tables are specific to the LDAP implementation, storing the cached state of the authenticated user's email and role. One table is for API sessions, and the other for API token support.

The idea here is that the session lifespan and background state sync config fields can be dialed to the level of risk an operator chooses to assume. As sessions are initially created and authenticated through the LDAP server, saved and active sessions will not be purged immediately if that user is removed , however there is also a configurable new background sync that checks the state of the upstream server on a user defined interval, see below.

LDAP Module

The bulk of the change is all of the functionality implemented in core/sessions/ldapauth/sync.go and core/sessions/ldapauth/sync.go

The first implements the functionality for all functions in the new AuthenticationProvider interface. As the identity provider's role is to securely handle user management, the implementation assumes the upstream LDAP server credentials are read only. Actions mutating the user such as creation and deletion are not supported, and stubbed with an error message stating such. The auth functionality is abstracted form the router controllers, the login session request is propagated through and handled by the LDAP module's CreateSession function. This makes use of the LDAP bind methodology, sending forward the user provided credentials the backend. This is a blocking call, so the upstream LDAP provider can synchronously block the request while waiting for the user to respond to a 2FA push notification for example (sufficient request time out should accommodate for a blocking auth call for 2FA).

Upon receiving a successful response for achieving an LDAP bind with the user credentials, the user email is then used to query LDAP group membership for the configured group names. There are currently four supported roles within the core node, and this update adds new config fields to allow parameterization of these group names. For example, groups should be created in the LDAP server to map to Core Node Admins, Editors, Runners, and Viewers.
The module also supports optionally asserting the user's 'Is Active' property (which is implemented as a further upstream query call) as some LDAP providers retain user groups even after the user is deactivated. All of these fields are parameterized via the new WebServer LDAP TOML config.

While full session is support with user authentication handled by a separate, remote service, local admin features are supported as well by the LDAP module directly. This is important because the core node still needs to be able to be managed locally, and if there are any issues with the config or upstream server the node should still be accessible. The local users table is still taken into consideration for calls such as ListUser, FindUser, and when logged in as a local admin change password is supported this account.

Lastly, the sync module provides the implementation for reaping both expired sessions and API tokens, in addition to pulling and fully syncing all saved local state (in the session and API token LDAP DB Tables) against the upstream server. This can be defined to run on a user defined interval, but also runs when any auth related event happens. Whenever a user logs in or out, the Work function is invoked, which first checks the optional rate limit for last synced time. If enough time has elapsed, a full query of all active local sessions and API tokens is performed against he LDAP server to assert LDAP group membership to check roles, if the user is still active (optional), and if the user belongs to any group at all anymore. The state is resolved by purging users if not present upstream, or updating their roles if they were removed from certain groups and added to others.
Rate limiting is implemented and configurable for the sync service to address metered API calls. The background task implementation is through a recursive time.AfterFunc call, which delays the goroutine cost.

Documentation and Use

To provision a node to use LDAP as an identity providers, after creating the four required LDAP groups within the LDAP server, simply populate the following TOML config and secret values:

config.toml:


[WebServer]
AuthenticationMethod = 'ldap' # local
...

[WebServer.LDAP]
ServerTLS = true
SessionTimeout = '50m0s'
QueryTimeout = '2m0s'
BaseUserAttr = 'uid'
BaseDN = 'dc=ldapprovider,dc=example,dc=com'
UsersDN = 'ou=users'
GroupsDN = 'ou=groups'
ActiveAttribute = ''
ActiveAttributeAllowedValue = ''
AdminUserGroupCN = 'Core Node ADMINS'
EditUserGroupCN = 'Core Node EDITORS'
RunUserGroupCN = 'Core Node RUNNERS'
ReadUserGroupCN = 'Core Node VIEWERS'
UserApiTokenEnabled = true
UserAPITokenDuration = '240h' 
UpstreamSyncInterval = '0s'
UpstreamSyncRateLimit = '5s' # '2m0s'

secrets.toml:

[WebServer.LDAP]
ServerAddress = "ldaps://provider.example.com.com:636"
ReadOnlyUserLogin = "[email protected]"
ReadOnlyUserPass = "root-readonly-pass"

Replace the ou users and groups values depending on the LDAP server, and populate the BaseDN. This additional filter criteria is applied on all queries performed by the core node.

Adding a user to the identity provider and assigning them the relevant groups will be effective immediately as the check is performed on login.

Note: User changes in the upstream LDAP server such as user removal or role change do not immediately update any active sessions. The background LDAP sync module will pull the latest state and update all active sessions and API tokens when discrepancies are found. The interval at which this sync runs can be configured through the UpstreamSyncInterval toml config. A short sessions duration and fast sync interval time can be set if prolonged session access after user removal is seen as a high and sensitive risk. The sessions in these tables (ldap_sessions and ldap_user_api_tokens).

@CL-Andrew CL-Andrew requested a review from a team as a code owner July 8, 2023 11:46
@CL-Andrew CL-Andrew self-assigned this Jul 8, 2023
@github-actions
Copy link
Contributor

github-actions bot commented Jul 8, 2023

I see that you haven't updated any CHANGELOG files. Would it make sense to do so?

@cl-sonarqube-production
Copy link

SonarQube Quality Gate

Quality Gate failed

Failed condition C Reliability Rating on New Code (is worse than A)
Failed condition 9.5% 9.5% Coverage on New Code (is less than 80%)
Failed condition 4.4% 4.45% Duplicated Lines (%) on New Code (is greater than 3%)
Failed condition 15 New Major Issues (is greater than 5)

See analysis details on SonarQube

Fix issues before they fail your Quality Gate with SonarLint SonarLint in your IDE.

core/cmd/shell.go Outdated Show resolved Hide resolved
core/cmd/shell.go Outdated Show resolved Hide resolved
Fix config sesion timeout interface naming (r -> l)

Typos fix in ldap.go docstring

Rework logic in checkErr for FindUser logic of testing admin table query before failing (rework to avoid error shadowing)

Invert logic for err != nil in case for local admin user found

Fix missing errors.Is comparison for sessions.ErrUserSessionExpired in ldap module

Run docs generate

Fix typo in txtar test LDAP config
…rrors in ldap.go and sync.go

Run go mod tidy
… wrapper interfaces and mocks

New LDAPClient and LDAPConn interfaces allow test mocks to handle Bind and Search functionality. the ldap implementation has been updated to store the ldapClient (still ephemeral single use, like a factory) in the struct such that the test harness can swap the implementation with the mocks.

Create helpers_test.go following codebase convention to allow a Setter method to be defined for the ldapClient field, but separated from the production build. This allows the ldap struct and field properties to remain unexported. Test helper contains test mock configand helper constructor function

New ldap_test.go module with cases for ldap query functionality and local admin support assertions

ldap.go module improvements, return struct in constructor instead of interface type for authentication provider, define user facing error consts (test assertion), store ldapClient in struct, nicer error handling for user not found in FindUser, fix err shadowing reuse error in token expired case, fix  typos in ListUsers

LDAP Sync rework for new ldapclient field, use new interface to support mocking

Remove dangling commited localauth orm.mock, which was being imported by a missed test. Test now imports the correct mock (new authentication provider mocks)
…support godoc render

Remove redundant LDAP prefix for UniqueMemberAttribute
…[WebServer].AuthenticationMethod in changelog
@javuto
Copy link
Contributor

javuto commented Nov 3, 2023

Screenshot 2023-11-03 at 18 24 25

…onnection as it is required for the sync functionality, flip return flow in TestPassword for admin fallback
core/config/toml/types.go Outdated Show resolved Hide resolved
core/config/toml/types.go Outdated Show resolved Hide resolved
core/config/toml/types.go Show resolved Hide resolved
core/config/web_config.go Show resolved Hide resolved
core/config/docs/core.toml Show resolved Hide resolved
core/sessions/ldapauth/ldap.go Show resolved Hide resolved
core/sessions/ldapauth/ldap.go Show resolved Hide resolved
core/sessions/ldapauth/ldap.go Show resolved Hide resolved
core/sessions/ldapauth/sync.go Show resolved Hide resolved
core/sessions/localauth/orm.go Show resolved Hide resolved
LocalAdminUsersORM -> BasicAdminUsersORM, regenerate mocks

Save indent in WebServer ValidateConfig when ldapauth

Update comments and rename CreateEphemeralClient -> CreateEphemeralConnection
@cl-sonarqube-production
Copy link

SonarQube Quality Gate

Quality Gate failed

Failed condition 45.3% 45.3% Coverage on New Code (is less than 75%)

See analysis details on SonarQube

@CL-Andrew CL-Andrew added this pull request to the merge queue Nov 3, 2023
Merged via the queue into develop with commit cea3e6e Nov 3, 2023
83 of 84 checks passed
@CL-Andrew CL-Andrew deleted the ldap-support branch November 3, 2023 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants