Skip to content

Commit

Permalink
Merge pull request #43 from fcinqmars/feat/allowed-users
Browse files Browse the repository at this point in the history
feat: add support for allowing individual users
  • Loading branch information
wiltonsr authored Jul 27, 2023
2 parents 423ef21 + 986bdce commit f3ed0c6
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 9 deletions.
2 changes: 1 addition & 1 deletion examples/conf-from-labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ services:
- traefik.http.middlewares.ldap_auth.plugin.ldapAuth.port=389
- traefik.http.middlewares.ldap_auth.plugin.ldapAuth.baseDN=dc=example,dc=com
- traefik.http.middlewares.ldap_auth.plugin.ldapAuth.attribute=uid
# AllowedGroups is not supported with labels, because multiple value labels are separated with commas
# AllowedGroups and AllowedUsers are not supported with labels, because multiple value labels are separated with commas
# SearchFilter must not escape curly braces when using labels
# - traefik.http.middlewares.ldap_auth.plugin.ldapAuth.searchFilter=({{.Attribute}}={{.Username}})
# =================================================================================================
1 change: 1 addition & 0 deletions examples/dynamic-conf/ldapAuth-conf.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ LogLevel = "DEBUG"
Port = "389"
Url = "ldap://ldap.forumsys.com"
AllowedGroups = ["ou=mathematicians,dc=example,dc=com","ou=italians,ou=scientists,dc=example,dc=com"]
AllowedUsers = ["euler", "euclid"]
# SearchFilter must escape curly braces when using toml file
# https://toml.io/en/v1.0.0#string
# SearchFilter = '''(\{\{.Attribute\}\}=\{\{.Username\}\})'''
3 changes: 3 additions & 0 deletions examples/dynamic-conf/ldapAuth-conf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ http:
AllowedGroups:
- ou=mathematicians,dc=example,dc=com
- ou=italians,ou=scientists,dc=example,dc=com
AllowedUsers:
- euler
- euclid
# SearchFilter must escape curly braces when using yml file
# https://yaml.org/spec/1.1/#id872840
# SearchFilter: (\{\{.Attribute\}\}=\{\{.Username\}\})
66 changes: 59 additions & 7 deletions ldapauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type Config struct {
WWWAuthenticateHeader bool `json:"wwwAuthenticateHeader,omitempty" yaml:"wwwAuthenticateHeader,omitempty"`
WWWAuthenticateHeaderRealm string `json:"wwwAuthenticateHeaderRealm,omitempty" yaml:"wwwAuthenticateHeaderRealm,omitempty"`
AllowedGroups []string `json:"allowedGroups,omitempty" yaml:"allowedGroups,omitempty"`
AllowedUsers []string `json:"allowedUsers,omitempty" yaml:"allowedUsers,omitempty"`
Username string
}

