Skip to content

Commit

Permalink
Optional and Configurable LDAP User/Session Management Support and Re…
Browse files Browse the repository at this point in the history
…worked Pluggable Auth Driver Interface (#9750)

* Initial commit of LDAP Auth driver support with toml config docs and parser driver, pluggable auth interface defined and localauth (default) moved to scoped module

* 'orm sessions.UserManager' to 'um sessions.UserManager'

* Add missing checks for the UserApiTokenEnabled config field for token related calls, rename ServerTls to ServerTLS

* Update docs toml LDAP section to clarify how the fields are used and specify LDAP terminology

* Clarify LDAP 'cn' in toml docs for LDAP

* Fix WebServer TOML and config definitions, split types for WebServerLDAPSecrets to following config and secret toml convention, improved error handling on startup for missing WebServer and LDAP fields

* Error application startup if authentication method is not one of the valid options, instead of defaulting to local

* Don't export unneeded ldapGroupMembersListToUser in ldap module

* Bugfixes for LDAP find user when no results of passed email, address the two ways local CLI can attempt auth using local client for LDAP implementation of createSession, moved ErrNotSupported up to authentication types level so router can expose to API response when type message

* Rework LDAP function to check if list of provided query emails possess the 'active' attribute/group in a single query. Now returns list of bools one to one for the passed in emails array and correctly handles the case for querying more than one email at a time, changing function return signature from just error

* Update LDAP field naming for Cn, Dn to Go convention CN, DN in toml and types, tidy god mod, fix path imports for test files, fix toml comments

* Post merge toml module rename LDAP model fixes

Populate test config and test secrets toml file with new LDAP config fields, use secrets parse type for LDAPSecrets interface

* Update top level application struct to accomodate new sibling AuthProvider field to preserve always available local admin auth

This commit splits the newly added UserManager interface (now renamed) into two interfaces where the existing local user ORM
auth provider covers the implementation for the required always available Admin commands. These are used when configuring the node
initially (creating the admin user) or assuming the admin role from the command line, which should work locally as well regardless
of the configured Authentication Provider.

Renamed new UserManager interface to AuthenticationProvider, which no longer has the boilerplate Admin prefix functions.

* Update all err comparison checks for ErrNotSupported to errors.Is

* go generate mocks

* Tidy unecessary string cast, bump ldap library to latest and use v3, test forward compatibility, mod tidy

* Clean up auth provider config switch statement

* Update checked in test txtar output

* Update gql test and mocks with new authprovider mock, updated in mock struct. Add missing TestPassword call

* Update remaining test config toml files with new WebServer LDAP fields, populated where test case makes sense, add toml config validation checks on parse for LDAP fields non empty, update LDAP Server field to Models Secret URL, add parsing test for secrets

* Update LDAP module with missing API token implementation - creation, use, and deletion

Bugfixes and logic improvements for LDAP session reaper/upstream sync. Reaper now correctly syncs roles and users from upstream via sleeper task tied to LDAP auth actions. User sessions and API tokens are correctly removed when the expire TTL is met, the local LDAP sessions and API tokens role is updated and synced with the state of the upstream LDAP server as part of the logic of this sleeper task, and if a user is no longer present in any of the defined groups they are automatically removed from the LDAP sessions and API tokens tables (checked on cadence of sleeper task Work call)

Update LDAP webconfigToken duration to match wrapped models.Duration type

Add const for LDAPUniqueMemberAttribute/uniqueMember in ldapauth module

Add info logger connection attempt message in case of hang on node startup

Fix expired LDAP api tokens purge issue

Nicer error for session missing / expired on attempt of sesion token use (remove error within stdout)

* Add missing support for local CLI user and auth when using LDAP Authentication module

Fixed edge cases for FindUser, check local users table as well, local user API Token creation and support

Add localauth_user flag for ldap specific tables to support node usage by the initial required local admin user, add logic in CreateSession

* Update config UpstreamSyncInterval and UpstreamSyncRateLimit functionality for LDAP Sync daemon

Implement .Work call on timer for LDAP sync in the background, independent of Auth related calls. The implementation of SleeperTask calls Work when hooked into Auth events, being called on login or logout. Now if UpstreamSyncInterval is defined as non 0, a background timer call will call the sync function, respecting the new UpstreamSyncRateLimit field

* LDAP Fix for checks of optional isactive property on group query, find user functionality, and admin functions

Bug fix for ldap driver not supporting local admin users case of change password and list users, FindUser functionality can now return matches of local admin users

List Users now includes local users and works as expected for upstream LDAP users who have any of the defined groups required for node access. Bugfix for group search query in both the sync and ldap providers modules. Factored out group query functionality for both call sites

Set Password support for only local admin users as functionality is still supported and required when using LDAP auth, upstream user modification remains unsupported

Bugfix for shell local initialization not using local admin auth ORM, causing issue with initial assume user step in ListUsers

* bump migration file index

* remove incorrect rebased merge resolution for Explorer removal

* Change default config definitions for LDAP 'Is Active' attribute checks to empty, as not all LDAP providers will use 'ActiveAttribute', or retain group member access when inactive. Fix error handling in find users for case when NoRows, dont log error automatically with Transaction middleware

* Simplify sessions purge sql exec using pq.Array instead of manually generating placeholders, and Regenerate mocks

* Merge go mod require groups, gotidy

* Rename changed authentication provider session ORM in test files, fix config test reordering, add missing mock, update const err strings

* Add mock value for one test case of config ldap is active attribute, revert purge sessions api token test file, migrate to new errors module and update how errors are wrapped, lowercase all error messages

* Factor out unsupported action error message in user controller with new errUnsupportedForAuth type

* Rebase, update migration index

* Update config_test full case, error case for missing fields

* Fix tests with missing Mocks for cmd shell, config resolver, and sessions localauth

Missing mocks for LocalAdminUsersORM

Revert change unrelated to LDAP feature in AuthorizedUserWithSession (refactored)

Fix leaked internal error over HTTP response + test case for delete user

Fix mocks and missing TestPassword call cases for graphql mutation tests, update incorrect password test case

Add expected LDAP config fields for config resolver tests

* Bump migration file index for ldap tables

* Linter fixes - application.go localAdminUsersORM in initialized one line

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

* Add missing ldap fields to warnings.txtar, fix err shadowing linter errors in ldap.go and sync.go

Run go mod tidy

* Fix linter import order and groupings

* More import ordering lint

* Rebase, bump sql migraiton index

* use correct guregu/null.v4 version

* Implement test cases for ldap module, create LDAP client and LDAPConn 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)

