From a98556d5c1241f9150202d72ea096a775d97a582 Mon Sep 17 00:00:00 2001 From: MrArmonius Date: Wed, 7 Jul 2021 01:49:33 +0200 Subject: Test End-to-end V1.0, testing Bottin's behavior Tests wrote in golang without framework Testing on the Bottin's behavior Tests made: - crated random Users and Group - LDAP ADD - check the match between Consul's data and Test's data- LDAP Search - modify attributes and check them - LDAP Modify --- .drone.yml | 25 +- .gitignore | 1 + go.mod | 1 + go.sum | 14 ++ main.go | 17 +- test_automatic/Scan_Bad_Packets.pcapng | Bin 0 -> 646080 bytes test_automatic/Scan_Good_Packets.pcapng | Bin 0 -> 1303556 bytes test_automatic/go.mod | 8 + test_automatic/go.sum | 23 ++ test_automatic/integration.go | 430 ++++++++++++++++++++++++++++++++ test_automatic/rapport_beug_bottin.txt | 40 +++ test_automatic/start_test.sh | 11 + 12 files changed, 558 insertions(+), 12 deletions(-) create mode 100644 test_automatic/Scan_Bad_Packets.pcapng create mode 100644 test_automatic/Scan_Good_Packets.pcapng create mode 100644 test_automatic/go.mod create mode 100644 test_automatic/go.sum create mode 100644 test_automatic/integration.go create mode 100644 test_automatic/rapport_beug_bottin.txt create mode 100755 test_automatic/start_test.sh diff --git a/.drone.yml b/.drone.yml index 9eea880..b83a87d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,13 +1,24 @@ --- -pipeline: - build: - image: golang:stretch - commands: - - go get -d -v - - go build -v +kind: pipeline +name: bottin + +steps: +- name: build + image: golang:stretch + commands: + - go get -d -v + - go build -v + - cd test_automatic + - go get -d -v + - go build -v + +- name: test_bottin + image: consul:latest + commands: + - ./test_automatic/start_test.sh --- kind: signature -hmac: 8f49fdf0e4abb0790827eed7cac8eedd5e11705d1fa01954a84929933eb7b254 +hmac: 939fca00ff84d40e9364cd936c18c40c5becafa05e0f887bc04cf6336a4913a2 ... diff --git a/.gitignore b/.gitignore index d739b6f..9edb3e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ bottin bottin.static config.json +test_automatic/integration diff --git a/go.mod b/go.mod index 75156e0..22c2023 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module bottin go 1.13 require ( + github.com/go-ldap/ldap/v3 v3.3.0 github.com/google/uuid v1.1.1 github.com/hashicorp/consul/api v1.3.0 github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 diff --git a/go.sum b/go.sum index c2db0e1..e4825c4 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -7,6 +9,11 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= +github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap v2.5.1+incompatible h1:Opaoft5zMW8IU/VRULB0eGMBQ9P5buRvCW6sFTRmMn8= +github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ= +github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= @@ -79,12 +86,19 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/main.go b/main.go index 13d3da6..91c3bbd 100644 --- a/main.go +++ b/main.go @@ -320,12 +320,19 @@ func (server *Server) init() error { return err } - admin_pass := make([]byte, 8) - _, err = rand.Read(admin_pass) - if err != nil { - return err + + admin_pass_str, environnement_variable_exist := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW") + if !environnement_variable_exist { + admin_pass := make([]byte, 8) + _, err = rand.Read(admin_pass) + if err != nil { + return err + } + admin_pass_str = base64.RawURLEncoding.EncodeToString(admin_pass) + } else { + server.logger.Printf("It seems that exists a password in environnement variable") } - admin_pass_str := base64.RawURLEncoding.EncodeToString(admin_pass) + admin_pass_hash := SSHAEncode([]byte(admin_pass_str)) admin_dn := "cn=admin," + server.config.Suffix diff --git a/test_automatic/Scan_Bad_Packets.pcapng b/test_automatic/Scan_Bad_Packets.pcapng new file mode 100644 index 0000000..cc209f5 Binary files /dev/null and b/test_automatic/Scan_Bad_Packets.pcapng differ diff --git a/test_automatic/Scan_Good_Packets.pcapng b/test_automatic/Scan_Good_Packets.pcapng new file mode 100644 index 0000000..5c87f82 Binary files /dev/null and b/test_automatic/Scan_Good_Packets.pcapng differ diff --git a/test_automatic/go.mod b/test_automatic/go.mod new file mode 100644 index 0000000..74ed1ce --- /dev/null +++ b/test_automatic/go.mod @@ -0,0 +1,8 @@ +module bottin/integration + +go 1.14 + +require ( + github.com/go-ldap/ldap/v3 v3.3.0 + github.com/sirupsen/logrus v1.4.2 +) diff --git a/test_automatic/go.sum b/test_automatic/go.sum new file mode 100644 index 0000000..8e94420 --- /dev/null +++ b/test_automatic/go.sum @@ -0,0 +1,23 @@ +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= +github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ= +github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/test_automatic/integration.go b/test_automatic/integration.go new file mode 100644 index 0000000..e5a4737 --- /dev/null +++ b/test_automatic/integration.go @@ -0,0 +1,430 @@ +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") + + return + +} diff --git a/test_automatic/rapport_beug_bottin.txt b/test_automatic/rapport_beug_bottin.txt new file mode 100644 index 0000000..1669e14 --- /dev/null +++ b/test_automatic/rapport_beug_bottin.txt @@ -0,0 +1,40 @@ +Introduction et Observation premières: + + Lors de la réalisation de mon code Go, j'ai trouvé un beug qui provoquait l'arrêt de mon programme car Bottin envoyé une réponse erronée à mon programme. + Dans logs de Bottin je ne voyais aucune erreur, Bottin n'avait pas cessé de fonctionner. Il s'arrêtait à chaque fois sur mes requêtes Del (mes dernières). + + +Reproduction du beug: + + Pour reproduire le beug, il suffit de lancer le programme interrogation.go et de commenter les lignes suivantes : + +260 //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 +261 l.Close() +262 l, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d",adresse, port)) +263 printError(err) +264 err = l.Bind(bindusername, bindpassword) +265 printError(err) + + Ainsi on obtient l'erreur suivante: + +2021/07/07 01:25:51 Received unexpected message -128, false + + +Test réalisé pour comprendre la source du problème: + + Ma première hypothèses fut que j'envoyais trop de requêtes dans un court laps de temps et ainsi Bottin ou Consul ne pouvait pas suivre. +J'ai placé un sleep de 50 puis de 100 Millisecondes entre chaque requête, j'obtenais toujours la même erreur. +J'ai essayé de mettre un sleep de 10 secondes avant mes requêtes de suppression mais j'obtenais toujours la même chose. + +Hack pour résoudre: + + La première solution qui a fonctionné était de réduire le nombre de requêtes en n'exécutant pas certains tests random. + La dernière solutions qui est utilisée: + Fermée la connexion puis la réouvrir permet de palier à ce problème + +Hypothèses du problème ?: + + Existerait-il un Buffer par Bind qui se remplirait ? Et lorsque celui-ci est plein, ne renvoie pas d'erreur juste n'arrive plus à répondre. + +Erwan DUFOUR +Deuxfleurs diff --git a/test_automatic/start_test.sh b/test_automatic/start_test.sh new file mode 100755 index 0000000..38fe706 --- /dev/null +++ b/test_automatic/start_test.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +trap "kill 0" EXIT + +export BOTTIN_DEFAULT_ADMIN_PW=$(openssl rand -base64 24) +echo $BOTTIN_DEFAULT_ADMIN_PW +consul agent -dev > /dev/null 2>&1 & +sleep 2 +./bottin > /dev/null 2>&1 & +sleep 1 +./test_automatic/integration -- cgit v1.2.3 From da627ac39ad437bfb6be7e5f343933a0ca4e5073 Mon Sep 17 00:00:00 2001 From: MrArmonius Date: Fri, 9 Jul 2021 15:47:54 +0200 Subject: Script in ash to launch our test V1.0 (end-to-end) in a Consul's container --- .drone.yml | 6 ++++-- test_automatic/config.json.test | 13 +++++++++++++ test_automatic/integration.go | 1 + test_automatic/start_test.sh | 9 +++++---- 4 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 test_automatic/config.json.test diff --git a/.drone.yml b/.drone.yml index b83a87d..8a79d64 100644 --- a/.drone.yml +++ b/.drone.yml @@ -14,11 +14,13 @@ steps: - name: test_bottin image: consul:latest + environment: + BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1 commands: - - ./test_automatic/start_test.sh + - ash test_automatic/start_test.sh --- kind: signature -hmac: 939fca00ff84d40e9364cd936c18c40c5becafa05e0f887bc04cf6336a4913a2 +hmac: a4455c124ee87ca8b0ef1779560703573f3a3f24d406e4cb281b9e0dab4ceeda ... diff --git a/test_automatic/config.json.test b/test_automatic/config.json.test new file mode 100644 index 0000000..bc1eeec --- /dev/null +++ b/test_automatic/config.json.test @@ -0,0 +1,13 @@ +{ + "suffix": "dc=deuxfleurs,dc=fr", + "bind": "127.0.0.1:1389", + "acl": [ + "ANONYMOUS::bind:*,ou=users,dc=deuxfleurs,dc=fr:", + "ANONYMOUS::bind:cn=admin,dc=deuxfleurs,dc=fr:", + "*,dc=deuxfleurs,dc=fr::read:*:* !userpassword", + "*::read modify:SELF:*", + "cn=admin,dc=deuxfleurs,dc=fr::read add modify delete:*:*", + "*:cn=admin,ou=groups,dc=deuxfleurs,dc=fr:read add modify delete:*:*" + ] +} + diff --git a/test_automatic/integration.go b/test_automatic/integration.go index e5a4737..d55d280 100644 --- a/test_automatic/integration.go +++ b/test_automatic/integration.go @@ -425,6 +425,7 @@ func main() { printError(err) log.Info("Clean succes") + defer os.Exit(0) return } diff --git a/test_automatic/start_test.sh b/test_automatic/start_test.sh index 38fe706..e7a1712 100755 --- a/test_automatic/start_test.sh +++ b/test_automatic/start_test.sh @@ -1,11 +1,12 @@ -#!/bin/bash +#!/bin/sh -trap "kill 0" EXIT - -export BOTTIN_DEFAULT_ADMIN_PW=$(openssl rand -base64 24) +#export BOTTIN_DEFAULT_ADMIN_PW=$(openssl rand -base64 24) echo $BOTTIN_DEFAULT_ADMIN_PW consul agent -dev > /dev/null 2>&1 & sleep 2 +cp test_automatic/config.json.test config.json ./bottin > /dev/null 2>&1 & sleep 1 ./test_automatic/integration +rm config.json +exit 0 -- cgit v1.2.3 From 9a8c19ec0f9b2f09daab244a49c67904c5c086aa Mon Sep 17 00:00:00 2001 From: MrArmonius Date: Fri, 16 Jul 2021 16:56:56 +0200 Subject: Bottin's Test V2.0 with Framework Testing V2 the test end-to-end, Tests made similar to V1.0, Add the possibility to pararellize the tests, Create an environnement for easy integration of news test, --- .drone.yml | 9 +- .gitignore | 2 +- main.go | 2 +- test/bottin_test.go | 160 ++++++++++++ test/config.json.test | 13 + test/create.go | 281 +++++++++++++++++++++ test/functionTest.go | 173 +++++++++++++ test/go.mod | 8 + test/go.sum | 26 ++ test/handler.go | 138 ++++++++++ test/request.go | 67 +++++ test/runner.sh | 12 + test_automatic/Scan_Bad_Packets.pcapng | Bin 646080 -> 0 bytes test_automatic/Scan_Good_Packets.pcapng | Bin 1303556 -> 0 bytes test_automatic/config.json.test | 13 - test_automatic/go.mod | 8 - test_automatic/go.sum | 23 -- test_automatic/integration.go | 431 -------------------------------- test_automatic/rapport_beug_bottin.txt | 40 --- test_automatic/start_test.sh | 12 - 20 files changed, 884 insertions(+), 534 deletions(-) create mode 100644 test/bottin_test.go create mode 100644 test/config.json.test create mode 100644 test/create.go create mode 100644 test/functionTest.go create mode 100644 test/go.mod create mode 100644 test/go.sum create mode 100644 test/handler.go create mode 100644 test/request.go create mode 100755 test/runner.sh delete mode 100644 test_automatic/Scan_Bad_Packets.pcapng delete mode 100644 test_automatic/Scan_Good_Packets.pcapng delete mode 100644 test_automatic/config.json.test delete mode 100644 test_automatic/go.mod delete mode 100644 test_automatic/go.sum delete mode 100644 test_automatic/integration.go delete mode 100644 test_automatic/rapport_beug_bottin.txt delete mode 100755 test_automatic/start_test.sh diff --git a/.drone.yml b/.drone.yml index 8a79d64..c7fb744 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,19 +8,18 @@ steps: commands: - go get -d -v - go build -v - - cd test_automatic - - go get -d -v - - go build -v + - cd test + - go test -i -c -o test - name: test_bottin image: consul:latest environment: BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1 commands: - - ash test_automatic/start_test.sh + - ash test/runner.sh --- kind: signature -hmac: a4455c124ee87ca8b0ef1779560703573f3a3f24d406e4cb281b9e0dab4ceeda +hmac: ff246a04c3df8a2f39c8b446dea920622d61950e6caaac886931bdb05d0706ed ... diff --git a/.gitignore b/.gitignore index 9edb3e5..2a2f53c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ bottin bottin.static config.json -test_automatic/integration +test/test diff --git a/main.go b/main.go index 91c3bbd..b490aa7 100644 --- a/main.go +++ b/main.go @@ -330,7 +330,7 @@ func (server *Server) init() error { } admin_pass_str = base64.RawURLEncoding.EncodeToString(admin_pass) } else { - server.logger.Printf("It seems that exists a password in environnement variable") + server.logger.Debug("BOTTIN_DEFAULT_ADMIN_PW environment variable is set, using it for admin's password") } admin_pass_hash := SSHAEncode([]byte(admin_pass_str)) diff --git a/test/bottin_test.go b/test/bottin_test.go new file mode 100644 index 0000000..00c0da9 --- /dev/null +++ b/test/bottin_test.go @@ -0,0 +1,160 @@ +package main + +import ( + "testing" +) + +func TestAddThenDelete(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +func TestConfirmAddAttributes(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Test search_attribute to confirm the Add + if ok, err := inst.CompareOurDataWithConsul(); !ok { + t.Error(err) + } + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +//Modifyrequest Test +func TestModifyRequest(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Test modify all data (groups and users) + err = inst.ModifyRandomAllData() + if err != nil { + t.Error(err) + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +func TestModifyRequestAndCheck(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Test modify all data (groups and users) + err = inst.ModifyRandomAllData() + if err != nil { + t.Error(err) + } + + //Check if the data was modify on Consul + if ok, err := inst.CompareOurDataWithConsul(); !ok { + t.Error(err) + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +func TestAddUserInGroup(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Add users in group + err = inst.AddAllUsersInGroup() + if err != nil { + t.Error(err) + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +func TestDeleteGroupsAfterAddedUsers(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Add users in group + err = inst.AddAllUsersInGroup() + if err != nil { + t.Error(err) + } + + //Delete the half groups + number := len(inst.dataGroups) / 2 + err = inst.clean(inst.dataGroups[0:number]) + if err != nil { + t.Error(err) + } + inst.dataGroups = inst.dataGroups[number:len(inst.dataGroups)] + + //Check all the groups in memberOf exist + ok, err := inst.CheckMemberOf() + if err != nil { + t.Error(err) + } + if !ok { + t.Errorf("Found group in memberOf that isn't in Consul.") + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +//Example of paralellism Test +func TestPrincipal(t *testing.T) { + + t.Run("A=1", TestAddThenDelete) + t.Run("A=2", TestModifyRequest) + if !testing.Short() { + t.Run("B=1", TestConfirmAddAttributes) + t.Run("B=2", TestModifyRequestAndCheck) + t.Run("C=1", TestAddUserInGroup) + t.Run("C=2", TestDeleteGroupsAfterAddedUsers) + } + +} diff --git a/test/config.json.test b/test/config.json.test new file mode 100644 index 0000000..bc1eeec --- /dev/null +++ b/test/config.json.test @@ -0,0 +1,13 @@ +{ + "suffix": "dc=deuxfleurs,dc=fr", + "bind": "127.0.0.1:1389", + "acl": [ + "ANONYMOUS::bind:*,ou=users,dc=deuxfleurs,dc=fr:", + "ANONYMOUS::bind:cn=admin,dc=deuxfleurs,dc=fr:", + "*,dc=deuxfleurs,dc=fr::read:*:* !userpassword", + "*::read modify:SELF:*", + "cn=admin,dc=deuxfleurs,dc=fr::read add modify delete:*:*", + "*:cn=admin,ou=groups,dc=deuxfleurs,dc=fr:read add modify delete:*:*" + ] +} + diff --git a/test/create.go b/test/create.go new file mode 100644 index 0000000..4ad8106 --- /dev/null +++ b/test/create.go @@ -0,0 +1,281 @@ +package main + +import ( + "fmt" + "strings" + "sync" + + "github.com/go-ldap/ldap/v3" + "github.com/sirupsen/logrus" +) + +//Mux value, this value permits do not have two identicals values in the parallel instances +type StoreAllCN struct { + mu sync.Mutex + cn map[string]struct{} +} + +var allNames = StoreAllCN{cn: make(map[string]struct{})} + +//Type used for the tests +type attributes struct { + Name string + Data []string +} + +type data_DN struct { + DN string + Attributes []attributes +} + +type instance struct { + numberUsers, numberGroups int + dataGroups, dataUsers []data_DN + logging *ldap.Conn +} + +//Create a new object instance +//With this instance, we can obtain an isolated container where +//we have our users and groups. It allows to run tests in parallel. +func NewInstance(numberUsers, numberGroups int) (*instance, error) { + l, err := Connect() + if err != nil { + return nil, err + } + + logging.Level = logrus.InfoLevel + + inst := instance{ + numberUsers: numberUsers, + numberGroups: numberGroups, + dataGroups: []data_DN{}, + dataUsers: []data_DN{}, + logging: l, + } + + err = inst.createOrganizationnalUnit() + if ldap.IsErrorWithCode(err, uint16(68)) { + logging.Warn("OrganizationnalUnit already created") + err = nil + } + if err != nil { + return nil, err + } + + err = inst.CreateGroups() + if err != nil { + return nil, err + } + err = inst.CreateUsers() + if err != nil { + return nil, err + } + + return &inst, nil +} + +//Part: Created users or groups or OU + +func (inst *instance) createOrganizationnalUnit() error { + dn := []string{"ou=groups,dc=deuxfleurs,dc=fr", "ou=users,dc=deuxfleurs,dc=fr"} + attributes := []map[string][]string{{ + "description": []string{"OrganizationalUnit qui regroupe tous les groupes"}, + "objectclass": []string{"organizationalUnit", "top"}, + "ou": []string{"groups"}, + "structuralobjectclass": []string{"organizationalUnit"}, + }, + { + "description": []string{"OrganizationalUnit qui regroupe tous les users"}, + "objectclass": []string{"organizationalUnit", "top"}, + "ou": []string{"users"}, + "structuralobjectclass": []string{"organizationalUnit"}, + }, + } + + for index := range dn { + err := inst.Add_Request(dn[index], attributes[index]) + if err != nil { + return err + } + } + return nil + +} + +//Part: Create User or group + +func (inst *instance) CreateUsers() (err error) { + + dn := "cn=%s,ou=users,dc=deuxfleurs,dc=fr" + attributes := map[string][]string{ + "displayname": {}, + "objectclass": {"inetOrgPerson", "organizationalPerson", "person", "top"}, + "structuralobjectclass": {"inetOrgPerson"}, + } + + du, err := inst.create(dn, []string{"displayname"}, inst.numberUsers, attributes, inst.dataUsers) + if err == nil { + inst.dataUsers = du + } + return err +} + +func (inst *instance) CreateGroups() error { + dn := "cn=%s,ou=groups,dc=deuxfleurs,dc=fr" + attributes := map[string][]string{ + "description": {}, + "objectclass": {"groupOfNames", "top"}, + "structuralobjectclass": {"groupOfNames"}, + } + + dg, err := inst.create(dn, []string{"description"}, inst.numberGroups, attributes, inst.dataGroups) + if err == nil { + inst.dataGroups = dg + } + + return err +} + +//Hard Function: She does: +//- generate an unique name +//- store the Data of each AddRequest in instance struct +//- send AddRequest to Bottin +func (inst *instance) create(dn string, unique_attr []string, number int, attributes map[string][]string, data []data_DN) ([]data_DN, error) { + for i := 0; i < number; i++ { + name := inst.GenerateName() + + datDn := data_DN{DN: fmt.Sprintf(dn, name)} + + for _, value := range unique_attr { + attributes[value] = []string{name} + } + + datDn.Attributes = MapAttToStruct(attributes) + data = append(data, datDn) + + err := inst.Add_Request(fmt.Sprintf(dn, name), attributes) + if err != nil { + return nil, err + } + } + return data, nil +} + +//Part: clean + +func (inst *instance) Clean() error { + err := inst.CleanGroups() + if err != nil { + return err + } + err = inst.CleanUsers() + return err +} + +func (inst *instance) CleanUsers() error { + err := inst.clean(inst.dataUsers) + if err != nil { + return err + } + inst.dataUsers = []data_DN{} + return err +} + +func (inst *instance) CleanGroups() error { + err := inst.clean(inst.dataGroups) + if err != nil { + return err + } + inst.dataGroups = []data_DN{} + return err +} + +func (inst *instance) clean(stock []data_DN) error { + logging.Debugf("Delete %d elements.", len(stock)) + for _, value := range stock { + err := inst.Delete_Request(value.DN) + if err != nil { + return err + } + } + return nil +} + +//Part: Verify if a data_Dn is a group or an user +func (inst *instance) VerifyUser(user data_DN) (bool, error) { + dn := "ou=users,dc=deuxfleurs,dc=fr" + cn := strings.Split(user.DN, ",")[0] + filter := fmt.Sprintf("(%s)", cn) + + res, err := inst.Search_Request(dn, filter, []string{"cn"}) + + return len(res.Entries) == 1, err +} + +func (inst *instance) VerifyGroup(group data_DN) (bool, error) { + dn := "ou=groups,dc=deuxfleurs,dc=fr" + cn := strings.Split(group.DN, ",")[0] + filter := fmt.Sprintf("(%s)", cn) + + res, err := inst.Search_Request(dn, filter, []string{"cn"}) + + return len(res.Entries) == 1, err +} + +//Part: Add user in a group +func (inst *instance) AddUserInGroup(user, group data_DN) error { + + err := inst.Modify_Request(group.DN, nil, nil, map[string][]string{ + "member": {user.DN}, + }) + return err +} + +func (inst *instance) AddUserSliceInGroup(users_cn []string, group_dn string) error { + + err := inst.Modify_Request(group_dn, nil, nil, map[string][]string{ + "member": users_cn, + }) + return err +} + +//Part: modify, add, delete data_DN struct + +func AddAtt(name string, data []string, dat data_DN) data_DN { + dat.Attributes = append(dat.Attributes, attributes{ + Name: name, + Data: data, + }) + + logging.Debug(fmt.Sprintf("Attributes %s add from %s.", name, dat.DN)) + return dat +} + +func DelAtt(name string, dat data_DN) data_DN { + for index, value := range dat.Attributes { + if value.Name == name { + dat.Attributes[index] = dat.Attributes[len(dat.Attributes)-1] + //tmp := dat.Attributes[:len(dat.Attributes)-1] + dat.Attributes = []attributes{} + logging.Debugf("Attributes %s delete from %s.", name, dat.DN) + return dat + } + } + logging.Debugf("Can't delete attribute %s from %s.", name, dat.DN) + return dat +} + +func ReplaceAtt(name string, data []string, dat data_DN) data_DN { + for index, value := range dat.Attributes { + if value.Name == name { + dat.Attributes[index] = attributes{ + Name: name, + Data: data, + } + logging.Debugf("Replace attributes %s from %s succesful..", name, dat.DN) + return dat + } + } + logging.Debugf("Can't replace attributes %s from %s.", name, dat.DN) + return dat +} diff --git a/test/functionTest.go b/test/functionTest.go new file mode 100644 index 0000000..e8d95ab --- /dev/null +++ b/test/functionTest.go @@ -0,0 +1,173 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/go-ldap/ldap/v3" +) + +const default_users, default_groups = 1000, 1000 + +func Init() (*instance, error) { + inst, err := NewInstance(default_users, default_groups) + return inst, err +} + +//Part to compare our datas +func (inst *instance) CompareOurDataWithConsul() (bool, error) { + if ok, err := inst.VerifyOurData(inst.dataUsers); !ok { + return false, err + } + if ok, err := inst.VerifyOurData(inst.dataGroups); !ok { + return false, err + } + return true, nil +} + +func (inst *instance) VerifyOurData(tabData []data_DN) (bool, error) { + for _, value := range tabData { + names := getNamesAtt(value) + cn := strings.Split(value.DN, ",")[0] + res, err := inst.Search_Request(value.DN, fmt.Sprintf("(&(%s))", cn), names) + if err != nil { + return false, err + } + if len(res.Entries) != 1 { + return false, fmt.Errorf("expected 1 entry, but found %d entry/ies", len(res.Entries)) + } + if !Compare(value, res.Entries[0]) { + return false, fmt.Errorf("no match with the DN: %s", value.DN) + } + } + return true, nil +} + +func Compare(dat data_DN, ent *ldap.Entry) bool { + for _, value := range dat.Attributes { + logging.Debugf("Attributes from %s is now: %s.", dat.DN, dat.Attributes) + entVal := GetAttributeValuesBottin(ent, value.Name) + logging.Debugf("Values of the Entry: attributName: %s, Values: %s.", value.Name, entVal) + if !CompareSliceString(entVal, value.Data) { + logging.Debugf("Values expected: %s, values found: %s.", value.Data, entVal) + return false + } + } + return true +} + +//Part modify datas +func (inst *instance) ModifyRandomAllData() error { + dg, err := inst.ModifyRandom(inst.dataGroups, []string{"description"}) + if err != nil { + return err + } else { + inst.dataGroups = dg + } + + dg, err = inst.ModifyRandom(inst.dataUsers, []string{"displayname"}) + if err != nil { + return err + } else { + inst.dataUsers = dg + } + return nil +} + +//Function which modify random way the attributes in attName of a data_DN's slice, it can delete, replace and delete +//The function modify also in the dat object +func (inst *instance) ModifyRandom(dat []data_DN, attName []string) ([]data_DN, error) { + for index, value := range dat { + del := make(map[string][]string) + add := make(map[string][]string) + replace := make(map[string][]string) + + for _, att := range attName { + + switch selNumber := R.Intn(3); selNumber { + case 0: + del[att] = []string{} + value = DelAtt(att, value) + logging.Debug(fmt.Sprintf("Delete the attribute %s of the DN %s.", att, value.DN)) + case 1: + name := inst.GenerateName() + value = AddAtt(name, []string{name}, value) + add[name] = []string{name} + logging.Debug(fmt.Sprintf("Add the attribute %s with value %s of the DN %s.", name, name, value.DN)) + case 2: + name := inst.GenerateName() + value = ReplaceAtt(att, []string{name}, value) + replace[att] = []string{name} + logging.Debug(fmt.Sprintf("Replace the attribute %s with value %s of the DN %s.", att, name, value.DN)) + } + + } + + err := inst.Modify_Request(value.DN, add, del, replace) + if err != nil { + return dat, err + } + dat[index] = value + } + return dat, nil +} + +//Add all users in a random group +func (inst *instance) AddAllUsersInGroup() error { + for _, value := range inst.dataGroups { + valueRand := (len(inst.dataUsers) + 1) / 30 + if valueRand == 0 { + valueRand = 1 + } + numberOfMembers := R.Intn(valueRand) + 1 + logging.Debugf("%s will be have %d members.", value.DN, numberOfMembers) + + groupMemory := make(map[int]struct{}) + users_cn := []string{} + + for i := 0; i < numberOfMembers; i++ { + selectGroup := R.Intn(len(inst.dataUsers)) + for _, ok := groupMemory[selectGroup]; ok; _, ok = groupMemory[selectGroup] { + selectGroup = R.Intn(len(inst.dataUsers)) + + logging.Debugf("Search an other member. The value is %d , and we have %d members available.", selectGroup, len(inst.dataUsers)) + } + groupMemory[selectGroup] = struct{}{} + + users_cn = append(users_cn, inst.dataGroups[selectGroup].DN) + + } + err := inst.AddUserSliceInGroup(users_cn, value.DN) + if err != nil { + return err + } + + } + return nil +} + +//Check if the groups in memberOf exist in Consul +func (inst *instance) CheckMemberOf() (bool, error) { + for _, value := range inst.dataUsers { + cn := strings.Split(value.DN, ",")[0] + res, err := inst.Search_Request(value.DN, fmt.Sprintf("(&(%s))", cn), []string{"memberOf"}) + if err != nil { + return false, err + } + if len(res.Entries) != 1 { + return false, fmt.Errorf("expected 1 entry, but found %d entry/ies", len(res.Entries)) + } + attValues := GetAttributeValuesBottin(res.Entries[0], "memberOf") + for _, dnGroup := range attValues { + logging.Debugf("Verify if the group %s exist...", dnGroup) + ok, err := inst.VerifyGroup(data_DN{DN: dnGroup}) + if err != nil { + return false, err + } + if !ok { + return false, fmt.Errorf("don't found the group: %s", dnGroup) + } + } + } + return true, nil +} diff --git a/test/go.mod b/test/go.mod new file mode 100644 index 0000000..74ed1ce --- /dev/null +++ b/test/go.mod @@ -0,0 +1,8 @@ +module bottin/integration + +go 1.14 + +require ( + github.com/go-ldap/ldap/v3 v3.3.0 + github.com/sirupsen/logrus v1.4.2 +) diff --git a/test/go.sum b/test/go.sum new file mode 100644 index 0000000..9c882d3 --- /dev/null +++ b/test/go.sum @@ -0,0 +1,26 @@ +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= +github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ= +github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/test/handler.go b/test/handler.go new file mode 100644 index 0000000..0e7a95b --- /dev/null +++ b/test/handler.go @@ -0,0 +1,138 @@ +package main + +import ( + "fmt" + "math/rand" + "os" + + ldap "github.com/go-ldap/ldap/v3" + "github.com/sirupsen/logrus" +) + +const maxlength_generateName, minlength_generateName = 25, 3 + +const bindusername = "cn=admin,dc=deuxfleurs,dc=fr" +const adresse = "127.0.0.1" +const port = 1389 + +var logging = logrus.New() + +const seed = 654258 + +var R = rand.New(rand.NewSource(seed)) + +var bindpassword = "sf7yO52NCuE" + +//Handler just to facilite the print error +func PrintError(LDAPError error) { + if LDAPError != nil { + logging.Fatal(LDAPError) + } +} + +//Generate an unique name, which store in all_names +func (inst *instance) GenerateName() (name string) { + alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + length := R.Intn(maxlength_generateName) + minlength_generateName + + //Check if this name not exist already + //Lock thhis variable because she is hared with other goroutine + allNames.mu.Lock() + for only_one := true; only_one; _, only_one = allNames.cn[name] { + //Create the name + for i := 0; i < length; i++ { + name += string(alphabet[R.Intn(len(alphabet))]) + } + + } + //Add the new name in the map to store this one + allNames.cn[name] = struct{}{} + allNames.mu.Unlock() + logging.Debug(fmt.Sprintf("Name generated: %s.", name)) + return +} + +//Handler to around the bug with MessageId +func (inst *instance) Reconnect() (err error) { + inst.logging.Close() + inst.logging, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", adresse, port)) + if err != nil { + return + } + err = inst.logging.Bind(bindusername, bindpassword) + //logging.Debug("Reconnect succesful") + return +} + +//Transform attributes in map format to the struct attributes +func MapAttToStruct(att map[string][]string) []attributes { + resultat := []attributes{} + for key, value := range att { + logging.Debug(fmt.Sprintf("Transform: key: %s, values: %s to attributes struct.\n", key, value)) + resultat = append(resultat, attributes{ + Name: key, + Data: value, + }) + } + return resultat +} + +func Connect() (*ldap.Conn, error) { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", adresse, port)) + if err != nil { + return nil, err + } + + if key, ok := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW"); ok { + bindpassword = key + } + + err = l.Bind(bindusername, bindpassword) + logging.Debug("Connection succesful") + return l, err +} + +//Handler to get only attributes names +func getNamesAtt(dat data_DN) []string { + resultat := []string{} + for _, values := range dat.Attributes { + resultat = append(resultat, values.Name) + } + return resultat +} + +//Handler to compare slice string +func CompareSliceString(string1, string2 []string) bool { + if len(string1) != len(string2) { + return false + } else { + for index := range string1 { + if string1[index] != string2[index] { + return false + } + } + } + return true +} + +//Handler to remove an element in slice string +func DeleteElementSliceString(s string, sSlice []string) []string { + + for index, value := range sSlice { + if value == s { + sSlice[index] = sSlice[len(sSlice)-1] + return sSlice[:len(sSlice)-1] + } + } + return sSlice +} + +//Get attributes entry values bottin bug +func GetAttributeValuesBottin(ent *ldap.Entry, name string) (res []string) { + for _, val := range ent.Attributes { + if val.Name == name { + res = append(res, val.Values...) + } + } + return +} diff --git a/test/request.go b/test/request.go new file mode 100644 index 0000000..b693211 --- /dev/null +++ b/test/request.go @@ -0,0 +1,67 @@ +package main + +import ( + ldap "github.com/go-ldap/ldap/v3" +) + +func (inst *instance) Add_Request(dn string, attributes map[string][]string) error { + //Create the AddRequest + req := ldap.NewAddRequest(dn, nil) + for key, value := range attributes { + req.Attribute(key, value) + } + + //Send the request + err := inst.logging.Add(req) + //@FIXME: Remove when you try to correct the bug MessageID + inst.Reconnect() + return err + +} + +//Use enum to select Replace,Delete,Modify +func (inst *instance) Modify_Request(dn string, add_attributes, delete_attributes, replace_attributes map[string][]string) error { + modifyReq := ldap.NewModifyRequest(dn, nil) + + for key, value := range add_attributes { + modifyReq.Add(key, value) + } + + for key, value := range delete_attributes { + modifyReq.Delete(key, value) + } + + for key, value := range replace_attributes { + modifyReq.Replace(key, value) + } + + err := inst.logging.Modify(modifyReq) + //@FIXME: Remove when you try to correct the bug MessageID + inst.Reconnect() + return err +} + +func (inst *instance) Delete_Request(dn string) error { + del := ldap.NewDelRequest(dn, nil) + + err := inst.logging.Del(del) + //@FIXME: Remove when you try to correct the bug MessageID + inst.Reconnect() + return err +} + +func (inst *instance) Search_Request(dn, filter string, name_attributes []string) (*ldap.SearchResult, error) { + searchReq := ldap.NewSearchRequest( + dn, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + filter, + name_attributes, + nil, + ) + + res, err := inst.logging.Search(searchReq) + logging.Debugf("Search Request made with: dn: %s, filter: %s, attributes: %s. \n", dn, filter, name_attributes) + //@FIXME: Remove when you try to correct the bug MessageID + inst.Reconnect() + return res, err +} diff --git a/test/runner.sh b/test/runner.sh new file mode 100755 index 0000000..0b97068 --- /dev/null +++ b/test/runner.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -ex + +echo $BOTTIN_DEFAULT_ADMIN_PW +consul agent -dev > /dev/null 2>&1 & +sleep 2 +cp test/config.json.test config.json +./bottin > /dev/null 2>&1 & +sleep 1 +./test/test -test.v -test.failfast -test.short -test.run TestPrincipal +./test/test -test.v -test.failfast -test.run TestPrincipal/B= diff --git a/test_automatic/Scan_Bad_Packets.pcapng b/test_automatic/Scan_Bad_Packets.pcapng deleted file mode 100644 index cc209f5..0000000 Binary files a/test_automatic/Scan_Bad_Packets.pcapng and /dev/null differ diff --git a/test_automatic/Scan_Good_Packets.pcapng b/test_automatic/Scan_Good_Packets.pcapng deleted file mode 100644 index 5c87f82..0000000 Binary files a/test_automatic/Scan_Good_Packets.pcapng and /dev/null differ diff --git a/test_automatic/config.json.test b/test_automatic/config.json.test deleted file mode 100644 index bc1eeec..0000000 --- a/test_automatic/config.json.test +++ /dev/null @@ -1,13 +0,0 @@ -{ - "suffix": "dc=deuxfleurs,dc=fr", - "bind": "127.0.0.1:1389", - "acl": [ - "ANONYMOUS::bind:*,ou=users,dc=deuxfleurs,dc=fr:", - "ANONYMOUS::bind:cn=admin,dc=deuxfleurs,dc=fr:", - "*,dc=deuxfleurs,dc=fr::read:*:* !userpassword", - "*::read modify:SELF:*", - "cn=admin,dc=deuxfleurs,dc=fr::read add modify delete:*:*", - "*:cn=admin,ou=groups,dc=deuxfleurs,dc=fr:read add modify delete:*:*" - ] -} - diff --git a/test_automatic/go.mod b/test_automatic/go.mod deleted file mode 100644 index 74ed1ce..0000000 --- a/test_automatic/go.mod +++ /dev/null @@ -1,8 +0,0 @@ -module bottin/integration - -go 1.14 - -require ( - github.com/go-ldap/ldap/v3 v3.3.0 - github.com/sirupsen/logrus v1.4.2 -) diff --git a/test_automatic/go.sum b/test_automatic/go.sum deleted file mode 100644 index 8e94420..0000000 --- a/test_automatic/go.sum +++ /dev/null @@ -1,23 +0,0 @@ -github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= -github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= -github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ= -github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/test_automatic/integration.go b/test_automatic/integration.go deleted file mode 100644 index d55d280..0000000 --- a/test_automatic/integration.go +++ /dev/null @@ -1,431 +0,0 @@ -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 - -} diff --git a/test_automatic/rapport_beug_bottin.txt b/test_automatic/rapport_beug_bottin.txt deleted file mode 100644 index 1669e14..0000000 --- a/test_automatic/rapport_beug_bottin.txt +++ /dev/null @@ -1,40 +0,0 @@ -Introduction et Observation premières: - - Lors de la réalisation de mon code Go, j'ai trouvé un beug qui provoquait l'arrêt de mon programme car Bottin envoyé une réponse erronée à mon programme. - Dans logs de Bottin je ne voyais aucune erreur, Bottin n'avait pas cessé de fonctionner. Il s'arrêtait à chaque fois sur mes requêtes Del (mes dernières). - - -Reproduction du beug: - - Pour reproduire le beug, il suffit de lancer le programme interrogation.go et de commenter les lignes suivantes : - -260 //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 -261 l.Close() -262 l, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d",adresse, port)) -263 printError(err) -264 err = l.Bind(bindusername, bindpassword) -265 printError(err) - - Ainsi on obtient l'erreur suivante: - -2021/07/07 01:25:51 Received unexpected message -128, false - - -Test réalisé pour comprendre la source du problème: - - Ma première hypothèses fut que j'envoyais trop de requêtes dans un court laps de temps et ainsi Bottin ou Consul ne pouvait pas suivre. -J'ai placé un sleep de 50 puis de 100 Millisecondes entre chaque requête, j'obtenais toujours la même erreur. -J'ai essayé de mettre un sleep de 10 secondes avant mes requêtes de suppression mais j'obtenais toujours la même chose. - -Hack pour résoudre: - - La première solution qui a fonctionné était de réduire le nombre de requêtes en n'exécutant pas certains tests random. - La dernière solutions qui est utilisée: - Fermée la connexion puis la réouvrir permet de palier à ce problème - -Hypothèses du problème ?: - - Existerait-il un Buffer par Bind qui se remplirait ? Et lorsque celui-ci est plein, ne renvoie pas d'erreur juste n'arrive plus à répondre. - -Erwan DUFOUR -Deuxfleurs diff --git a/test_automatic/start_test.sh b/test_automatic/start_test.sh deleted file mode 100755 index e7a1712..0000000 --- a/test_automatic/start_test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -#export BOTTIN_DEFAULT_ADMIN_PW=$(openssl rand -base64 24) -echo $BOTTIN_DEFAULT_ADMIN_PW -consul agent -dev > /dev/null 2>&1 & -sleep 2 -cp test_automatic/config.json.test config.json -./bottin > /dev/null 2>&1 & -sleep 1 -./test_automatic/integration -rm config.json -exit 0 -- cgit v1.2.3 From a53641e773730ba171df2602c8d199968d6e6447 Mon Sep 17 00:00:00 2001 From: MrArmonius Date: Mon, 26 Jul 2021 15:36:45 +0200 Subject: Correct the function GenerateName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The problem was the encode in `name += string(alphabet[])` It takes only 1 byte but the characters like 'è','@' are encoding on several bytes (1 to 4 bytes). The better solution was to create a slice of string, like this we don't have problem about take only one byte instead of 2,3 or 4 bytes. --- test/handler.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/handler.go b/test/handler.go index 0e7a95b..43fad77 100644 --- a/test/handler.go +++ b/test/handler.go @@ -32,7 +32,9 @@ func PrintError(LDAPError error) { //Generate an unique name, which store in all_names func (inst *instance) GenerateName() (name string) { - alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + alphabet := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", + "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", + "Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "é", "è", "ê", "ë", "à", "@", "â", "ä", "û", "ü", "ù", "$", "£", "%", "ø", "€"} length := R.Intn(maxlength_generateName) + minlength_generateName //Check if this name not exist already @@ -41,7 +43,7 @@ func (inst *instance) GenerateName() (name string) { for only_one := true; only_one; _, only_one = allNames.cn[name] { //Create the name for i := 0; i < length; i++ { - name += string(alphabet[R.Intn(len(alphabet))]) + name += alphabet[R.Intn(len(alphabet))] } } -- cgit v1.2.3