diff options
-rw-r--r-- | README.md | 9 | ||||
-rw-r--r-- | TODO.md | 6 | ||||
-rw-r--r-- | bottin.hcl.example | 2 | ||||
-rw-r--r-- | main.go | 12 | ||||
-rw-r--r-- | read.go | 18 | ||||
-rw-r--r-- | util.go | 28 | ||||
-rw-r--r-- | write.go | 49 |
7 files changed, 82 insertions, 42 deletions
@@ -15,7 +15,6 @@ Features: - Access control through an ACL (hardcoded in the configuration file) -Building Bottin can be done simply by running `go build` in this folder. A Docker image is provided on the [Docker hub](https://hub.docker.com/r/lxpz/bottin_amd64). An example for running Bottin on a Nomad cluster can be found in `bottin.hcl.example`. @@ -26,6 +25,14 @@ The configuration file is a JSON file whose contents is described in the followi Bottin is licensed under the terms of the GPLv3. +## Building Bottin + +Bottin requires go 1.13 or later. + +To build Bottin, clone this repository outside of your `$GOPATH`. +Then, run `make` in the root of the repo. + + ## Server initialization When Bottin is launched on an empty database, @@ -0,0 +1,6 @@ +- Switch to `go mod` for building Bottin + +- Implement missing search filters (in applyFilter) +- Add an initial prefix to the consul key value + +- Potential bugs with different combinations of lower/uppercase names diff --git a/bottin.hcl.example b/bottin.hcl.example index 8305a01..eb29095 100644 --- a/bottin.hcl.example +++ b/bottin.hcl.example @@ -12,7 +12,7 @@ job "directory" { task "server" { driver = "docker" config { - image = "lxpz/bottin_amd64:4" + image = "lxpz/bottin_amd64:6" readonly_rootfs = true port_map { ldap_port = 389 @@ -1,8 +1,5 @@ package main -// @FIXME: Proper handling of various upper/lower case combinations -// @FIXME: Implement missing search filters (in applyFilter) -// @FIXME: Add an initial prefix to the consul key value import ( "crypto/rand" @@ -381,7 +378,14 @@ func (server *Server) objectExists(dn string) (bool, error) { return len(data) > 0, nil } -func (server *Server) checkSuffix(dn string, allow_extend bool) (string, error) { +func (server *Server) checkDN(dn string, allow_extend bool) (string, error) { + // 1. Canonicalize: remove spaces between things + dn, err := canonicalDN(dn) + if err != nil { + return "", err + } + + // 2. Check suffix (add it if allow_extend is set) suffix := server.config.Suffix if len(dn) < len(suffix) { if dn != suffix[len(suffix)-len(dn):] || !allow_extend { @@ -25,11 +25,10 @@ func (server *Server) handleCompare(s ldap.UserState, w ldap.ResponseWriter, m * } func (server *Server) handleCompareInternal(state *State, r *message.CompareRequest) (int, error) { - dn := string(r.Entry()) attr := string(r.Ava().AttributeDesc()) expected := string(r.Ava().AssertionValue()) - _, err := server.checkSuffix(dn, false) + dn, err := server.checkDN(string(r.Entry()), false) if err != nil { return ldap.LDAPResultInvalidDNSyntax, err } @@ -82,21 +81,22 @@ func (server *Server) handleSearch(s ldap.UserState, w ldap.ResponseWriter, m *l func (server *Server) handleSearchInternal(state *State, w ldap.ResponseWriter, r *message.SearchRequest) (int, error) { + baseObject, err := server.checkDN(string(r.BaseObject()), true) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + server.logger.Tracef("-- SEARCH REQUEST: --") - server.logger.Tracef("Request BaseDn=%s", r.BaseObject()) + server.logger.Tracef("Request BaseDn=%s", baseObject) server.logger.Tracef("Request Filter=%s", r.Filter()) server.logger.Tracef("Request FilterString=%s", r.FilterString()) server.logger.Tracef("Request Attributes=%s", r.Attributes()) server.logger.Tracef("Request TimeLimit=%d", r.TimeLimit().Int()) - if !server.config.Acl.Check(&state.login, "read", string(r.BaseObject()), []string{}) { + if !server.config.Acl.Check(&state.login, "read", baseObject, []string{}) { return ldap.LDAPResultInsufficientAccessRights, fmt.Errorf("Please specify a base object on which you have read rights") } - baseObject, err := server.checkSuffix(string(r.BaseObject()), true) - if err != nil { - return ldap.LDAPResultInvalidDNSyntax, err - } basePath, err := dnToConsul(baseObject) if err != nil { return ldap.LDAPResultInvalidDNSyntax, err @@ -206,7 +206,7 @@ func applyFilter(entry Entry, filter message.Filter) (bool, error) { for entry_desc, value := range entry { if strings.EqualFold(entry_desc, desc) { for _, val := range value { - if val == target { + if valueMatch(entry_desc, val, target) { return true, nil } } @@ -98,13 +98,29 @@ func parseDN(dn string) ([]DNComponent, error) { return nil, fmt.Errorf("Wrong DN component: %s (expected type=value)", rdn) } ret = append(ret, DNComponent{ - Type: splits[0], - Value: splits[1], + Type: strings.TrimSpace(splits[0]), + Value: strings.TrimSpace(splits[1]), }) } return ret, nil } +func canonicalDN(dn string) (string, error) { + path, err := parseDN(dn) + if err != nil { + return "", err + } + + ret := "" + for _, c := range path { + if ret != "" { + ret = ret + "," + } + ret = ret + c.Type + "=" + c.Value + } + return ret, nil +} + func checkRestrictedAttr(attr string) error { RESTRICTED_ATTRS := []string{ ATTR_MEMBEROF, @@ -138,3 +154,11 @@ func genUuid() string { } return uuid.String() } + +func valueMatch(attr, val1, val2 string) bool { + if strings.EqualFold(attr, ATTR_USERPASSWORD) { + return val1 == val2 + } else { + return strings.EqualFold(val1, val2) + } +} @@ -30,9 +30,7 @@ func (server *Server) handleAdd(s ldap.UserState, w ldap.ResponseWriter, m *ldap } func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (int, error) { - dn := string(r.Entry()) - - _, err := server.checkSuffix(dn, false) + dn, err := server.checkDN(string(r.Entry()), false) if err != nil { return ldap.LDAPResultInvalidDNSyntax, err } @@ -81,18 +79,18 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in if strings.EqualFold(key, ATTR_MEMBER) { members = vals_str for _, member := range members { - _, err := server.checkSuffix(member, false) + member_canonical, err := server.checkDN(member, false) if err != nil { return ldap.LDAPResultInvalidDNSyntax, err } - exists, err = server.objectExists(member) + exists, err = server.objectExists(member_canonical) if err != nil { return ldap.LDAPResultOperationsError, err } if !exists { return ldap.LDAPResultNoSuchObject, fmt.Errorf( "Cannot add %s to members, it does not exist!", - member) + member_canonical) } } } @@ -104,7 +102,7 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in entry[ATTR_ENTRYUUID] = []string{genUuid()} entry[dnSplit[0].Type] = []string{dnSplit[0].Value} - // Add our intem in the DB + // Add our item in the DB err = server.addElements(dn, entry) if err != nil { return ldap.LDAPResultOperationsError, err @@ -117,7 +115,7 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in for _, member := range members { memberGroups, err := server.getAttribute(member, ATTR_MEMBEROF) if err != nil { - server.logger.Printf("Could not add %s to memberOf of %s: %s", dn, member, err) + server.logger.Warnf("Could not add %s to memberOf of %s: %s", dn, member, err) continue } if memberGroups == nil { @@ -128,7 +126,7 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in for _, mb := range memberGroups { if mb == dn { alreadyMember = true - server.logger.Printf("Warning: inconsistency detected, %s was memberOf %s at a time when it didn't exist!", + server.logger.Warnf("Warning: inconsistency detected, %s was memberOf %s at a time when it didn't exist!", member, dn) break } @@ -140,7 +138,7 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in ATTR_MEMBEROF: memberGroups, }) if err != nil { - server.logger.Printf("Could not add %s to memberOf of %s: %s", dn, member, err) + server.logger.Warnf("Could not add %s to memberOf of %s: %s", dn, member, err) } } } @@ -170,9 +168,7 @@ func (server *Server) handleDelete(s ldap.UserState, w ldap.ResponseWriter, m *l } func (server *Server) handleDeleteInternal(state *State, r *message.DelRequest) (int, error) { - dn := string(*r) - - _, err := server.checkSuffix(dn, false) + dn, err := server.checkDN(string(*r), false) if err != nil { return ldap.LDAPResultInvalidDNSyntax, err } @@ -230,7 +226,7 @@ func (server *Server) handleDeleteInternal(state *State, r *message.DelRequest) for _, group := range memberOf { groupMembers, err := server.getAttribute(group, ATTR_MEMBER) if err != nil { - server.logger.Printf("Could not remove %s from members of %s: %s", dn, group, err) + server.logger.Warnf("Could not remove %s from members of %s: %s", dn, group, err) continue } @@ -245,7 +241,7 @@ func (server *Server) handleDeleteInternal(state *State, r *message.DelRequest) ATTR_MEMBER: newMembers, }) if err != nil { - server.logger.Printf("Could not remove %s from members of %s: %s", dn, group, err) + server.logger.Warnf("Could not remove %s from members of %s: %s", dn, group, err) } } } @@ -255,7 +251,7 @@ func (server *Server) handleDeleteInternal(state *State, r *message.DelRequest) for _, member := range memberList { memberOf, err := server.getAttribute(member, ATTR_MEMBEROF) if err != nil || memberOf == nil { - server.logger.Printf("Could not remove %s from memberOf of %s: %s", dn, member, err) + server.logger.Warnf("Could not remove %s from memberOf of %s: %s", dn, member, err) continue } @@ -270,7 +266,7 @@ func (server *Server) handleDeleteInternal(state *State, r *message.DelRequest) ATTR_MEMBEROF: newMemberOf, }) if err != nil { - server.logger.Printf("Could not remove %s from memberOf of %s: %s", dn, member, err) + server.logger.Warnf("Could not remove %s from memberOf of %s: %s", dn, member, err) } } } @@ -299,9 +295,7 @@ func (server *Server) handleModify(s ldap.UserState, w ldap.ResponseWriter, m *l } func (server *Server) handleModifyInternal(state *State, r *message.ModifyRequest) (int, error) { - dn := string(r.Object()) - - _, err := server.checkSuffix(dn, false) + dn, err := server.checkDN(string(r.Object()), false) if err != nil { return ldap.LDAPResultInvalidDNSyntax, err } @@ -448,7 +442,11 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques } // Check that added members actually exist - for _, addMem := range addMembers { + for i := range addMembers { + addMem, err := server.checkDN(addMembers[i], false) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } exists, err := server.objectExists(addMem) if err != nil { return ldap.LDAPResultOperationsError, err @@ -457,6 +455,7 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques return ldap.LDAPResultNoSuchObject, fmt.Errorf( "Cannot add member %s, it does not exist", addMem) } + addMembers[i] = addMem } newEntry[ATTR_MODIFIERSNAME] = []string{state.login.user} @@ -471,7 +470,7 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques for _, addMem := range addMembers { memberOf, err := server.getAttribute(addMem, ATTR_MEMBEROF) if err != nil { - server.logger.Printf("Could not add %s to memberOf of %s: %s", dn, addMem, err) + server.logger.Warnf("Could not add %s to memberOf of %s: %s", dn, addMem, err) continue } if memberOf == nil { @@ -491,7 +490,7 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques ATTR_MEMBEROF: memberOf, }) if err != nil { - server.logger.Printf("Could not add %s to memberOf of %s: %s", dn, addMem, err) + server.logger.Warnf("Could not add %s to memberOf of %s: %s", dn, addMem, err) } } } @@ -499,7 +498,7 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques for _, delMem := range delMembers { memberOf, err := server.getAttribute(delMem, ATTR_MEMBEROF) if err != nil { - server.logger.Printf("Could not remove %s from memberOf of %s: %s", dn, delMem, err) + server.logger.Warnf("Could not remove %s from memberOf of %s: %s", dn, delMem, err) continue } if memberOf == nil { @@ -514,7 +513,7 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques err = server.addElements(delMem, Entry{ATTR_MEMBEROF: newMemberOf}) if err != nil { - server.logger.Printf("Could not remove %s from memberOf of %s: %s", dn, delMem, err) + server.logger.Warnf("Could not remove %s from memberOf of %s: %s", dn, delMem, err) } } |