diff options
Diffstat (limited to 'main.go')
-rw-r--r-- | main.go | 188 |
1 files changed, 86 insertions, 102 deletions
@@ -3,6 +3,7 @@ package main // @FIXME: Implement a real permission system: limit read/write scope/attributes, possibly based on group membership // @FIXME: Implement missing search filters (in applyFilter) // @FIXME: Add an initial prefix to the consul key value +// @FIXME: Add TLS connections import ( "encoding/base64" @@ -20,100 +21,9 @@ import ( message "github.com/vjeantet/goldap/message" ) -func dnToConsul(dn string) (string, error) { - if strings.Contains(dn, "/") { - return "", fmt.Errorf("DN %s contains a /", dn) - } - - rdns := strings.Split(dn, ",") - - // Reverse rdns - for i, j := 0, len(rdns)-1; i < j; i, j = i+1, j-1 { - rdns[i], rdns[j] = rdns[j], rdns[i] - } - - return strings.Join(rdns, "/"), nil -} - -func consulToDN(key string) (string, string) { - path := strings.Split(key, "/") - dn := "" - for _, cpath := range path { - if cpath == "" { - continue - } - kv := strings.Split(cpath, "=") - if len(kv) == 2 && kv[0] == "attribute" { - return dn, kv[1] - } - if dn != "" { - dn = "," + dn - } - dn = cpath + dn - } - panic("Consul key " + key + " does not end with attribute=something") -} - -func parseValue(value []byte) ([]string, error) { - val := []string{} - err := json.Unmarshal(value, &val) - if err == nil { - return val, nil - } - - val2 := "" - err = json.Unmarshal(value, &val2) - if err == nil { - return []string{val2}, nil - } - - return nil, fmt.Errorf("Not a string or list of strings: %s", value) -} - -func parseConsulResult(data []*consul.KVPair) (map[string]Entry, error) { - aggregator := map[string]Entry{} - - for _, kv := range data { - log.Printf("(parseConsulResult) %s %s", kv.Key, string(kv.Value)) - dn, attr := consulToDN(kv.Key) - if _, exists := aggregator[dn]; !exists { - aggregator[dn] = Entry{} - } - value, err := parseValue(kv.Value) - if err != nil { - return nil, err - } - aggregator[dn][attr] = value - } - - return aggregator, nil -} - -type DNComponent struct { - Type string - Value string -} - -func parseDN(dn string) ([]DNComponent, error) { - rdns := strings.Split(dn, ",") - - ret := []DNComponent{} - - for _, rdn := range rdns { - splits := strings.Split(rdn, "=") - if len(splits) != 2 { - return nil, fmt.Errorf("Wrong DN component: %s (expected type=value)", rdn) - } - ret = append(ret, DNComponent{ - Type: splits[0], - Value: splits[1], - }) - } - return ret, nil -} - type Config struct { Suffix string + Acl ACL } type Server struct { @@ -122,7 +32,7 @@ type Server struct { } type State struct { - bindDn string + login Login } type Entry map[string][]string @@ -138,9 +48,29 @@ func main() { } kv := client.KV() + aclStr := []string{ + // Anybody (before binding) can bind to an entity under ou=users,dc=gobottin,dc=eu + "ANONYMOUS::bind:*,ou=users,dc=gobottin,dc=eu:", + // Anybody (before binding) can bind to the specific admin entity + "ANONYMOUS::bind:cn=admin,dc=gobottin,dc=eu:", + // Anybody who is logged in can read anything that is not a userpassword attribute + "*,dc=gobottin,dc=eu::read:*:* !userpassword", + // Anybody can read and modify anything from their own entry + "*::read modify:SELF:*", + // The admin can add, modify, delete anything + "cn=admin,dc=gobottin,dc=eu::add modify delete:*:*", + // Members of the admin group can add, modify, delete anything + "*:cn=admin,ou=groups,dc=gobottin,dc=eu:add modify delete:*:*", + } + acl, err := ParseACL(aclStr) + if err != nil { + panic(err) + } + // TODO read config from somewhere config := Config{ Suffix: "dc=gobottin,dc=eu", + Acl: acl, } gobottin := Server{config: config, kv: kv} @@ -152,7 +82,12 @@ func main() { //Create a new LDAP Server ldapserver := ldap.NewServer() ldapserver.NewUserState = func() ldap.UserState { - return &State{} + return &State{ + login: Login{ + user: "ANONYMOUS", + groups: []string{}, + }, + } } routes := ldap.NewRouteMux() @@ -328,11 +263,16 @@ func (server *Server) handleBind(s ldap.UserState, w ldap.ResponseWriter, m *lda } func (server *Server) handleBindInternal(state *State, r *message.BindRequest) (int, error) { + // Check permissions + if !server.config.Acl.Check(&state.login, "bind", string(r.Name()), []string{}) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + + // Try to retrieve password and check for match passwd, err := server.getAttribute(string(r.Name()), "userpassword") if err != nil { return ldap.LDAPResultOperationsError, err } - if passwd == nil { return ldap.LDAPResultNoSuchObject, nil } @@ -340,7 +280,14 @@ func (server *Server) handleBindInternal(state *State, r *message.BindRequest) ( for _, hash := range passwd { valid := SSHAMatches(hash, []byte(r.AuthenticationSimple())) if valid { - state.bindDn = string(r.Name()) + groups, err := server.getAttribute(string(r.Name()), "memberOf") + if err != nil { + return ldap.LDAPResultOperationsError, err + } + state.login = Login{ + user: string(r.Name()), + groups: groups, + } return ldap.LDAPResultSuccess, nil } } @@ -368,7 +315,9 @@ func (server *Server) handleSearchInternal(state *State, w ldap.ResponseWriter, log.Printf("Request Attributes=%s", r.Attributes()) log.Printf("Request TimeLimit=%d", r.TimeLimit().Int()) - // TODO check authorizations + if !server.config.Acl.Check(&state.login, "read", string(r.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 { @@ -392,7 +341,7 @@ func (server *Server) handleSearchInternal(state *State, w ldap.ResponseWriter, log.Printf("%#v", entries) for dn, entry := range entries { - // TODO filter out if no permission to read this + // Filter out if we don't match requested filter matched, err := applyFilter(entry, r.Filter()) if err != nil { return ldap.LDAPResultUnwillingToPerform, err @@ -401,6 +350,11 @@ func (server *Server) handleSearchInternal(state *State, w ldap.ResponseWriter, continue } + // Filter out if user is not allowed to read this + if !server.config.Acl.Check(&state.login, "read", dn, []string{}) { + continue + } + e := ldap.NewSearchResultEntry(dn) for attr, val := range entry { // If attribute is not in request, exclude it from returned entry @@ -416,6 +370,10 @@ func (server *Server) handleSearchInternal(state *State, w ldap.ResponseWriter, continue } } + // If we are not allowed to read attribute, exclude it from returned entry + if !server.config.Acl.Check(&state.login, "read", dn, []string{attr}) { + continue + } // Send result for _, v := range val { e.AddAttribute(message.AttributeDescription(attr), @@ -507,6 +465,16 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in return ldap.LDAPResultInvalidDNSyntax, err } + // Check permissions + attrListStr := []string{} + for _, attribute := range r.Attributes() { + attrListStr = append(attrListStr, string(attribute.Type_())) + } + if !server.config.Acl.Check(&state.login, "add", dn, attrListStr) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + + // Check that object does not already exist exists, err := server.objectExists(dn) if err != nil { return ldap.LDAPResultOperationsError, err @@ -515,8 +483,9 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in return ldap.LDAPResultEntryAlreadyExists, nil } - // TODO check permissions + // Add object + // If adding a group, track of who the members will be so that their memberOf field can be updated later var members []string = nil entry := Entry{} @@ -617,8 +586,12 @@ func (server *Server) handleCompareInternal(state *State, r *message.CompareRequ return ldap.LDAPResultInvalidDNSyntax, err } - // TODO check user for permissions to read dn + // Check permissions + if !server.config.Acl.Check(&state.login, dn, "read", []string{attr}) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + // Do query exists, err := server.objectExists(dn) if err != nil { return ldap.LDAPResultOperationsError, err @@ -662,7 +635,10 @@ func (server *Server) handleDeleteInternal(state *State, r *message.DelRequest) return ldap.LDAPResultInvalidDNSyntax, err } - // TODO check user for permissions to write dn + // Check for delete permission + if !server.config.Acl.Check(&state.login, "delete", dn, []string{}) { + return ldap.LDAPResultInsufficientAccessRights, nil + } // Check that this LDAP entry exists and has no children path, err := dnToConsul(dn) @@ -746,7 +722,10 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques return ldap.LDAPResultInvalidDNSyntax, err } - // TODO check user for permissions to write dn + // First permission check with no particular attributes + if !server.config.Acl.Check(&state.login, "modify", dn, []string{}) { + return ldap.LDAPResultInsufficientAccessRights, nil + } // Retrieve previous values (by the way, check object exists) path, err := dnToConsul(dn) @@ -785,6 +764,11 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques attr := string(change.Modification().Type_()) values := change.Modification().Vals() + // Check for permission to modify this attribute + if !server.config.Acl.Check(&state.login, "modify", dn, []string{attr}) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + if change.Operation() == ldap.ModifyRequestChangeOperationAdd { newEntry[attr] = prevEntry[attr] for _, val := range values { |