forked from Deuxfleurs/bottin
More serious schema enforcement
This commit is contained in:
parent
8e4537d2ef
commit
97f5effe55
2 changed files with 85 additions and 15 deletions
63
main.go
63
main.go
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// @FIXME: Auto populate entryuuid creatorsname createtimestamp modifiersname modifytimestamp (uuid: github.com/google/uuid, timestamps: 20060102150405Z)
|
// @FIXME: Proper handling of various upper/lower case combinations
|
||||||
// @FIXME: Use memberof and not memberOf
|
|
||||||
// @FIXME: Implement missing search filters (in applyFilter)
|
// @FIXME: Implement missing search filters (in applyFilter)
|
||||||
// @FIXME: Add an initial prefix to the consul key value
|
// @FIXME: Add an initial prefix to the consul key value
|
||||||
|
|
||||||
|
@ -26,6 +25,14 @@ import (
|
||||||
|
|
||||||
const DEBUG = false
|
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 {
|
type ConfigFile struct {
|
||||||
Suffix string `json:"suffix"`
|
Suffix string `json:"suffix"`
|
||||||
BindAddress string `json:"bind_address"`
|
BindAddress string `json:"bind_address"`
|
||||||
|
@ -220,7 +227,7 @@ func (server *Server) init() error {
|
||||||
"objectClass": []string{"simpleSecurityObject", "organizationalRole"},
|
"objectClass": []string{"simpleSecurityObject", "organizationalRole"},
|
||||||
"description": []string{"LDAP administrator"},
|
"description": []string{"LDAP administrator"},
|
||||||
"cn": []string{"admin"},
|
"cn": []string{"admin"},
|
||||||
"userpassword": []string{admin_pass_hash},
|
ATTR_USERPASSWORD: []string{admin_pass_hash},
|
||||||
"structuralObjectClass": []string{"organizationalRole"},
|
"structuralObjectClass": []string{"organizationalRole"},
|
||||||
"permissions": []string{"read", "write"},
|
"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
|
// 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 {
|
if err != nil {
|
||||||
return ldap.LDAPResultOperationsError, err
|
return ldap.LDAPResultOperationsError, err
|
||||||
}
|
}
|
||||||
|
@ -353,7 +360,7 @@ func (server *Server) handleBindInternal(state *State, r *message.BindRequest) (
|
||||||
for _, hash := range passwd {
|
for _, hash := range passwd {
|
||||||
valid := SSHAMatches(hash, []byte(r.AuthenticationSimple()))
|
valid := SSHAMatches(hash, []byte(r.AuthenticationSimple()))
|
||||||
if valid {
|
if valid {
|
||||||
groups, err := server.getAttribute(string(r.Name()), "memberOf")
|
groups, err := server.getAttribute(string(r.Name()), ATTR_MEMBEROF)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ldap.LDAPResultOperationsError, err
|
return ldap.LDAPResultOperationsError, err
|
||||||
}
|
}
|
||||||
|
@ -547,6 +554,11 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in
|
||||||
return ldap.LDAPResultInvalidDNSyntax, err
|
return ldap.LDAPResultInvalidDNSyntax, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dnSplit, err := parseDN(dn)
|
||||||
|
if err != nil {
|
||||||
|
return ldap.LDAPResultInvalidDNSyntax, err
|
||||||
|
}
|
||||||
|
|
||||||
// Check permissions
|
// Check permissions
|
||||||
attrListStr := []string{}
|
attrListStr := []string{}
|
||||||
for _, attribute := range r.Attributes() {
|
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
|
// Fail if they are trying to write memberOf, we manage this ourselves
|
||||||
if strings.EqualFold(key, "memberOf") {
|
err = checkRestrictedAttr(key)
|
||||||
return ldap.LDAPResultObjectClassViolation, fmt.Errorf(
|
if err != nil {
|
||||||
"memberOf cannot be defined directly, membership must be specified in the group itself")
|
return ldap.LDAPResultObjectClassViolation, err
|
||||||
}
|
}
|
||||||
// If they are writing a member key, we have to check they are adding valid members
|
// If they are writing a member key, we have to check they are adding valid members
|
||||||
if strings.EqualFold(key, "member") {
|
if strings.EqualFold(key, "member") {
|
||||||
|
@ -605,6 +617,11 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in
|
||||||
entry[key] = vals_str
|
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)
|
err = server.addElements(dn, entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ldap.LDAPResultOperationsError, err
|
return ldap.LDAPResultOperationsError, err
|
||||||
|
@ -612,7 +629,7 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in
|
||||||
|
|
||||||
if members != nil {
|
if members != nil {
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
memberGroups, err := server.getAttribute(member, "memberOf")
|
memberGroups, err := server.getAttribute(member, ATTR_MEMBEROF)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ldap.LDAPResultOperationsError, err
|
return ldap.LDAPResultOperationsError, err
|
||||||
}
|
}
|
||||||
|
@ -633,7 +650,7 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in
|
||||||
if !alreadyMember {
|
if !alreadyMember {
|
||||||
memberGroups = append(memberGroups, dn)
|
memberGroups = append(memberGroups, dn)
|
||||||
err = server.addElements(member, Entry{
|
err = server.addElements(member, Entry{
|
||||||
"memberOf": memberGroups,
|
ATTR_MEMBEROF: memberGroups,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ldap.LDAPResultOperationsError, err
|
return ldap.LDAPResultOperationsError, err
|
||||||
|
@ -753,7 +770,7 @@ func (server *Server) handleDeleteInternal(state *State, r *message.DelRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve group membership before we delete everything
|
// Retrieve group membership before we delete everything
|
||||||
memberOf, err := server.getAttribute(dn, "memberOf")
|
memberOf, err := server.getAttribute(dn, ATTR_MEMBEROF)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ldap.LDAPResultOperationsError, err
|
return ldap.LDAPResultOperationsError, err
|
||||||
}
|
}
|
||||||
|
@ -817,6 +834,11 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques
|
||||||
return ldap.LDAPResultInvalidDNSyntax, err
|
return ldap.LDAPResultInvalidDNSyntax, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dnSplit, err := parseDN(dn)
|
||||||
|
if err != nil {
|
||||||
|
return ldap.LDAPResultInvalidDNSyntax, err
|
||||||
|
}
|
||||||
|
|
||||||
// First permission check with no particular attributes
|
// First permission check with no particular attributes
|
||||||
if !server.config.Acl.Check(&state.login, "modify", dn, []string{}) {
|
if !server.config.Acl.Check(&state.login, "modify", dn, []string{}) {
|
||||||
return ldap.LDAPResultInsufficientAccessRights, nil
|
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||||
|
@ -862,6 +884,14 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques
|
||||||
attr := string(change.Modification().Type_())
|
attr := string(change.Modification().Type_())
|
||||||
values := change.Modification().Vals()
|
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
|
// Check for permission to modify this attribute
|
||||||
if !server.config.Acl.Check(&state.login, "modify", dn, []string{attr}) {
|
if !server.config.Acl.Check(&state.login, "modify", dn, []string{attr}) {
|
||||||
return ldap.LDAPResultInsufficientAccessRights, nil
|
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
|
// Save the edited values
|
||||||
server.addElements(dn, newEntry)
|
server.addElements(dn, newEntry)
|
||||||
|
|
||||||
// Update memberOf for added members and deleted members
|
// Update memberOf for added members and deleted members
|
||||||
for _, addMem := range addMembers {
|
for _, addMem := range addMembers {
|
||||||
memberOf, err := server.getAttribute(addMem, "memberOf")
|
memberOf, err := server.getAttribute(addMem, ATTR_MEMBEROF)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ldap.LDAPResultOperationsError, err
|
return ldap.LDAPResultOperationsError, err
|
||||||
}
|
}
|
||||||
|
@ -970,14 +1003,14 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques
|
||||||
memberOf = []string{}
|
memberOf = []string{}
|
||||||
}
|
}
|
||||||
memberOf = append(memberOf, dn)
|
memberOf = append(memberOf, dn)
|
||||||
err = server.addElements(addMem, Entry{"memberOf": memberOf})
|
err = server.addElements(addMem, Entry{ATTR_MEMBEROF: memberOf})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ldap.LDAPResultOperationsError, err
|
return ldap.LDAPResultOperationsError, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, delMem := range delMembers {
|
for _, delMem := range delMembers {
|
||||||
memberOf, err := server.getAttribute(delMem, "memberOf")
|
memberOf, err := server.getAttribute(delMem, ATTR_MEMBEROF)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ldap.LDAPResultOperationsError, err
|
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 {
|
if err != nil {
|
||||||
return ldap.LDAPResultOperationsError, err
|
return ldap.LDAPResultOperationsError, err
|
||||||
}
|
}
|
||||||
|
|
37
util.go
37
util.go
|
@ -3,8 +3,11 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
uuid "github.com/google/uuid"
|
||||||
consul "github.com/hashicorp/consul/api"
|
consul "github.com/hashicorp/consul/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -101,3 +104,37 @@ func parseDN(dn string) ([]DNComponent, error) {
|
||||||
}
|
}
|
||||||
return ret, nil
|
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()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue