package main import ( "github.com/go-ldap/ldap/v3" "fmt" log "github.com/sirupsen/logrus" "math/rand" "strings" "errors" "os" ) const bindusername = "cn=admin,dc=deuxfleurs,dc=fr" const adresse = "127.0.0.1" const port = 1389 var bindpassword string var all_names = make(map[string]struct{}) func printError(LDAPError error) { if LDAPError != nil { log.Fatal(LDAPError) } } func createOU(l *ldap.Conn) error { req := ldap.NewAddRequest("ou=groups,dc=deuxfleurs,dc=fr",nil) req.Attribute("description",[]string{"OrganizationalUnit qui regroupe tous les groupes"}) req.Attribute("objectclass",[]string{"organizationalUnit", "top"}) req.Attribute("ou",[]string{"groups"}) req.Attribute("structuralobjectclass", []string{"organizationalUnit"}) err := l.Add(req) if err != nil { return err } req = ldap.NewAddRequest("ou=users,dc=deuxfleurs,dc=fr",nil) req.Attribute("description",[]string{"OrganizationalUnit qui regroupe tous les utilisateurs"}) req.Attribute("objectclass",[]string{"organizationalUnit", "top"}) req.Attribute("ou",[]string{"users"}) req.Attribute("structuralobjectclass", []string{"organizationalUnit"}) err = l.Add(req) return err } func generateName(r *rand.Rand) (name string) { for only_one := true; only_one; _, only_one = all_names[name]{ name = fmt.Sprintf("%d",r.Int()) } all_names[name] = struct{}{} log.Debug(fmt.Sprintf("Name generated: %s.\n", name)) return } func createGroup(r *rand.Rand, l *ldap.Conn) (tab_AddRequest []ldap.AddRequest, err error) { StructuralObjectClass := []string{"groupOfNames"} ObjectClass := []string{"groupOfNames","top"} for i := 0; i<20; i++ { //Generate name and check if he is unique name := generateName(r) req := ldap.NewAddRequest(fmt.Sprintf("cn=%s,ou=groups,dc=deuxfleurs,dc=fr",name),nil) req.Attribute("description",[]string{generateName(r)}) req.Attribute("objectclass",ObjectClass) req.Attribute("structuralobjectclass",StructuralObjectClass) err = l.Add(req) if err != nil { log.Warn(fmt.Sprintf("Erreur survenue sur la création du [%d] groupe.\n",i)) return nil, err } tab_AddRequest = append(tab_AddRequest, *req) } return } func createUser(r *rand.Rand, l *ldap.Conn) (tab_AddRequest []ldap.AddRequest, err error) { StructuralObjectClass := []string{"inetOrgPerson"} ObjectClass := []string{"inetOrgPerson","organizationalPerson","person","top"} for i := 0; i<20; i++ { name := generateName(r) req := ldap.NewAddRequest(fmt.Sprintf("cn=%s,ou=users,dc=deuxfleurs,dc=fr",name),nil) req.Attribute("displayname",[]string{generateName(r)}) req.Attribute("objectclass",ObjectClass) req.Attribute("structuralobjectclass",StructuralObjectClass) err = l.Add(req) if err != nil { log.Warn(fmt.Sprintf("Erreur survenue sur la création du [%d] user.\n",i)) return nil, err } tab_AddRequest = append(tab_AddRequest, *req) } return } func search_attributes(tab_Attributes []ldap.Attribute, tipe string) (*ldap.Attribute) { for _,att := range tab_Attributes { if att.Type == tipe { return &att } } return nil // return ldap.Attribute{} } func test_attributes(l *ldap.Conn, tab_AddRequest []ldap.AddRequest, filter_objectclass, user_or_group string) (err error) { for _, addRequest := range tab_AddRequest { //On prend le cn en supposant qu'il est unique cn := strings.Split(addRequest.DN,",")[0] //On crée la requête pour la recherche search_req := ldap.NewSearchRequest( fmt.Sprintf("ou=%s,dc=deuxfleurs,dc=fr",user_or_group), ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectclass=%s)(%s))", filter_objectclass,cn), []string{"displayname","objectclass","structuralobjectclass"}, nil, ) //On lance la recherche result, err := l.Search(search_req) if err != nil { return err } if len(result.Entries) != 1 { return errors.New("Test a trouvé plusieurs displaynames en commun ou en a trouvé aucun") } //On compare les attributs qu'on a reçu avec les attributs qu'on a envoyé result_attributes := result.Entries[0].Attributes log.Debug(fmt.Sprintf("La longueur est de %d, contient : \n %s.\n",len(result_attributes), result_attributes)) //Notre recherche crée un attribut par valeur, même si les valeurs viennent du même nom d'attribut //Par exemple: objectclass possède 4 valeurs. Alors on aura 4 EntryAttribute qui contient chacune une des 4 valeurs de l'attribut objectclass //j est l'indice qui représente la j-ème valeur de notre attribut var j int var att *ldap.Attribute for i,attributes := range result_attributes { //On cherche l'attribut de l'user i qui a le même nom que celui qu'on a reçu et qu'on traite dans cette boucle if j == 0 { att = search_attributes(addRequest.Attributes, attributes.Name) if att == nil { return errors.New(fmt.Sprintf("Error: test_attributes - Don't find match name attributes. We search %s.\n", attributes.Name)) } } log.Debug(fmt.Sprintf("Le nom de l'attribut est %s, sa valeur est: \n %s.", att.Type, att)) if j >= len(att.Vals) || att.Vals[j] != attributes.Values[0] { return errors.New(fmt.Sprintf("Error: test_attributes - Theses values aren't the same: %d, %d",att.Vals, attributes.Values)) } if i+1 < len(result_attributes) && result_attributes[i+1].Name == attributes.Name { j += 1 } else { j = 0} } } return nil } func clean(l *ldap.Conn, AddReq_users, AddReq_groups []ldap.AddRequest,user, group bool) (err error){ log.Debug("Debut clean") if(user) { for _,req := range AddReq_users { delReq := ldap.NewDelRequest(req.DN,nil) err = l.Del(delReq) if err != nil { return } } } if group { for _,req := range AddReq_groups { delReq := ldap.NewDelRequest(req.DN, nil) err = l.Del(delReq) if err != nil { return } } } defer log.Debug("Fin clean") return } func test_modify_attributes(l *ldap.Conn, r *rand.Rand, tab_AddReq []ldap.AddRequest, tab_type_name []string) (err error) { for _, AddReq := range tab_AddReq { modReq := ldap.NewModifyRequest(AddReq.DN,nil) for _, type_name := range tab_type_name { newName := generateName(r) modReq.Replace(type_name, []string{newName}) att := search_attributes(AddReq.Attributes, type_name) att.Vals[0] = newName } err = l.Modify(modReq) if err != nil { return } } return } func add_user_in_groups(l *ldap.Conn, r *rand.Rand, users, groups []ldap.AddRequest) (err error) { for _,group := range groups { numberUsers := r.Intn(19) + 1 //Always a minimum of 1 user list_users := []string{} for i:=0; i < numberUsers; i++ { list_users = append(list_users, users[i].DN) } modifyReq := ldap.NewModifyRequest( group.DN, nil) modifyReq.Add("member", list_users) err = l.Modify(modifyReq) if err != nil { log.Warn(fmt.Sprintf("Error: ModifyReq failed, func:add_users_in_groups from group:\n %d",group)) return } } return } func delete_groups(l *ldap.Conn, groups []ldap.AddRequest) (list map[string][]string ,err error) { list = make(map[string][]string) for _, group := range groups { //Get lists_users cn := strings.Split(group.DN,",")[0] search_req := ldap.NewSearchRequest( "ou=groups,dc=deuxfleurs,dc=fr", ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectclass=groupOfNames)(%s))",cn), []string{"member"}, nil, ) res , err := l.Search(search_req) if err != nil { log.Warn(fmt.Sprintf("Error Search: func: delete_groups_and_check_memberOf, from group: \n %d", group)) return list, err } if len(res.Entries) != 1 { err = errors.New(fmt.Sprintf("SearchResult get: %s, SearchResult wanted: 1", len(res.Entries))) return list, err } EntryAtt := res.Entries[0].Attributes list_users := []string{} for _, att := range EntryAtt { list_users = append(list_users ,att.Values[0]) } //Del group del := ldap.NewDelRequest( group.DN, nil) err = l.Del(del) if err != nil { return list, err } list[group.DN] = list_users } return } func check_memberOf(l *ldap.Conn, list map[string][]string) (err error) { //Check the memberOf of all users for groupeDN,_ := range list{ search_req := ldap.NewSearchRequest( "ou=users,dc=deuxfleurs,dc=fr", ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,0 ,0, false, fmt.Sprintf("(&(objectclass=inetOrgPerson)(memberOf=%s))",groupeDN), []string{"cn"}, nil, ) res, err := l.Search(search_req) if err != nil { return err } if len(res.Entries) != 0 { err = errors.New(fmt.Sprintf("L'user '%s' a encore le DN d'un groupe supprimé: %s",res.Entries[0].Attributes[0].Values[0],groupeDN)) return err } } return err } func reconnect(l *ldap.Conn) (l_nouv *ldap.Conn, err error){ l.Close() l_nouv, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d",adresse,port)) if err != nil { return } err = l_nouv.Bind(bindusername, bindpassword) return } func main() { var ok bool bindpassword, ok = os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW") if !ok { if len(os.Args) == 2 { bindpassword = os.Args[1] } else { bindpassword = "" } } log.Info(fmt.Sprintf("Password selected: %s",bindpassword)) //log.SetLevel(log.TraceLevel) //Create a connection with Bottin server l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", adresse, port)) //l.Debug = true printError(err) //Bind with the admin account generated err = l.Bind(bindusername, bindpassword) printError(err) //Create our object Rand, it's important to always have the same values source := rand.NewSource(666475745) r := rand.New(source) log.Info(fmt.Sprintf("The seed of the rand object is %d.\n",r.Seed)) //Create user and groups OrgaUnit err = createOU(l) if ldap.IsErrorWithCode(err, uint16(68)) { log.Warn("Les OrganizationalUnit users et groups sont déjà présents.") }else { printError(err) log.Info("Création des OU de groups et users") } //Create random groups tab_AddRequest_groups, err := createGroup(r, l) printError(err) log.Info(fmt.Sprintf("Création des groupes aléatoirement réussi: %d\n", len(tab_AddRequest_groups))) //Create random users tab_AddRequest_users, err := createUser(r, l) printError(err) log.Info(fmt.Sprintf("Création des users aléatoirement réussi: %d\n", len(tab_AddRequest_users))) //Search and compare attribute Users. (We keep Attribute object from 'Create random users' and compare with the result of our search) err = test_attributes(l,tab_AddRequest_users, "inetOrgPerson","users") printError(err) log.Info("Tous les attributs users insérés dans Consul ont été vérifiés..\n") //Search and compare attributes Groups err = test_attributes(l,tab_AddRequest_groups, "groupOfNames","groups") printError(err) log.Info("Tous les attributs groups insérés dans Consul ont été vérifiés.\n") //Close the connection and open an other. If we don't do this, bottin server send a wrong answer. Comment this part if you want to try this l,err = reconnect(l) printError(err) //Modify attributes users and groups. //Modify users' attributes and check them log.Debug(fmt.Sprintf("Les valeurs sont:\n %s", tab_AddRequest_users)) err = test_modify_attributes(l, r, tab_AddRequest_users, []string{"displayname"}) printError(err) log.Debug("Modifications users faites") //Check if the attributes are correct: err = test_attributes(l,tab_AddRequest_users, "inetOrgPerson", "users") printError(err) log.Info("Les modifications ont bien été prises en compte") log.Debug(fmt.Sprintf("Les nouvelles valeurs sont:\n %s", tab_AddRequest_users)) //Modify users' attributes and check them err = test_modify_attributes(l, r, tab_AddRequest_groups, []string{"description"}) printError(err) log.Info("Modifications groups faites") //Check if the attributes are correct: err = test_attributes(l,tab_AddRequest_groups, "groupOfNames", "groups") printError(err) log.Info("Les modifications ont bien été prises en compte") //Close the connection l, err = reconnect(l) printError(err) //Add users in group, search them, delete several samples and search again to be sur it's good err = add_user_in_groups(l, r, tab_AddRequest_users, tab_AddRequest_groups) printError(err) log.Info("Ajout d'users dans les groupes fait") //Close the connection l, err = reconnect(l) printError(err) list, err := delete_groups(l, tab_AddRequest_groups) printError(err) log.Info("groupe supprimé") l,err = reconnect(l) printError(err) err = check_memberOf(l, list) printError(err) log.Info("Le memberOf a été correctement vidé") //Clean: Delete all users and groups (not OU users and groups) err = clean(l, tab_AddRequest_users, tab_AddRequest_groups, true, false) printError(err) log.Info("Clean succes") defer os.Exit(0) return }