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

Remote Authenticator Service & LDAP Removal #5285

Merged

Conversation

Isan-Rivkin
Copy link
Contributor

@Isan-Rivkin Isan-Rivkin commented Feb 19, 2023

Closes #5262

  1. Support remote authentication service, updated configuration, docs and functionality.
  2. [Breaking Change] Remove LDAP functionality including configuration.

@Isan-Rivkin Isan-Rivkin added include-changelog PR description should be included in next release changelog team/cloud-native Team cloud native labels Feb 19, 2023
@Isan-Rivkin Isan-Rivkin self-assigned this Feb 19, 2023
@Isan-Rivkin Isan-Rivkin linked an issue Feb 19, 2023 that may be closed by this pull request
@Isan-Rivkin Isan-Rivkin changed the title External Authenticator Service WIP: External Authenticator Service Feb 19, 2023
@Isan-Rivkin Isan-Rivkin requested a review from nopcoder February 20, 2023 16:59
@Isan-Rivkin
Copy link
Contributor Author

@nopcoder please jump in as well if you can take a look 🙏
It's mostly done, so feel free to skim through it.
I'll ping when all done.

Copy link
Contributor

@arielshaqed arielshaqed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Nothing major requested :-)

externalAuthenticator, err := newExternalBasicAuthenticator(cfg, authService, logger)