* Updated CHANGELOG.md

* Update go.mod

* Linter fixes UserNoLDAPGroups -> ErrUserNoLDAPGroups, shadowing

* module -> package, format package docstring properly for ldapauth to support godoc render

Remove redundant LDAP prefix for UniqueMemberAttribute

* Remove rebased gomod line

* define const NodeAdmins* for mocked tests in helpers_test, reference [WebServer].AuthenticationMethod in changelog

* Add missing returns in sync Work call when failed to establish LDAP connection as it is required for the sync functionality, flip return flow in TestPassword for admin fallback

* Updating naming and address nits

LocalAdminUsersORM -> BasicAdminUsersORM, regenerate mocks

Save indent in WebServer ValidateConfig when ldapauth

Update comments and rename CreateEphemeralClient -> CreateEphemeralConnection

* Add missed go generate Application change for BasicAdminUsersORM rename
  • Loading branch information
CL-Andrew authored Nov 3, 2023
1 parent 41eac0a commit cea3e6e
Show file tree
Hide file tree
Showing 78 changed files with 3,743 additions and 341 deletions.
6 changes: 3 additions & 3 deletions core/cmd/admin_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestShell_ChangeRole(t *testing.T) {
app := startNewApplicationV2(t, nil)
client, _ := app.NewShellAndRenderer()
user := cltest.MustRandomUser(t)
require.NoError(t, app.SessionORM().CreateUser(&user))
require.NoError(t, app.AuthenticationProvider().CreateUser(&user))

tests := []struct {
name string
Expand Down Expand Up @@ -101,7 +101,7 @@ func TestShell_DeleteUser(t *testing.T) {
app := startNewApplicationV2(t, nil)
client, _ := app.NewShellAndRenderer()
user := cltest.MustRandomUser(t)
require.NoError(t, app.SessionORM().CreateUser(&user))
require.NoError(t, app.BasicAdminUsersORM().CreateUser(&user))

tests := []struct {
name string
Expand Down Expand Up @@ -135,7 +135,7 @@ func TestShell_ListUsers(t *testing.T) {
app := startNewApplicationV2(t, nil)
client, _ := app.NewShellAndRenderer()
user := cltest.MustRandomUser(t)
require.NoError(t, app.SessionORM().CreateUser(&user))
require.NoError(t, app.AuthenticationProvider().CreateUser(&user))

set := flag.NewFlagSet("test", 0)
cltest.FlagSetApplyFromAction(client.ListUsers, set, "")
Expand Down
1 change: 1 addition & 0 deletions core/cmd/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ func Test_initServerConfig(t *testing.T) {
"../services/chainlink/testdata/mergingsecretsdata/secrets-mercury-split-one.toml",
"../services/chainlink/testdata/mergingsecretsdata/secrets-mercury-split-two.toml",
"../services/chainlink/testdata/mergingsecretsdata/secrets-threshold.toml",
"../services/chainlink/testdata/mergingsecretsdata/secrets-webserver-ldap.toml",
},
},
wantErr: false,
Expand Down
12 changes: 6 additions & 6 deletions core/cmd/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -776,8 +776,8 @@ func (f *fileSessionRequestBuilder) Build(file string) (sessions.SessionRequest,
// APIInitializer is the interface used to create the API User credentials
// needed to access the API. Does nothing if API user already exists.
type APIInitializer interface {
// Initialize creates a new user for API access, or does nothing if one exists.
Initialize(orm sessions.ORM, lggr logger.Logger) (sessions.User, error)
// Initialize creates a new local Admin user for API access, or does nothing if one exists.
Initialize(orm sessions.BasicAdminUsersORM, lggr logger.Logger) (sessions.User, error)
}

type promptingAPIInitializer struct {
Expand All @@ -791,11 +791,11 @@ func NewPromptingAPIInitializer(prompter Prompter) APIInitializer {
}

// Initialize uses the terminal to get credentials that it then saves in the store.
func (t *promptingAPIInitializer) Initialize(orm sessions.ORM, lggr logger.Logger) (sessions.User, error) {
func (t *promptingAPIInitializer) Initialize(orm sessions.BasicAdminUsersORM, lggr logger.Logger) (sessions.User, error) {
// Load list of users to determine which to assume, or if a user needs to be created
dbUsers, err := orm.ListUsers()
if err != nil {
return sessions.User{}, err
return sessions.User{}, errors.Wrap(err, "Unable to List users for initialization")
}

// If there are no users in the database, prompt for initial admin user creation
Expand Down Expand Up @@ -845,7 +845,7 @@ func NewFileAPIInitializer(file string) APIInitializer {
return fileAPIInitializer{file: file}
}

func (f fileAPIInitializer) Initialize(orm sessions.ORM, lggr logger.Logger) (sessions.User, error) {
func (f fileAPIInitializer) Initialize(orm sessions.BasicAdminUsersORM, lggr logger.Logger) (sessions.User, error) {
request, err := credentialsFromFile(f.file, lggr)
if err != nil {
return sessions.User{}, err
Expand All @@ -854,7 +854,7 @@ func (f fileAPIInitializer) Initialize(orm sessions.ORM, lggr logger.Logger) (se
// Load list of users to determine which to assume, or if a user needs to be created
dbUsers, err := orm.ListUsers()
if err != nil {
return sessions.User{}, err
return sessions.User{}, errors.Wrap(err, "Unable to List users for initialization")
}

// If there are no users in the database, create initial admin user from session request from file creds
Expand Down
7 changes: 4 additions & 3 deletions core/cmd/shell_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@ func (s *Shell) runNode(c *cli.Context) error {
return s.errorOut(errors.Wrap(err, "fatal error instantiating application"))
}

sessionORM := app.SessionORM()
// Local shell initialization always uses local auth users table for admin auth
authProviderORM := app.BasicAdminUsersORM()
keyStore := app.GetKeyStore()
err = s.KeyStoreAuthenticator.authenticate(keyStore, s.Config.Password())
if err != nil {
Expand Down Expand Up @@ -449,11 +450,11 @@ func (s *Shell) runNode(c *cli.Context) error {
}

var user sessions.User
if user, err = NewFileAPIInitializer(c.String("api")).Initialize(sessionORM, lggr); err != nil {
if user, err = NewFileAPIInitializer(c.String("api")).Initialize(authProviderORM, lggr); err != nil {
if !errors.Is(err, ErrNoCredentialFile) {
return errors.Wrap(err, "error creating api initializer")
}
if user, err = s.FallbackAPIInitializer.Initialize(sessionORM, lggr); err != nil {
if user, err = s.FallbackAPIInitializer.Initialize(authProviderORM, lggr); err != nil {
if errors.Is(err, ErrorNoAPICredentialsAvailable) {
return errors.WithStack(err)
}
Expand Down
11 changes: 6 additions & 5 deletions core/cmd/shell_local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
chainlinkmocks "github.com/smartcontractkit/chainlink/v2/core/services/chainlink/mocks"
"github.com/smartcontractkit/chainlink/v2/core/services/pg"
evmrelayer "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm"
"github.com/smartcontractkit/chainlink/v2/core/sessions"
"github.com/smartcontractkit/chainlink/v2/core/sessions/localauth"
"github.com/smartcontractkit/chainlink/v2/core/store/dialects"
"github.com/smartcontractkit/chainlink/v2/core/store/models"
"github.com/smartcontractkit/chainlink/v2/core/utils"
Expand Down Expand Up @@ -79,7 +79,7 @@ func TestShell_RunNodeWithPasswords(t *testing.T) {
})
db := pgtest.NewSqlxDB(t)
keyStore := cltest.NewKeyStore(t, db, cfg.Database())
sessionORM := sessions.NewORM(db, time.Minute, logger.TestLogger(t), cfg.Database(), audit.NoopLogger)
authProviderORM := localauth.NewORM(db, time.Minute, logger.TestLogger(t), cfg.Database(), audit.NoopLogger)

lggr := logger.TestLogger(t)

Expand All @@ -100,7 +100,8 @@ func TestShell_RunNodeWithPasswords(t *testing.T) {
pgtest.MustExec(t, db, "DELETE FROM users;")

app := mocks.NewApplication(t)
app.On("SessionORM").Return(sessionORM).Maybe()
app.On("AuthenticationProvider").Return(authProviderORM).Maybe()
app.On("BasicAdminUsersORM").Return(authProviderORM).Maybe()
app.On("GetKeyStore").Return(keyStore).Maybe()
app.On("GetRelayers").Return(testRelayers).Maybe()
app.On("Start", mock.Anything).Maybe().Return(nil)
Expand Down Expand Up @@ -171,7 +172,7 @@ func TestShell_RunNodeWithAPICredentialsFile(t *testing.T) {
c.Insecure.OCRDevelopmentMode = nil
})
db := pgtest.NewSqlxDB(t)
sessionORM := sessions.NewORM(db, time.Minute, logger.TestLogger(t), cfg.Database(), audit.NoopLogger)
authProviderORM := localauth.NewORM(db, time.Minute, logger.TestLogger(t), cfg.Database(), audit.NoopLogger)

// Clear out fixture users/users created from the other test cases
// This asserts that on initial run with an empty users table that the credentials file will instantiate and
Expand Down Expand Up @@ -199,7 +200,7 @@ func TestShell_RunNodeWithAPICredentialsFile(t *testing.T) {
}
testRelayers := genTestEVMRelayers(t, opts, keyStore)
app := mocks.NewApplication(t)
app.On("SessionORM").Return(sessionORM)
app.On("BasicAdminUsersORM").Return(authProviderORM)
app.On("GetKeyStore").Return(keyStore)
app.On("GetRelayers").Return(testRelayers).Maybe()
app.On("Start", mock.Anything).Maybe().Return(nil)
Expand Down
16 changes: 8 additions & 8 deletions core/cmd/shell_remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ func TestShell_DestroyExternalInitiator_NotFound(t *testing.T) {
func TestShell_RemoteLogin(t *testing.T) {

app := startNewApplicationV2(t, nil)
orm := app.SessionORM()
orm := app.AuthenticationProvider()

u := cltest.NewUserWithSession(t, orm)

Expand Down Expand Up @@ -301,7 +301,7 @@ func TestShell_RemoteBuildCompatibility(t *testing.T) {
t.Parallel()

app := startNewApplicationV2(t, nil)
u := cltest.NewUserWithSession(t, app.SessionORM())
u := cltest.NewUserWithSession(t, app.AuthenticationProvider())
enteredStrings := []string{u.Email, cltest.Password}
prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: append(enteredStrings, enteredStrings...)}
client := app.NewAuthenticatingShell(prompter)
Expand Down Expand Up @@ -340,7 +340,7 @@ func TestShell_CheckRemoteBuildCompatibility(t *testing.T) {
t.Parallel()

app := startNewApplicationV2(t, nil)
u := cltest.NewUserWithSession(t, app.SessionORM())
u := cltest.NewUserWithSession(t, app.AuthenticationProvider())
tests := []struct {
name string
remoteVersion, remoteSha string
Expand Down Expand Up @@ -416,7 +416,7 @@ func TestShell_ChangePassword(t *testing.T) {
t.Parallel()

app := startNewApplicationV2(t, nil)
u := cltest.NewUserWithSession(t, app.SessionORM())
u := cltest.NewUserWithSession(t, app.AuthenticationProvider())

enteredStrings := []string{u.Email, cltest.Password}
prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: enteredStrings}
Expand Down Expand Up @@ -466,7 +466,7 @@ func TestShell_Profile_InvalidSecondsParam(t *testing.T) {
t.Parallel()

app := startNewApplicationV2(t, nil)
u := cltest.NewUserWithSession(t, app.SessionORM())
u := cltest.NewUserWithSession(t, app.AuthenticationProvider())
enteredStrings := []string{u.Email, cltest.Password}
prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: enteredStrings}

Expand Down Expand Up @@ -497,7 +497,7 @@ func TestShell_Profile(t *testing.T) {
t.Parallel()

app := startNewApplicationV2(t, nil)
u := cltest.NewUserWithSession(t, app.SessionORM())
u := cltest.NewUserWithSession(t, app.AuthenticationProvider())
enteredStrings := []string{u.Email, cltest.Password}
prompter := &cltest.MockCountingPrompter{T: t, EnteredStrings: enteredStrings}

Expand Down Expand Up @@ -648,7 +648,7 @@ func TestShell_AutoLogin(t *testing.T) {
app := startNewApplicationV2(t, nil)

user := cltest.MustRandomUser(t)
require.NoError(t, app.SessionORM().CreateUser(&user))
require.NoError(t, app.BasicAdminUsersORM().CreateUser(&user))

sr := sessions.SessionRequest{
Email: user.Email,
Expand Down Expand Up @@ -676,7 +676,7 @@ func TestShell_AutoLogin_AuthFails(t *testing.T) {
app := startNewApplicationV2(t, nil)

user := cltest.MustRandomUser(t)
require.NoError(t, app.SessionORM().CreateUser(&user))
require.NoError(t, app.BasicAdminUsersORM().CreateUser(&user))

sr := sessions.SessionRequest{
Email: user.Email,
Expand Down
13 changes: 7 additions & 6 deletions core/cmd/shell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/services/chainlink"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks"
"github.com/smartcontractkit/chainlink/v2/core/sessions"
"github.com/smartcontractkit/chainlink/v2/core/sessions/localauth"
"github.com/smartcontractkit/chainlink/v2/plugins"
)

func TestTerminalCookieAuthenticator_AuthenticateWithoutSession(t *testing.T) {
t.Parallel()

app := cltest.NewApplicationEVMDisabled(t)
u := cltest.NewUserWithSession(t, app.SessionORM())
u := cltest.NewUserWithSession(t, app.AuthenticationProvider())

tests := []struct {
name, email, pwd string
Expand Down Expand Up @@ -65,7 +66,7 @@ func TestTerminalCookieAuthenticator_AuthenticateWithSession(t *testing.T) {
app := cltest.NewApplicationEVMDisabled(t)
require.NoError(t, app.Start(testutils.Context(t)))

u := cltest.NewUserWithSession(t, app.SessionORM())
u := cltest.NewUserWithSession(t, app.AuthenticationProvider())

tests := []struct {
name, email, pwd string
Expand Down Expand Up @@ -155,7 +156,7 @@ func TestTerminalAPIInitializer_InitializeWithoutAPIUser(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
db := pgtest.NewSqlxDB(t)
lggr := logger.TestLogger(t)
orm := sessions.NewORM(db, time.Minute, lggr, pgtest.NewQConfig(true), audit.NoopLogger)
orm := localauth.NewORM(db, time.Minute, lggr, pgtest.NewQConfig(true), audit.NoopLogger)

mock := &cltest.MockCountingPrompter{T: t, EnteredStrings: test.enteredStrings, NotTerminal: !test.isTerminal}
tai := cmd.NewPromptingAPIInitializer(mock)
Expand Down Expand Up @@ -186,7 +187,7 @@ func TestTerminalAPIInitializer_InitializeWithExistingAPIUser(t *testing.T) {
db := pgtest.NewSqlxDB(t)
cfg := configtest.NewGeneralConfig(t, nil)
lggr := logger.TestLogger(t)
orm := sessions.NewORM(db, time.Minute, lggr, cfg.Database(), audit.NoopLogger)
orm := localauth.NewORM(db, time.Minute, lggr, cfg.Database(), audit.NoopLogger)

// Clear out fixture users/users created from the other test cases
// This asserts that on initial run with an empty users table that the credentials file will instantiate and
Expand Down Expand Up @@ -223,7 +224,7 @@ func TestFileAPIInitializer_InitializeWithoutAPIUser(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
db := pgtest.NewSqlxDB(t)
lggr := logger.TestLogger(t)
orm := sessions.NewORM(db, time.Minute, lggr, pgtest.NewQConfig(true), audit.NoopLogger)
orm := localauth.NewORM(db, time.Minute, lggr, pgtest.NewQConfig(true), audit.NoopLogger)

// Clear out fixture users/users created from the other test cases
// This asserts that on initial run with an empty users table that the credentials file will instantiate and
Expand All @@ -248,7 +249,7 @@ func TestFileAPIInitializer_InitializeWithoutAPIUser(t *testing.T) {
func TestFileAPIInitializer_InitializeWithExistingAPIUser(t *testing.T) {
db := pgtest.NewSqlxDB(t)
cfg := configtest.NewGeneralConfig(t, nil)
orm := sessions.NewORM(db, time.Minute, logger.TestLogger(t), cfg.Database(), audit.NoopLogger)
orm := localauth.NewORM(db, time.Minute, logger.TestLogger(t), cfg.Database(), audit.NoopLogger)

tests := []struct {
name string
Expand Down
40 changes: 40 additions & 0 deletions core/config/docs/core.toml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ MaxAgeDays = 0 # Default
MaxBackups = 1 # Default

[WebServer]
# AuthenticationMethod defines which pluggable auth interface to use for user login and role assumption. Options include 'local' and 'ldap'. See docs for more details
AuthenticationMethod = 'local' # Default
# AllowOrigins controls the URLs Chainlink nodes emit in the `Allow-Origins` header of its API responses. The setting can be a comma-separated list with no spaces. You might experience CORS issues if this is not set correctly.
#
# You should set this to the external URL that you use to access the Chainlink UI.
Expand Down Expand Up @@ -191,6 +193,44 @@ StartTimeout = '15s' # Default
# ListenIP specifies the IP to bind the HTTP server to
ListenIP = '0.0.0.0' # Default

# Optional LDAP config if WebServer.AuthenticationMethod is set to 'ldap'
# LDAP queries are all parameterized to support custom LDAP 'dn', 'cn', and attributes
[WebServer.LDAP]
# ServerTLS defines the option to require the secure ldaps
ServerTLS = true # Default
# SessionTimeout determines the amount of idle time to elapse before session cookies expire. This signs out GUI users from their sessions.
SessionTimeout = '15m0s' # Default
# QueryTimeout defines how long queries should wait before timing out, defined in seconds
QueryTimeout = '2m0s' # Default
# BaseUserAttr defines the base attribute used to populate LDAP queries such as "uid=$", default is example
BaseUserAttr = 'uid' # Default
# BaseDN defines the base LDAP 'dn' search filter to apply to every LDAP query, replace example,com with the appropriate LDAP server's structure
BaseDN = 'dc=custom,dc=example,dc=com' # Example
# UsersDN defines the 'dn' query to use when querying for the 'users' 'ou' group
UsersDN = 'ou=users' # Default
# GroupsDN defines the 'dn' query to use when querying for the 'groups' 'ou' group
GroupsDN = 'ou=groups' # Default
# ActiveAttribute is an optional user field to check truthiness for if a user is valid/active. This is only required if the LDAP provider lists inactive users as members of groups
ActiveAttribute = '' # Default
# ActiveAttributeAllowedValue is the value to check against for the above optional user attribute
ActiveAttributeAllowedValue = '' # Default
# AdminUserGroupCN is the LDAP 'cn' of the LDAP group that maps the core node's 'Admin' role
AdminUserGroupCN = 'NodeAdmins' # Default
# EditUserGroupCN is the LDAP 'cn' of the LDAP group that maps the core node's 'Edit' role
EditUserGroupCN = 'NodeEditors' # Default
# RunUserGroupCN is the LDAP 'cn' of the LDAP group that maps the core node's 'Run' role
RunUserGroupCN = 'NodeRunners' # Default
# ReadUserGroupCN is the LDAP 'cn' of the LDAP group that maps the core node's 'Read' role
ReadUserGroupCN = 'NodeReadOnly' # Default
# UserApiTokenEnabled enables the users to issue API tokens with the same access of their role
UserApiTokenEnabled = false # Default
# UserAPITokenDuration is the duration of time an API token is active for before expiring
UserAPITokenDuration = '240h0m0s' # Default
# UpstreamSyncInterval is the interval at which the background LDAP sync task will be called. A '0s' value disables the background sync being run on an interval. This check is already performed during login/logout actions, all sessions and API tokens stored in the local ldap tables are updated to match the remote server
UpstreamSyncInterval = '0s' # Default
# UpstreamSyncRateLimit defines a duration to limit the number of query/API calls to the upstream LDAP provider. It prevents the sync functionality from being called multiple times within the defined duration
UpstreamSyncRateLimit = '2m0s' # Default

[WebServer.RateLimit]
# Authenticated defines the threshold to which authenticated requests get limited. More than this many authenticated requests per `AuthenticatedRateLimitPeriod` will be rejected.
Authenticated = 1000 # Default
Expand Down
9 changes: 9 additions & 0 deletions core/config/docs/secrets.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ BackupURL = "postgresql://user:[email protected]:5432/dbname?sslmode
# Environment variable: `CL_DATABASE_ALLOW_SIMPLE_PASSWORDS`
AllowSimplePasswords = false # Default

# Optional LDAP config
[WebServer.LDAP]
# ServerAddress is the full ldaps:// address of the ldap server to authenticate with and query
ServerAddress = 'ldaps://127.0.0.1' # Example
# ReadOnlyUserLogin is the username of the read only root user used to authenticate the requested LDAP queries
ReadOnlyUserLogin = '[email protected]' # Example
# ReadOnlyUserPass is the password for the above account
ReadOnlyUserPass = 'password' # Example

[Password]
# Keystore is the password for the node's account.
#
Expand Down
Loading

0 comments on commit cea3e6e

Please sign in to comment.