workin oc-auth

This commit is contained in:
mr
2025-01-17 17:24:08 +01:00
parent fd65220b91
commit b84c2ef353
23 changed files with 551 additions and 104 deletions

View File

@@ -31,8 +31,9 @@ var (
type conn interface {
Bind(bindDN, password string) error
SearchUser(user string, attrs ...string) ([]map[string]interface{}, error)
SearchUserRoles(user string, attrs ...string) ([]map[string]interface{}, error)
SearchRoles(attrs ...string) ([]map[string][]string, error)
SearchUser(user string, attrs ...string) ([]map[string][]string, error)
SearchUserRoles(user string, attrs ...string) ([]map[string][]string, error)
Close() error
}
@@ -78,7 +79,7 @@ type Client struct {
cache *freecache.Cache
}
func (cli *Client) Authenticate(ctx context.Context, username, password string) (bool, error) {
func (cli *Client) Authenticate(ctx context.Context, username string, password string) (bool, error) {
if username == "" || password == "" {
return false, nil
}
@@ -101,8 +102,8 @@ func (cli *Client) Authenticate(ctx context.Context, username, password string)
if details == nil {
return false, nil
}
if err := cn.Bind(details["dn"].(string), password); err != nil {
a := details["dn"]
if err := cn.Bind(a[0], password); err != nil {
if err == errInvalidCredentials {
return false, nil
}
@@ -118,6 +119,21 @@ func (cli *Client) Authenticate(ctx context.Context, username, password string)
return true, nil
}
func (cli *Client) GetRoles(ctx context.Context) (map[string]LDAPRoles, error) {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
cn, ok := <-cli.connect(ctx)
cancel()
if !ok {
return map[string]LDAPRoles{}, errConnectionTimeout
}
defer cn.Close()
// Find a user DN by his or her username.
return cli.findRoles(cn, "dn", "member", "uniqueMember")
}
// Claim is the FindOIDCClaims result struct
type LDAPClaim struct {
Code string // the root claim name
@@ -125,6 +141,10 @@ type LDAPClaim struct {
Value interface{} // the value
}
type LDAPRoles struct {
Members map[string][]string
}
// FindOIDCClaims finds all OIDC claims for a user.
func (cli *Client) FindOIDCClaims(ctx context.Context, username string) ([]LDAPClaim, error) {
if username == "" {
@@ -193,11 +213,12 @@ func (cli *Client) FindOIDCClaims(ctx context.Context, username string) ([]LDAPC
roles := make(map[string]interface{})
for _, entry := range entries {
roleDN, ok := entry["dn"].(string)
if !ok || roleDN == "" {
roleDNs, ok := entry["dn"]
if !ok || len(roleDNs) == 0 {
log.Infow("No required LDAP attribute for a role", "ldapAttribute", "dn", "entry", entry)
continue
}
roleDN := roleDNs[0]
if entry[cli.RoleAttr] == nil {
log.Infow("No required LDAP attribute for a role", "ldapAttribute", cli.RoleAttr, "roleDN", roleDN)
continue
@@ -278,8 +299,79 @@ func (cli *Client) connect(ctx context.Context) <-chan conn {
return ch
}
func (cli *Client) findRoles(cn conn, attrs ...string) (map[string]LDAPRoles, error) {
if cli.BindDN != "" {
// We need to login to a LDAP server with a service account for retrieving user data.
if err := cn.Bind(cli.BindDN, cli.BindPass); err != nil {
return map[string]LDAPRoles{}, errors.New(err.Error() + " : failed to login to a LDAP woth a service account")
}
}
entries, err := cn.SearchRoles(attrs...)
fmt.Println("entries", entries)
if err != nil {
return map[string]LDAPRoles{}, err
}
claims := map[string]LDAPRoles{}
for _, entry := range entries {
roleDNs, ok := entry["dn"]
if !ok || len(roleDNs) == 0 {
continue
}
roleDN := roleDNs[0]
// Ensure that a role's DN is inside of the role's base DN.
// It's sufficient to compare the DN's suffix with the base DN.
n, k := len(roleDN), len(cli.RoleBaseDN)
if n < k || !strings.EqualFold(roleDN[n-k:], cli.RoleBaseDN) {
panic("You should never see that")
}
// The DN without the role's base DN must contain a CN and OU
// where the CN is for uniqueness only, and the OU is an application id.
path := strings.Split(roleDN[:n-k-1], ",")
if len(path) != 2 {
continue
}
appID := path[1][len("OU="):]
if _, ok := claims[appID]; !ok {
claims[appID] = LDAPRoles{
Members: map[string][]string{},
}
}
role := path[0][len("cn="):]
if claims[appID].Members[role] == nil {
claims[appID].Members[role] = []string{}
}
fmt.Println("entry", entry)
memberDNs, ok := entry["member"]
for _, memberDN := range memberDNs {
if !ok || memberDN == "" {
continue
}
path = strings.Split(memberDN[:n-k-1], ",")
if len(path) < 1 {
continue
}
member := strings.Split(path[0][len("uid="):], ",")
claims[appID].Members[role] = append(claims[appID].Members[role], member[0])
}
memberDNs, ok = entry["uniqueMember"]
for _, memberDN := range memberDNs {
if !ok || memberDN == "" {
continue
}
path = strings.Split(memberDN[:n-k-1], ",")
if len(path) < 1 {
continue
}
member := strings.Split(path[0][len("uid="):], ",")
claims[appID].Members[role] = append(claims[appID].Members[role], member[0])
}
}
return claims, nil
}
// findBasicUserDetails finds user's LDAP attributes that were specified. It returns nil if no such user.
func (cli *Client) findBasicUserDetails(cn conn, username string, attrs []string) (map[string]interface{}, error) {
func (cli *Client) findBasicUserDetails(cn conn, username string, attrs []string) (map[string][]string, error) {
if cli.BindDN != "" {
// We need to login to a LDAP server with a service account for retrieving user data.
if err := cn.Bind(cli.BindDN, cli.BindPass); err != nil {
@@ -298,7 +390,7 @@ func (cli *Client) findBasicUserDetails(cn conn, username string, attrs []string
var (
entry = entries[0]
details = make(map[string]interface{})
details = make(map[string][]string)
)
for _, attr := range attrs {
if v, ok := entry[attr]; ok {
@@ -349,35 +441,40 @@ func (c *ldapConn) Bind(bindDN, password string) error {
return err
}
func (c *ldapConn) SearchUser(user string, attrs ...string) ([]map[string]interface{}, error) {
func (c *ldapConn) SearchUser(user string, attrs ...string) ([]map[string][]string, error) {
query := fmt.Sprintf(
"(&(|(objectClass=organizationalPerson)(objectClass=inetOrgPerson))"+
"(|(uid=%[1]s)(mail=%[1]s)(userPrincipalName=%[1]s)(sAMAccountName=%[1]s)))", user)
return c.searchEntries(c.BaseDN, query, attrs)
}
func (c *ldapConn) SearchUserRoles(user string, attrs ...string) ([]map[string]interface{}, error) {
func (c *ldapConn) SearchUserRoles(user string, attrs ...string) ([]map[string][]string, error) {
query := fmt.Sprintf("(|"+
"(&(|(objectClass=group)(objectClass=groupOfNames))(member=%[1]s))"+
"(&(|(objectClass=group)(objectClass=groupOfNames)(objectClass=groupofnames))(member=%[1]s))"+
"(&(objectClass=groupOfUniqueNames)(uniqueMember=%[1]s))"+
")", user)
return c.searchEntries(c.RoleBaseDN, query, attrs)
}
func (c *ldapConn) SearchRoles(attrs ...string) ([]map[string][]string, error) {
query := "(|(&(|(objectClass=group)(objectClass=groupOfNames)(objectClass=groupofnames))))"
return c.searchEntries(c.RoleBaseDN, query, attrs)
}
// searchEntries executes a LDAP query, and returns a result as entries where each entry is mapping of LDAP attributes.
func (c *ldapConn) searchEntries(baseDN, query string, attrs []string) ([]map[string]interface{}, error) {
func (c *ldapConn) searchEntries(baseDN, query string, attrs []string) ([]map[string][]string, error) {
req := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, query, attrs, nil)
res, err := c.Search(req)
if err != nil {
return nil, err
}
var entries []map[string]interface{}
var entries []map[string][]string
for _, v := range res.Entries {
entry := map[string]interface{}{"dn": v.DN}
entry := map[string][]string{"dn": []string{v.DN}}
for _, attr := range v.Attributes {
// We need the first value only for the named attribute.
entry[attr.Name] = attr.Values[0]
entry[attr.Name] = attr.Values
}
entries = append(entries, entry)
}