if err != nil {
fmt.Printf("%s: %s\n", "failed to create external authenticator", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please exit lakeFS from the main program. This function could just return an error, which would let us wrap it with additional information about why the LDAP authenticator got called.

Also if we did that then we would not be writing error messages to standard output as we end up doing here (although that is of course easy to fix at this place).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure what you mean, can you take a look at the change I added? If not that then please elaborate.
Also, assuming I understand you correctly notice I applied the same change to allowForeign, err := cmd.Flags().GetBool(mismatchedReposFlagName) in the same file as well.

@@ -27,6 +27,12 @@ Web UI at Administration / Users to create users. Users have an access key
for logging into the Web UI or authenticating programmatic requests to the API
Server or the S3 Gateway.

#### Remote Authenticator Service

LakeFS server supports external authentication, the feature can be configured by providing an HTTP endpoint to an external authentication service. This integration can be especially useful if you already have an existing authentication system in place, as it allows you to reuse that system instead of maintaining a new one.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ortz how much guidance do we plan for explaining external authentication?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's see how many requests we got for this and we'll invest efforts accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


// Expected response from the remote authenticator service
type AuthenticationResponse struct {
// optional, if returned then the user will be used as the official username in lakeFS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure that this is how LDAP works today. Isn't this planned to be the user's DN?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes correct, but the field name is generic to different systems, not sure what's the question?

data, err := ra.doRequest(log, req)

if err != nil {
return "", fmt.Errorf("doing http request %s: %w", username, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return "", fmt.Errorf("doing http request %s: %w", username, err)
return "", fmt.Errorf("remote authenticate %s: %w", username, err)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's less indicative since it's less explicit on which step failed. there are many authenticators running (chain) with errors chaining when they occur.
I prefer to keep it (specifically the part about http)

@@ -25,6 +25,7 @@ import (
"github.com/treeverse/lakefs/pkg/auth"
"github.com/treeverse/lakefs/pkg/auth/crypt"
"github.com/treeverse/lakefs/pkg/auth/email"
remoteauth "github.com/treeverse/lakefs/pkg/auth/remote_authenticator"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

authremote or same package - not sure we need a different package for this one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Fixed
  • Regarding another package, even though it's fairly small, the reason I prefer another package is due to encapsulation. In this particular case for a new comer like myself it's really confusing what code in auth suppose to do, authentication, authorization etc.

WithField("feature", "LDAP").
Warn("Enabling LDAP on lakeFS server, but this functionality is deprecated")
middlewareAuthenticator = append(middlewareAuthenticator, newLDAPAuthenticator(cfg.Auth.LDAP, authService))
externalAuthenticator, err := newExternalBasicAuthenticator(cfg, authService, logger)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Think we should drop the Basic from the name - it is a remote authenticator. So maybe call it
  • We have external and remote - I assume this func was made to consolidate LDAP and Remote. It the code looks more complex as the previous code was simple: check the configuration and chain the auth to the middleware.
  • Suggest: moving the building of the auth middleware chain to a function (not partial). Check the configuration validity as part of Config, so the code here will just allocate the right middleware based on verified configuration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's now all in the same place like LDAP used to be.

Comment on lines 104 to 107
if err != nil {
log.WithError(err).Error("failed creating request")
return "", auth.ErrInvalidRequest
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just long the err in this case and throw the error - I suggest to return the original in this case. Alt. will be to wrap it by invalid request, but I don't see the point in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if err != nil {
return "", fmt.Errorf("add newly created remote auth user %s to %s: %w", dbUsername, ra.Config.DefaultUserGroup, err)
}
return newUser.Username, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use dbUsername while we use it all the way until here. Or use newUser.Username for all places we used dbUsername.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

}

func (la *RemoteAuthenticator) String() string {
return RemoteAuthSource
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to serialize remote authenticator as string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's used by the chain authenticator.

@Isan-Rivkin Isan-Rivkin changed the title WIP: External Authenticator Service Remote Authenticator Service & LDAP Removal Feb 23, 2023
Copy link
Contributor

@ortz ortz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Isan-Rivkin !
Overall, looks good to me... I do have some questions


lakeFS server supports external authentication, the feature can be configured by providing an HTTP endpoint to an external authentication service. This integration can be especially useful if you already have an existing authentication system in place, as it allows you to reuse that system instead of maintaining a new one.

TBD
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover? or you plan on adding content here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a left over, just not sure how deep to do see

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated all relevant docs.

LDAP *LDAP
OIDC OIDC
RemoteAuthenticator *RemoteAuthenticator `mapstructure:"remote_authenticator"`
OIDC OIDC `mapstructure:"oidc"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this relevant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, The OIDC itself config is relevant. (From previous PR's)
here it's only mapstructure:"oidc" following comment previously about alignment incase field name change.

Comment on lines 155 to 158
_, err = ra.AuthService.CreateCredentials(ctx, newUser.Username)
if err != nil {
return "", fmt.Errorf("create credentials for remote auth user %s: %w", newUser.Username, err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to create credentials for that user?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's a good question, right now it's backward compatible with the LDAP implementation.

I couldn't find a reason for it, testing without this function CreateCredentials seems to work.
Do you happen to know why it's here @arielshaqed @nopcoder ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember why we did it this way. I think I didn't want to special-case the existing auth path, which needed to find some credentials or something??!?

Anyway if it works then we're well rid of it >:-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed!

@Isan-Rivkin Isan-Rivkin requested a review from ortz February 23, 2023 14:24
Copy link
Contributor

@arielshaqed arielshaqed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Minor points, mostly about logging...

Comment on lines 155 to 158
_, err = ra.AuthService.CreateCredentials(ctx, newUser.Username)
if err != nil {
return "", fmt.Errorf("create credentials for remote auth user %s: %w", newUser.Username, err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember why we did it this way. I think I didn't want to special-case the existing auth path, which needed to find some credentials or something??!?

Anyway if it works then we're well rid of it >:-)

@@ -163,8 +166,8 @@ type Config struct {
Token string
SupportsInvites bool `mapstructure:"supports_invites"`
}
LDAP *LDAP
OIDC OIDC
RemoteAuthenticator *RemoteAuthenticator `mapstructure:"remote_authenticator"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RemoteAuthenticator have Enabled property - suggest to remove the pointer from here. It will make handling the configuration less issue in case it is nil.
We only need to check cfg.RemoteAuthenticator.Enabled
Note - I'll also embed the type here as the complete configuration is easy to understand without jumping over the types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 as discussed

UsernameAttribute string `mapstructure:"username_attribute"`
UserBaseDN string `mapstructure:"user_base_dn"`
UserFilter string `mapstructure:"user_filter"`
// RemoteAuthenticator holds remote authentication configuration.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just note that in the release notes of this release we should guide ppl to update the configuration as lakeFS will not run with known configuration properties - and we are removing LDAP.
So starting lakeFS with the old configuration will break.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

client *http.Client
}

func NewRemoteAuthenticator(conf *config.RemoteAuthenticator, authService auth.Service, logger logging.Logger) (auth.Authenticator, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Return the concrete type and not the interface
  • Remove dependency in config as possible - create a structure with the same layout as your configuration and just pass it while calling NewRemoteAuthenticator from your main. Alternative: pass the relevant parameters to configuration struct that you define as part of this package. This is to decouple of use of config package from this one.

Copy link
Contributor Author

@Isan-Rivkin Isan-Rivkin Feb 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Return the concrete type and not the interface, updated but for clarification, WHY?
  • Done

}

func (ra *RemoteAuthenticator) String() string {
return RemoteAuthSource
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure I answered this and somehow my comment disappeared 😮
Anyway, it's used by chain auth when auth errors occurs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I missed your answer.

// if the external authentication service provided an external user identifier, use it as the username

if res.ExternalUserIdentifier != nil && *res.ExternalUserIdentifier != "" {
log = ra.Logger.WithField("external_user_identifier", *res.ExternalUserIdentifier)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use log and not ra as you like to add the extra information and not re-write

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch thanks!

Comment on lines 132 to 137
// if the external authentication service provided an external user identifier, use it as the username

if res.ExternalUserIdentifier != nil && *res.ExternalUserIdentifier != "" {
log = ra.Logger.WithField("external_user_identifier", *res.ExternalUserIdentifier)
dbUsername = *res.ExternalUserIdentifier
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// if the external authentication service provided an external user identifier, use it as the username
if res.ExternalUserIdentifier != nil && *res.ExternalUserIdentifier != "" {
log = ra.Logger.WithField("external_user_identifier", *res.ExternalUserIdentifier)
dbUsername = *res.ExternalUserIdentifier
}
// if the external authentication service provided an external user identifier, use it as the username
externalUserIdentifier := swag.String(res.ExternalUserIdentifier)
if externalUserIdentifier != "" {
log = log.WithField("external_user_identifier", externalUserIdentifier)
dbUsername = externalUserIdentifier
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 added (with swag.StringValue)

Comment on lines 20 to 21
const DefaultRequestTimeout = 7 * time.Second
const RemoteAuthSource = "remote_authenticator"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. use scope const
  2. suggest to use https://github.com/mvdan/gofumpt

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure I see the benefit of adding gofumpt, but changed scope

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its alternative to gofmt (command line) that will format your code just in strict way

@Isan-Rivkin Isan-Rivkin requested a review from nopcoder February 26, 2023 13:33

func NewRemoteAuthenticator(conf RemoteAuthenticatorConfig, authService auth.Service, logger logging.Logger) (*RemoteAuthenticator, error) {
if conf.Endpoint == "" {
return nil, fmt.Errorf("base URL is empty: %w", ErrBadConfig)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Endpoint is empty / missing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -47,6 +47,10 @@ func setDefaults(local bool) {
viper.SetDefault("auth.ui_config.login_failed_message", "The credentials don't match.")
viper.SetDefault("auth.ui_config.login_cookie_names", "internal_auth_session")

viper.SetDefault("auth.remote_authenticator.enabled", false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to set default to the flag's default

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

@@ -47,6 +47,10 @@ func setDefaults(local bool) {
viper.SetDefault("auth.ui_config.login_failed_message", "The credentials don't match.")
viper.SetDefault("auth.ui_config.login_cookie_names", "internal_auth_session")

viper.SetDefault("auth.remote_authenticator.enabled", false)
viper.SetDefault("auth.remote_authenticator.default_user_group", "Viewers")
viper.SetDefault("auth.remote_authenticator.request_timeout", 7*time.Second)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the to have a default to be 10 sec or 30 sec - something smaller than 'no timeout' but something that will work for many use-cases and in case the user will want to restrict the time for authenticate, it can be done by configuration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously (In LDAP) was 7, changed to 10 👍

@Isan-Rivkin Isan-Rivkin requested a review from nopcoder February 26, 2023 14:08
Copy link
Contributor

@nopcoder nopcoder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Contributor

@ortz ortz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job!
I do have some comments, some small fixes and questions

@@ -47,6 +47,9 @@ func setDefaults(local bool) {
viper.SetDefault("auth.ui_config.login_failed_message", "The credentials don't match.")
viper.SetDefault("auth.ui_config.login_cookie_names", "internal_auth_session")

viper.SetDefault("auth.remote_authenticator.default_user_group", "Viewers")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that lakefs' default group is Developers, I think that we should be aligned with the non-remote authenticator configuration.

@Isan-Rivkin Isan-Rivkin merged commit 529d1a7 into master Feb 27, 2023
@Isan-Rivkin Isan-Rivkin deleted the 5262-add-external-authenticator-support-and-deprecate-ldap branch February 27, 2023 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
include-changelog PR description should be included in next release changelog team/cloud-native Team cloud native
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add external authenticator support and deprecate LDAP
4 participants