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

[FEATURE] multiple ldap backend support #63

Closed
V1pr opened this issue Oct 9, 2024 · 13 comments · Fixed by #65
Closed

[FEATURE] multiple ldap backend support #63

V1pr opened this issue Oct 9, 2024 · 13 comments · Fixed by #65
Assignees
Labels
enhancement New feature or request

Comments

@V1pr
Copy link

V1pr commented Oct 9, 2024

Since I did not find any related info I'm opening this ticket. We're using multiple (redundant) LDAP servers, it would be nice if all were configurable, like:

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
    name: my-ldapAuth
    namespace: my-namespace
spec:
    plugin:
        ldapAuth:
          - name: ldap01
            Attribute: uid
            BaseDn: dc=example,dc=org
            Enabled: "true"
            LogLevel: DEBUG
            Port: "389"
            Url: ldap://ldap01.example.com
            weight: 1
          - name: ldap02
            Attribute: uid
            BaseDn: dc=example,dc=org
            Enabled: "true"
            LogLevel: DEBUG
            Port: "389"
            Url: ldap://ldap02.example.com
            weight: 2
@wiltonsr
Copy link
Owner

wiltonsr commented Oct 10, 2024

Hi, @V1pr

I started developing this feature. Please feel free to test it and give me any feedback.

The changes are in the branch multiple-ldap-backends.

There are some use cases in the examples directory.

@wiltonsr wiltonsr added the enhancement New feature or request label Oct 10, 2024
@V1pr
Copy link
Author

V1pr commented Oct 10, 2024

Hi, @V1pr

I started developing this feature. Please feel free to test it and give me any feedback.

The changes are in the branch multiple-ldap-backends.

There are some use cases in the examples directory.

thanks, that was quick :)

Tried to test if, but was not able to put it in my setup (k8s). Any suggestions how to refer the branch? Or only localplugins will work (somehow)?

@wiltonsr
Copy link
Owner

Any suggestions how to refer the branch?

Use the pre-release v0.1.9-beta.

@V1pr
Copy link
Author

V1pr commented Oct 11, 2024

Any suggestions how to refer the branch?

Use the pre-release v0.1.9-beta.

Thanks, this works!

First of all, thanks for your work! The auth is working, but I found some small issues:

  1. after upgrading the component I've checked with the old CRD config. The auth works OK, but the message indicating to change the config should be a warning/deprecation message instead of the error:
ERROR: ldapAuth: 2024/10/11 12:27:32 restricted.go:51: Passing LDAP Server Attributes directly is deprecated, please use 'ServerList' instead
  1. haven't seen any warning for giving the same 'weight' for the 2 servers. IMHO, giving the same weight should mean round-robin (which is not the case after testing)

  2. if debug mode is enabled, it prints out the non-serverlist empty values - this is not printed for serverlist entries just the array dump:

DEBUG: ldapAuth: 2024/10/11 12:36:44 restricted.go:51: URL => ''
DEBUG: ldapAuth: 2024/10/11 12:36:44 restricted.go:51: Port => '0
DEBUG: ldapAuth: 2024/10/11 12:36:44 restricted.go:51: StartTLS => 'false'
DEBUG: ldapAuth: 2024/10/11 12:36:44 restricted.go:51: InsecureSkipVerify => 'false'
  1. after a successful auth, I've stopped the first server and tried again the auth in an incognito window. This resulted in a 'panic' - there was a config error for the 2nd server. Please also note the INFO messages for TLSVersion. I can paste the Stack also, if needed.
DEBUG: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Session details: &{ map[] 0xc004429840 true {0xc002d3f900 {0xc002d5c640 0xc0027d1ca0 406}} ldapAuth_session_token}
DEBUG: ldapAuth: 2024/10/11 12:46:11 restricted.go:52: No session found! Trying to authenticate in LDAP
DEBUG: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Attempt 1/2
DEBUG: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Connect Address: 'ldap://ic01.mydomain.intra:389'
INFO: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Version: '' doesnt match any value. Using 'tls.VersionTLS10' instead
INFO: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Please check https://pkg.go.dev/crypto/tls#pkg-constants to a list of valid versions
INFO: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Version: '' doesnt match any value. Using 'tls.VersionTLS10' instead
INFO: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Please check https://pkg.go.dev/crypto/tls#pkg-constants to a list of valid versions
DEBUG: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Attempt 2/2
ERROR: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: LDAP Result Code 200 "Network Error": dial tcp 10.1.1.11:389: connect: connection refused
DEBUG: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Connect Address: 'ldap://ic02.mydomain.intra:389'
INFO: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Version: '' doesnt match any value. Using 'tls.VersionTLS10' instead
INFO: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Please check https://pkg.go.dev/crypto/tls#pkg-constants to a list of valid versions
INFO: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Version: '' doesnt match any value. Using 'tls.VersionTLS10' instead
INFO: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Please check https://pkg.go.dev/crypto/tls#pkg-constants to a list of valid versions
ERROR: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: LDAP Result Code 200 "Network Error": TLS handshake failed (tls: failed to verify certificate: x509: certificate is valid for ldap.mydomain.intra, not ic02.mydomain.intra)
DEBUG: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Running in Bind Mode
DEBUG: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Authenticating User: =viper
ERROR: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: ldap: conn is nil, expected net.Conn
ERROR: ldapAuth: 2024/10/11 12:46:11 restricted.go:51: Authentication failed
DEBUG: ldapAuth: 2024/10/11 12:46:11 restricted.go:52: [ldap: conn is nil, expected net.Conn]
2024-10-11T12:46:11Z ERR plugins-storage/sources/gop-4053897358/src/github.com/wiltonsr/ldapAuth/vendor/github.com/go-ldap/ldap/v3/conn.go:264:2: panic: github.com/go-ldap/ldap/v3.Close(...) module=github.com/wiltonsr/ldapAuth plugin=plugin-ldapAuth runtime=
2024-10-11T12:46:11Z ERR Recovered from panic in HTTP handler [10.1.1.113:26719 - /dashboard/]: reflect: call of reflect.Value.Field on zero Value middlewareName=traefik-internal-recovery middlewareType=Recovery

