Add end-to-end tests to Bottin #2

Merged
erwan merged 3 commits from test-go-Bottin into main 2021-07-22 07:59:11 +00:00
14 changed files with 925 additions and 12 deletions

View file

@ -1,13 +1,25 @@
--- ---
pipeline: kind: pipeline
build: name: bottin
image: golang:stretch
commands: steps:
- go get -d -v - name: build
- go build -v image: golang:stretch
commands:
- 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/runner.sh
--- ---
kind: signature kind: signature
hmac: 8f49fdf0e4abb0790827eed7cac8eedd5e11705d1fa01954a84929933eb7b254 hmac: ff246a04c3df8a2f39c8b446dea920622d61950e6caaac886931bdb05d0706ed
... ...

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
bottin bottin
bottin.static bottin.static
config.json config.json
test/test

1
go.mod
View file

@ -3,6 +3,7 @@ module bottin
go 1.13 go 1.13
require ( require (
github.com/go-ldap/ldap/v3 v3.3.0
erwan marked this conversation as resolved Outdated

Pourquoi ce commentaire //indirect ? On peut le supprimer si il ne sert à rien ;)

Pourquoi ce commentaire `//indirect` ? On peut le supprimer si il ne sert à rien ;)
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/hashicorp/consul/api v1.3.0 github.com/hashicorp/consul/api v1.3.0
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3

14
go.sum
View file

@ -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/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 h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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 h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 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= 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= 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 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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-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 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-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 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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-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 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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=

17
main.go
View file

@ -320,12 +320,19 @@ func (server *Server) init() error {
return err return err
} }
admin_pass := make([]byte, 8)
_, err = rand.Read(admin_pass) admin_pass_str, environnement_variable_exist := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW")
if err != nil { if !environnement_variable_exist {
return err 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.Debug("BOTTIN_DEFAULT_ADMIN_PW environment variable is set, using it for admin's password")
erwan marked this conversation as resolved Outdated

Je mettrais bien quelque chose comme ça :

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.

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_str := base64.RawURLEncoding.EncodeToString(admin_pass)
admin_pass_hash := SSHAEncode([]byte(admin_pass_str)) admin_pass_hash := SSHAEncode([]byte(admin_pass_str))
admin_dn := "cn=admin," + server.config.Suffix admin_dn := "cn=admin," + server.config.Suffix

160
test/bottin_test.go Normal file
View file

@ -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)
}
}

13
test/config.json.test Normal file
View file

@ -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:*:*"
]
}

281
test/create.go Normal file
View file

@ -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
}

173
test/functionTest.go Normal file
View file

@ -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
}

8
test/go.mod Normal file
View file

@ -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
)

26
test/go.sum Normal file
View file

@ -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=

138
test/handler.go Normal file
View file

@ -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
}

67
test/request.go Normal file
View file

@ -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
}

12
test/runner.sh Executable file
View file

@ -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=