aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2020-01-26 21:22:51 +0100
committerAlex Auvolat <alex@adnab.me>2020-01-26 21:22:51 +0100
commit97f5effe5506a8cbfbbb3fbe176929c188c361b3 (patch)
tree5919f8083444042411d2126e85de0103407e748a
parent8e4537d2ef7cad3428d1ca668568d928f2a472d3 (diff)
downloadbottin-97f5effe5506a8cbfbbb3fbe176929c188c361b3.tar.gz
bottin-97f5effe5506a8cbfbbb3fbe176929c188c361b3.zip
More serious schema enforcement
-rw-r--r--main.go63
-rw-r--r--util.go37
2 files changed, 85 insertions, 15 deletions
diff --git a/main.go b/main.go
index d55601b..d5dcdb4 100644
--- a/main.go
+++ b/main.go
@@ -1,7 +1,6 @@
package main
-// @FIXME: Auto populate entryuuid creatorsname createtimestamp modifiersname modifytimestamp (uuid: github.com/google/uuid, timestamps: 20060102150405Z)
-// @FIXME: Use memberof and not memberOf
+// @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
@@ -26,6 +25,14 @@ import (
const DEBUG = false
+const ATTR_USERPASSWORD = "userpassword"
+const ATTR_MEMBEROF = "memberof"
+const ATTR_ENTRYUUID = "entryuuid"
+const ATTR_CREATORSNAME = "creatorsname"
+const ATTR_CREATETIMESTAMP = "createtimestamp"
+const ATTR_MODIFIERSNAME = "modifiersname"
+const ATTR_MODIFYTIMESTAMP = "modifytimestamp"
+
type ConfigFile struct {
Suffix string `json:"suffix"`
BindAddress string `json:"bind_address"`
@@ -220,7 +227,7 @@ func (server *Server) init() error {
"objectClass": []string{"simpleSecurityObject", "organizationalRole"},
"description": []string{"LDAP administrator"},
"cn": []string{"admin"},
- "userpassword": []string{admin_pass_hash},
+ ATTR_USERPASSWORD: []string{admin_pass_hash},
"structuralObjectClass": []string{"organizationalRole"},
"permissions": []string{"read", "write"},
}
@@ -342,7 +349,7 @@ func (server *Server) handleBindInternal(state *State, r *message.BindRequest) (
}
// Try to retrieve password and check for match
- passwd, err := server.getAttribute(string(r.Name()), "userpassword")
+ passwd, err := server.getAttribute(string(r.Name()), ATTR_USERPASSWORD)
if err != nil {
return ldap.LDAPResultOperationsError, err
}
@@ -353,7 +360,7 @@ func (server *Server) handleBindInternal(state *State, r *message.BindRequest) (
for _, hash := range passwd {
valid := SSHAMatches(hash, []byte(r.AuthenticationSimple()))
if valid {
- groups, err := server.getAttribute(string(r.Name()), "memberOf")
+ groups, err := server.getAttribute(string(r.Name()), ATTR_MEMBEROF)
if err != nil {
return ldap.LDAPResultOperationsError, err
}
@@ -547,6 +554,11 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in
return ldap.LDAPResultInvalidDNSyntax, err
}
+ dnSplit, err := parseDN(dn)
+ if err != nil {
+ return ldap.LDAPResultInvalidDNSyntax, err
+ }
+
// Check permissions
attrListStr := []string{}
for _, attribute := range r.Attributes() {
@@ -579,9 +591,9 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in
}
// Fail if they are trying to write memberOf, we manage this ourselves
- if strings.EqualFold(key, "memberOf") {
- return ldap.LDAPResultObjectClassViolation, fmt.Errorf(
- "memberOf cannot be defined directly, membership must be specified in the group itself")
+ err = checkRestrictedAttr(key)
+ if err != nil {
+ return ldap.LDAPResultObjectClassViolation, err
}
// If they are writing a member key, we have to check they are adding valid members
if strings.EqualFold(key, "member") {
@@ -605,6 +617,11 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in
entry[key] = vals_str
}
+ entry[ATTR_CREATORSNAME] = []string{state.login.user}
+ entry[ATTR_CREATETIMESTAMP] = []string{genTimestamp()}
+ entry[ATTR_ENTRYUUID] = []string{genUuid()}
+ entry[dnSplit[0].Type] = []string{dnSplit[0].Value}
+
err = server.addElements(dn, entry)
if err != nil {
return ldap.LDAPResultOperationsError, err
@@ -612,7 +629,7 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in
if members != nil {
for _, member := range members {
- memberGroups, err := server.getAttribute(member, "memberOf")
+ memberGroups, err := server.getAttribute(member, ATTR_MEMBEROF)
if err != nil {
return ldap.LDAPResultOperationsError, err
}
@@ -633,7 +650,7 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in
if !alreadyMember {
memberGroups = append(memberGroups, dn)
err = server.addElements(member, Entry{
- "memberOf": memberGroups,
+ ATTR_MEMBEROF: memberGroups,
})
if err != nil {
return ldap.LDAPResultOperationsError, err
@@ -753,7 +770,7 @@ func (server *Server) handleDeleteInternal(state *State, r *message.DelRequest)
}
// Retrieve group membership before we delete everything
- memberOf, err := server.getAttribute(dn, "memberOf")
+ memberOf, err := server.getAttribute(dn, ATTR_MEMBEROF)
if err != nil {
return ldap.LDAPResultOperationsError, err
}
@@ -817,6 +834,11 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques
return ldap.LDAPResultInvalidDNSyntax, err
}
+ dnSplit, err := parseDN(dn)
+ if err != nil {
+ return ldap.LDAPResultInvalidDNSyntax, err
+ }
+
// First permission check with no particular attributes
if !server.config.Acl.Check(&state.login, "modify", dn, []string{}) {
return ldap.LDAPResultInsufficientAccessRights, nil
@@ -862,6 +884,14 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques
attr := string(change.Modification().Type_())
values := change.Modification().Vals()
+ err = checkRestrictedAttr(attr)
+ if err != nil {
+ return ldap.LDAPResultObjectClassViolation, err
+ }
+ if strings.EqualFold(attr, dnSplit[0].Type) {
+ return ldap.LDAPResultObjectClassViolation, fmt.Errorf("%s may not be changed as it is part of object path", attr)
+ }
+
// Check for permission to modify this attribute
if !server.config.Acl.Check(&state.login, "modify", dn, []string{attr}) {
return ldap.LDAPResultInsufficientAccessRights, nil
@@ -957,12 +987,15 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques
}
}
+ newEntry[ATTR_MODIFIERSNAME] = []string{state.login.user}
+ newEntry[ATTR_MODIFYTIMESTAMP] = []string{genTimestamp()}
+
// Save the edited values
server.addElements(dn, newEntry)
// Update memberOf for added members and deleted members
for _, addMem := range addMembers {
- memberOf, err := server.getAttribute(addMem, "memberOf")
+ memberOf, err := server.getAttribute(addMem, ATTR_MEMBEROF)
if err != nil {
return ldap.LDAPResultOperationsError, err
}
@@ -970,14 +1003,14 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques
memberOf = []string{}
}
memberOf = append(memberOf, dn)
- err = server.addElements(addMem, Entry{"memberOf": memberOf})
+ err = server.addElements(addMem, Entry{ATTR_MEMBEROF: memberOf})
if err != nil {
return ldap.LDAPResultOperationsError, err
}
}
for _, delMem := range delMembers {
- memberOf, err := server.getAttribute(delMem, "memberOf")
+ memberOf, err := server.getAttribute(delMem, ATTR_MEMBEROF)
if err != nil {
return ldap.LDAPResultOperationsError, err
}
@@ -991,7 +1024,7 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques
}
}
- err = server.addElements(delMem, Entry{"memberOf": newMemberOf})
+ err = server.addElements(delMem, Entry{ATTR_MEMBEROF: newMemberOf})
if err != nil {
return ldap.LDAPResultOperationsError, err
}
diff --git a/util.go b/util.go
index 014805c..d7c42d7 100644
--- a/util.go
+++ b/util.go
@@ -3,8 +3,11 @@ package main
import (
"encoding/json"
"fmt"
+ "log"
"strings"
+ "time"
+ uuid "github.com/google/uuid"
consul "github.com/hashicorp/consul/api"
)
@@ -101,3 +104,37 @@ func parseDN(dn string) ([]DNComponent, error) {
}
return ret, nil
}
+
+func checkRestrictedAttr(attr string) error {
+ RESTRICTED_ATTRS := []string{
+ ATTR_MEMBEROF,
+ ATTR_ENTRYUUID,
+ ATTR_CREATORSNAME,
+ ATTR_CREATETIMESTAMP,
+ ATTR_MODIFIERSNAME,
+ ATTR_MODIFYTIMESTAMP,
+ }
+
+ if strings.EqualFold(attr, ATTR_MEMBEROF) {
+ return fmt.Errorf("memberOf cannot be defined directly, membership must be specified in the group itself")
+ }
+
+ for _, s := range RESTRICTED_ATTRS {
+ if strings.EqualFold(attr, s) {
+ return fmt.Errorf("Attribute %s is restricted and may only be set by the system", s)
+ }
+ }
+ return nil
+}
+
+func genTimestamp() string {
+ return time.Now().Format("20060102150405Z")
+}
+
+func genUuid() string {
+ uuid, err := uuid.NewRandom()
+ if err != nil {
+ log.Panicf("UUID generation error: %s", err)
+ }
+ return uuid.String()
+}