Expand Down Expand Up @@ -89,6 +90,7 @@ func CreateConfig() *Config {
WWWAuthenticateHeader: true,
WWWAuthenticateHeaderRealm: "",
AllowedGroups: nil,
AllowedUsers: nil,
Username: "",
}
}
Expand Down Expand Up @@ -134,6 +136,7 @@ func (la *LdapAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
LoggerDEBUG.Printf("Session details: %v", session)

username, password, ok := req.BasicAuth()
username = strings.ToLower(username)

la.config.Username = username

Expand Down Expand Up @@ -185,13 +188,12 @@ func (la *LdapAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return
}

hasValidGroups, err := LdapCheckUserGroups(conn, la.config, entry, username)

if !hasValidGroups {
isAuthorized, err := LdapCheckUserAuthorized(conn, la.config, entry, username)
if !isAuthorized {
defer conn.Close()
LoggerERROR.Printf("%s", err)
RequireAuth(rw, req, la.config, err)
return
return
}

defer conn.Close()
Expand Down Expand Up @@ -227,7 +229,7 @@ func (la *LdapAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
la.next.ServeHTTP(rw, req)
}

// LdapCheckUser chec if user and password are correct.
// LdapCheckUser check if user and password are correct.
func LdapCheckUser(conn *ldap.Conn, config *Config, username, password string) (bool, *ldap.Entry, error) {
if config.SearchFilter == "" {
LoggerDEBUG.Printf("Running in Bind Mode")
Expand All @@ -253,10 +255,60 @@ func LdapCheckUser(conn *ldap.Conn, config *Config, username, password string) (
return err == nil, result.Entries[0], err
}

// LdapCheckUserAuthorized check if user is authorized post-authentication
func LdapCheckUserAuthorized(conn *ldap.Conn, config *Config, entry *ldap.Entry, username string) (bool, error) {
// Check if authorization is required or simply authentication
if len(config.AllowedUsers) == 0 && len(config.AllowedGroups) == 0 {
LoggerDEBUG.Printf("No authorization requirements")
return true, nil
}

// Check if user is explicitly allowed
if LdapCheckAllowedUsers(conn, config, entry, username) {
return true, nil
}

// Check if user is allowed through groups
isValidGroups, err := LdapCheckUserGroups(conn, config, entry, username)
if isValidGroups {
return true, err
}

errMsg := fmt.Sprintf("User '%s' does not match any allowed users nor allowed groups.", username)

if err != nil {
err = fmt.Errorf("%w\n%s", err, errMsg)
} else {
err = errors.New(errMsg)
}

return false, err
}

// LdapCheckAllowedUsers check if user is explicitly allowed in AllowedUsers list
func LdapCheckAllowedUsers(conn *ldap.Conn, config *Config, entry *ldap.Entry, username string) bool {
if len(config.AllowedUsers) == 0 {
return false
}

found := false

for _, u := range config.AllowedUsers {
lowerAllowedUser := strings.ToLower(u)
if lowerAllowedUser == username || lowerAllowedUser == strings.ToLower(entry.DN) {
LoggerDEBUG.Printf("User: '%s' explicitly allowed in AllowedUsers", entry.DN)
found = true
}
}

return found
}

// LdapCheckUserGroups check if the is user is a member of any of the AllowedGroups list
func LdapCheckUserGroups(conn *ldap.Conn, config *Config, entry *ldap.Entry, username string) (bool, error) {

if len(config.AllowedGroups) == 0 {
return true, nil
return false, nil
}

found := false
Expand Down Expand Up @@ -300,7 +352,7 @@ func LdapCheckUserGroups(conn *ldap.Conn, config *Config, entry *ldap.Entry, use
break
}

err = fmt.Errorf("User not in any of the allowed groups")
LoggerDEBUG.Printf("User '%s' not in any of the allowed groups", username)
}

return found, err
Expand Down
13 changes: 12 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,17 @@ _Optional, Default: `[]`_

The list of LDAP group DNs that users must be members of to be granted access. If a user is in any of the listed groups, then that user is granted access.

If set to an empty list, all users with an LDAP account can log in, without performing any group membership checks.
If set to an empty list, all users with an LDAP account can log in, without performing any group membership checks unless `allowedUsers` is set. In that case, the user must be a part of the `allowedUsers` list.

`allowedGroups` is not supported with labels, because multiple value labels are separated with commas. You must use `toml` or `yaml` configuration file. For more details, check [examples](https://github.com/wiltonsr/ldapAuth/tree/main/examples) page.

##### `allowedUsers`
Needs `traefik` >= [`v2.8.2`](https://github.com/traefik/traefik/releases/tag/v2.8.2)

_Optional, Default: `[]`_

The list of LDAP user DNs or usernames to be granted access. If a user is in the listed users, then that user is granted access.

If set to an empty list, all users with an LDAP account can log in, unless `allowedGroups` is set. In that case, group membership checks will be performed.

`allowedUsers` is not supported with labels, because multiple value labels are separated with commas. You must use `toml` or `yaml` configuration file. For more details, check [examples](https://github.com/wiltonsr/ldapAuth/tree/main/examples) page.

0 comments on commit f3ed0c6

Please sign in to comment.