@wiltonsr
Copy link
Owner

  1. after upgrading the component I've checked with the old CRD config. The auth works OK, but the message indicating to change the config should be a warning/deprecation message instead of the error:
ERROR: ldapAuth: 2024/10/11 12:27:32 restricted.go:51: Passing LDAP Server Attributes directly is deprecated, please use 'ServerList' instead

Fixed, I created a LoggerWARNING handler.

  1. haven't seen any warning for giving the same 'weight' for the 2 servers. IMHO, giving the same weight should mean round-robin (which is not the case after testing)

For now, I'm just sorting the backends based on their weight and iterating in each of them, the first to connect successfully will be elected to use.

ldapAuth/ldapauth.go

Lines 155 to 157 in 37e8653

sort.Slice(config.ServerList, func(i, j int) bool {
return config.ServerList[i].Weight > config.ServerList[j].Weight
})

  1. if debug mode is enabled, it prints out the non-serverlist empty values - this is not printed for serverlist entries just the array dump:
DEBUG: ldapAuth: 2024/10/11 12:36:44 restricted.go:51: URL => ''
DEBUG: ldapAuth: 2024/10/11 12:36:44 restricted.go:51: Port => '0
DEBUG: ldapAuth: 2024/10/11 12:36:44 restricted.go:51: StartTLS => 'false'
DEBUG: ldapAuth: 2024/10/11 12:36:44 restricted.go:51: InsecureSkipVerify => 'false'

You should get a entry in log to ServerList too.

DEBUG: ldapAuth: 2024/10/11 15:40:54 restricted.go:51: ServerList => '[{ldaps://ldap2.forumsys.com 0 1 false false         false [] []} {ldaps://ldap2.forumsys.com 636 0 false false         false [] []} {ldap://ldap.forumsys.com 389 0 false false    uid  dc=example,dc=com   false [ou=mathematicians,dc=example,dc=com ou=italians,ou=scientists,dc=example,dc=com] [euler uid=euclid,dc=example,dc=com]}]'

The old parameters must be presented in the config to prevent old configurations from breaking. If the middleware detects old settings, they will be converted automatically.

  1. after a successful auth, I've stopped the first server and tried again the auth in an incognito window. This resulted in a 'panic' - there was a config error for the 2nd server. Please also note the INFO messages for TLSVersion. I can paste the Stack also, if needed.

This point is weird. You should get an error because both servers returned an error.

The TLSVersion log happens because there is no explicit setting. But in this case, there is a fallback to set to tls.VersionTLS10.

The panic issue it's happening because de conn is nil.

ldapAuth/ldapauth.go

Lines 218 to 236 in 37e8653

var conn *ldap.Conn = nil
var serverInUse LdapServerConfig
for i, s := range la.config.ServerList {
LoggerDEBUG.Printf("Attempt %d/%d", i+1, len(la.config.ServerList))
conn, err = Connect(s)
if err != nil {
LoggerERROR.Printf("%s", err)
} else {
serverInUse = s
break
}
if len(la.config.ServerList) == i-1 {
RequireAuth(rw, req, la.config, err)
return
}
}

But you should be caught in the return statement.

I will try to check with more details when I have some time.

@wiltonsr
Copy link
Owner

wiltonsr commented Oct 11, 2024

Hello, @V1pr

Please, test again with the release v0.1.9-beta.3.

@V1pr
Copy link
Author

V1pr commented Oct 11, 2024

Hello, @V1pr

Please, test again with the release v0.1.9-beta.3.

All working, as it should. I've tested with 2 servers, all cases were ok (even if it's a TLS connect error). I couldn't reproduce the panic situation.

As about my previous point 3): yes, I've seen the "dumped" ServerList. However it's a bit misleading, that the ServerList is just dumped, not prettified, however the old pretty paramslist are empty. I know this is just a "cosmetic issue", but still, it can be frustrating for other users.

IMHO the TLSVersion should get a default value (tls.VersionTLS11), if not set to get rid of the WARNING.

The only thing I would add, is to print out the server's URL, which did the auth:

INFO: ldapAuth: 2024/10/11 19:50:54 restricted.go:51: Authentication succeeded

like:

INFO: ldapAuth: 2024/10/11 19:50:54 restricted.go:51: Authentication succeeded using **URL**

@wiltonsr wiltonsr self-assigned this Oct 14, 2024
@wiltonsr
Copy link
Owner

All working, as it should.

Glad to know.

@fcinqmars can you review this feature?

This will be a break change as we modify how LDAP backends are provided in the configuration.

@wiltonsr wiltonsr linked a pull request Oct 14, 2024 that will close this issue
@fcinqmars
Copy link
Collaborator

In my opinion, anything related to post-connection steps (bind, authorization, etc) should be configured globally for all servers in ServerList to ensure consistency in the authorization flow.

With the current implementation, we could end up with a scenario where different servers are configured to allow different groups/users depending on which LDAP server is used for a same domain (replicas).

I think supporting multiple backend servers makes sense as long as the servers are replicas of each other. As per @V1pr's initial message, it does look like that this is their use case (same root domain, same base DN)

        ldapAuth:
          - name: ldap01
            Attribute: uid
            BaseDn: dc=example,dc=org
            Enabled: "true"
            LogLevel: DEBUG
            Port: "389"
            Url: ldap://ldap01.example.com
            weight: 1
          - name: ldap02
            Attribute: uid
            BaseDn: dc=example,dc=org
            Enabled: "true"
            LogLevel: DEBUG
            Port: "389"
            Url: ldap://ldap02.example.com
            weight: 2

With that in mind, the parameters below should probably be global to the plugin instead of being server-dependent.

  • attribute
  • searchFilter
  • baseDn
  • bindDn
  • bindPassword
  • enableNestedgroupsFilter
  • allowedGroups
  • allowedUsers

On a less critical note, the TLS settings could also be left global. The plugin's LDAP client could be configured to apply the same settings such as:

  • Minimum TLS version
  • Maxiumum TLS version
  • Certificate authorities (more than one certificate can be provided though realistically all servers should be under the same authority). No extra work needed as AppendCertsFromPEM already does it for you
  • InsecureSkipVerify
  • StartTLS

The goal is to ensure the plugin behaves as consistently as possible, so we don't introduce security risks down the line.

@wiltonsr
Copy link
Owner

wiltonsr commented Oct 14, 2024

With that in mind, the parameters below should probably be global to the plugin instead of being server-dependent.

  • attribute
  • searchFilter
  • baseDn
  • bindDn
  • bindPassword
  • enableNestedgroupsFilter
  • allowedGroups
  • allowedUsers

I agree that these parameters should be global to ensure consistency in the authorization flow.

On the other hand, I think that all connection-related parameters should be per server since, from previous experiences, it's possible to have a production server with TLS configured but a replica without that.

So the following parameters should be server-dependent in ServerList:

  • Minimum TLS version
  • Maxiumum TLS version
  • Certificate authorities
  • InsecureSkipVerify
  • StartTLS

Can we proceed with these definitions, @fcinqmars @V1pr ?

@V1pr
Copy link
Author

V1pr commented Oct 14, 2024

With that in mind, the parameters below should probably be global to the plugin instead of being server-dependent.

  • attribute
  • searchFilter
  • baseDn
  • bindDn
  • bindPassword
  • enableNestedgroupsFilter
  • allowedGroups
  • allowedUsers

I agree that these parameters should be global to ensure consistency in the authorization flow.

On the other hand, I think that all connection-related parameters should be per server since, from previous experiences, it's possible to have a production server with TLS configured but a replica without that.

So the following parameters should be server-dependent in ServerList:

  • Minimum TLS version
  • Maxiumum TLS version
  • Certificate authorities
  • InsecureSkipVerify
  • StartTLS

Can we proceed with these definitions, @fcinqmars @V1pr ?

Yes, this looks good and logical!

Usually the servers run with the same config, but if anything happens - from an operational point of view - it's good that the connection security related settings can be changed per server.

Just to confirm @fcinqmars assumption, we're using replicas for redundancy (that's why I've asked if same weighting means round-robin)

@fcinqmars
Copy link
Collaborator

With that in mind, the parameters below should probably be global to the plugin instead of being server-dependent.

  • attribute
  • searchFilter
  • baseDn
  • bindDn
  • bindPassword
  • enableNestedgroupsFilter
  • allowedGroups
  • allowedUsers

I agree that these parameters should be global to ensure consistency in the authorization flow.

On the other hand, I think that all connection-related parameters should be per server since, from previous experiences, it's possible to have a production server with TLS configured but a replica without that.

So the following parameters should be server-dependent in ServerList:

  • Minimum TLS version
  • Maxiumum TLS version
  • Certificate authorities
  • InsecureSkipVerify
  • StartTLS

Can we proceed with these definitions, @fcinqmars @V1pr ?

Yeah, that's good for me too.

@wiltonsr
Copy link
Owner

Yes, this looks good and logical!

Yeah, that's good for me too.

Nice, I will update the code and update #65.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants