Add end-to-end tests to Bottin #2
17
.drone.yml
|
@ -1,13 +1,24 @@
|
|||
---
|
||||
pipeline:
|
||||
build:
|
||||
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
|
||||
|
||||
...
|
||||
|
|
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
bottin
|
||||
bottin.static
|
||||
config.json
|
||||
test_automatic/integration
|
||||
|
|
1
go.mod
|
@ -3,6 +3,7 @@ module bottin
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
erwan marked this conversation as resolved
Outdated
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/hashicorp/consul/api v1.3.0
|
||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
||||
|
|
14
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=
|
||||
|
|
9
main.go
|
@ -320,12 +320,19 @@ func (server *Server) init() error {
|
|||
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)
|
||||
admin_pass_str = base64.RawURLEncoding.EncodeToString(admin_pass)
|
||||
} else {
|
||||
server.logger.Printf("It seems that exists a password in environnement variable")
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Je mettrais bien quelque chose comme ça :
Je pense qu'on devrait utiliser le logger "normal" ici, du moins si on peut. Je mettrais bien quelque chose comme ça :
```go
logger.Debug("BOTTIN_DEFAULT_ADMIN_PW environment variable is set, using it for admin's password")
```
Je pense qu'on devrait utiliser le logger "normal" ici, du moins si on peut.
|
||||
}
|
||||
|
||||
admin_pass_hash := SSHAEncode([]byte(admin_pass_str))
|
||||
|
||||
admin_dn := "cn=admin," + server.config.Suffix
|
||||
|
|
BIN
test_automatic/Scan_Bad_Packets.pcapng
Normal file
BIN
test_automatic/Scan_Good_Packets.pcapng
Normal file
8
test_automatic/go.mod
Normal file
|
@ -0,0 +1,8 @@
|
|||
module bottin/integration
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Juste pour info, là en réalité on créer un projet go à part qui nous sert pour tester. Mais je pense qu'on peut dire que c'est en dehors du scope de cette demande de fusion. Juste pour info, là en réalité on créer un projet go à part qui nous sert pour tester.
À terme, ça pourrait être intéressant de l'intégrer dans le projet parent en utilisant le framework de test de go.
Mais je pense qu'on peut dire que c'est en dehors du scope de cette demande de fusion.
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
)
|
23
test_automatic/go.sum
Normal file
|
@ -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=
|
430
test_automatic/integration.go
Normal file
|
@ -0,0 +1,430 @@
|
|||
package main
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Je te recommande d'exécuter Vis à vis du nommage, je dirais que tu as fais un test de bout en bout : C'est différent du test d'intégration qui va tester indépendamment. Je te propose de renommer ton dossier Je te recommande d'exécuter `go fmt tonfichier.go` pour le formatter automatiquement de sorte à qu'il formatte de manière standard ton fichier.
Vis à vis du nommage, je dirais que tu as fais un test de bout en bout :
chaque étape de ton test dépend de la précédente, le test entier étant un scénario.
C'est différent du test d'intégration qui va tester indépendamment.
Je te propose de renommer ton dossier `test_automatic` en `test`, ce qui est la convention.
Et de renommer integration.go en e2e.go (end to end, de bout en bout), ce qui sera plus clair pour les autres personnes.
|
||||
|
||||
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) {
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Handler pour gérer les erreurs de LDAP Handler pour gérer les erreurs de LDAP
|
||||
if LDAPError != nil {
|
||||
log.Fatal(LDAPError)
|
||||
}
|
||||
}
|
||||
|
||||
func createOU(l *ldap.Conn) error {
|
||||
|
||||
req := ldap.NewAddRequest("ou=groups,dc=deuxfleurs,dc=fr",nil)
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Ce qui pourrait être intéressant c'est d'avoir une fonction qui fait les appels à la lib LDAP pour ajouter l'organisation unit. Et une autre fonction qui contient les données. Exemple de la signature de la fonction qui ajoute les organisation units:
Et après tu peux écrire tes données de test comme ça :
Ce qui pourrait être intéressant c'est d'avoir une fonction qui fait les appels à la lib LDAP pour ajouter l'organisation unit.
Et une autre fonction qui contient les données.
Exemple de la signature de la fonction qui ajoute les organisation units:
```go
func createOU (l *ldap.Conn, dn string, attributes map[string][]string)
```
Et après tu peux écrire tes données de test comme ça :
```go
func createHierarchy(l *ldap.Conn) {
base := map[string][]string{
"objectclass": []string{"organizationalUnit", "top"},
"structuralobjectclass": []string{"organizationalUnit"},
}
base["description"] = []string{"OrganizationalUnit qui regroupe tous les groupes"}
createOU(l, "ou=groups,dc=deuxfleurs,dc=fr", base)
base["description"] = []string{"OrganizationalUnit qui regroupe tous les users"}
createOU(l, "ou=users,dc=deuxfleurs,dc=fr", base)
}
```
|
||||
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) {
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Là aussi on a un handler, en tout cas de la logique pure. Là aussi on a un handler, en tout cas de la logique pure.
|
||||
for only_one := true; only_one; _, only_one = all_names[name]{
|
||||
name = fmt.Sprintf("%d",r.Int())
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Tu peux rendre ta fonction de génération plus intéressante si tu pioches sur un ensemble de caractère plus grand que juste des nombres. Idéalement tout UTF-8 mais peut-être pas, au moins Quelque chose comme ça :
Et normalement tu as pas besoin du sprintf. Tu peux rendre ta fonction de génération plus intéressante si tu pioches sur un ensemble de caractère plus grand que juste des nombres. Idéalement tout UTF-8 mais peut-être pas, au moins `[a-zA-Z0-9]`, peut-être aussi des accents et des caractères spéciaux ?
Quelque chose comme ça :
```go
charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/-*éç"
charset[r.Intn(len(charset)-1)]
```
Et normalement tu as pas besoin du sprintf.
|
||||
}
|
||||
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) {
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Tu fais trois choses ici :
Je dirais juste, d'une manière générale, génère toutes tes données en dehors des fonctions Tu pourrais avoir un fichier qui génèrent les données. Tu fais trois choses ici :
1. Tu as une logique pour créer un seul groupe
2. Tu as de la logique pour créer plusieurs groupes
3. Tu as de la logique pour collecter les données des groupes que tu as généré, le `tab_AddRequest`.
Je dirais juste, d'une manière générale, génère toutes tes données en dehors des fonctions
qui font des appels à LDAP.
Tu pourrais avoir un fichier qui génèrent les données.
|
||||
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) {
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Tu l'as dit toi même séparer récupérer les résultats et les comparer. Tu l'as dit toi même séparer récupérer les résultats et les comparer.
|
||||
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 {
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Simplifier avec la fonction Entries.GetAttributesValues. Simplifier avec la fonction Entries.GetAttributesValues.
En tout cas, ne pas hésiter à créer un handler pour gérer ça.
|
||||
//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){
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Juste pour info, c'est pas terrible des booleens comme ça parce que tu ne sais pas à quoi correspond le booleen quand tu appelles la fonction. Donc déjà souvent on va préférer mettre un enum, tu aurais pu faire un seul enum avec user et group. En plus, si tu n'utilises pas cette possibilité, ça ne sert à rien de la mettre. Enfin, les deux sont des tableaux, sur lesquels tu itères, donc en réalité si tu veux en "désactiver" un, tu peux simplement mettre un tableau vide. Pour l'utilisateur, tu peux créer un type spécial, nommé KeepUser et KeepGroup, qui sont en réalité des tableaux vides. Juste pour info, c'est pas terrible des booleens comme ça parce que tu ne sais pas à quoi correspond le booleen quand tu appelles la fonction.
Donc déjà souvent on va préférer mettre un enum, tu aurais pu faire un seul enum avec user et group.
USER, GROUP, et BOTH.
En plus, si tu n'utilises pas cette possibilité, ça ne sert à rien de la mettre.
Enfin, les deux sont des tableaux, sur lesquels tu itères, donc en réalité si tu veux en "désactiver" un, tu peux simplement mettre un tableau vide.
Pour l'utilisateur, tu peux créer un type spécial, nommé KeepUser et KeepGroup, qui sont en réalité des tableaux vides.
|
||||
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")
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Pourquoi un defer log.Debug ? :P Pourquoi un defer log.Debug ? :P
|
||||
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))
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
?? bien que tu mettes un nombre fixe pour la source, la seed change à chaque run. ?? bien que tu mettes un nombre fixe pour la source, la seed change à chaque run.
Saurais-tu expliquer/corriger ça ?
|
||||
|
||||
//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
|
||||
|
||||
}
|
40
test_automatic/rapport_beug_bottin.txt
Normal file
|
@ -0,0 +1,40 @@
|
|||
Introduction et Observation premières:
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
On peut l'enlever mais ne jette pas ces informations, reporte les bien toutes dans l'issue ! On peut l'enlever mais ne jette pas ces informations, reporte les **bien toutes** dans l'issue !
|
||||
|
||||
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
|
11
test_automatic/start_test.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Un nom qui me semble bien pour ce fichier c'est Un nom qui me semble bien pour ce fichier c'est `runner.sh` mais tu peux conserver le nom actuel si tu préfères :)
|
||||
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Pour être sûr que toutes les erreurs soient bien remontées, je te recommande de commencer ton script par :
Le modifier
Pour être sûr que toutes les erreurs soient bien remontées, je te recommande de commencer ton script par :
```
set -e
```
Le *modifier* `-x` te permet aussi de voir la commande finale exécutée, ça peut être pratique lors de la lecture des logs de drone, tu aurais alors :
```
set -ex
```
|
||||
trap "kill 0" EXIT
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Ça je suppose qu'il n'y avait pas openssl dans l'image Consul. Ça je suppose qu'il n'y avait pas openssl dans l'image Consul.
Dans ce cas, on peut l'enlever, et c'est un sujet intéressant à discuter.
Ta solution de contournement (mettre en dur le mot de passe dans le `.drone.yml`) me semble adaptée.
|
||||
|
||||
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
|
||||
erwan marked this conversation as resolved
Outdated
quentin
commented
Le Le `rm` n'est pasobligatoire, à chaque *build* drone repart d'un environnement propre, tu n'as pas besoin de nettoyer das tes scripts de test
|
Pourquoi ce commentaire
//indirect
? On peut le supprimer si il ne sert à rien ;)