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,
This commit is contained in:
parent
da627ac39a
commit
9a8c19ec0f
17 changed files with 840 additions and 490 deletions
|
@ -8,19 +8,18 @@ steps:
|
||||||
commands:
|
commands:
|
||||||
- go get -d -v
|
- go get -d -v
|
||||||
- go build -v
|
- go build -v
|
||||||
- cd test_automatic
|
- cd test
|
||||||
- go get -d -v
|
- go test -i -c -o test
|
||||||
- go build -v
|
|
||||||
|
|
||||||
- name: test_bottin
|
- name: test_bottin
|
||||||
image: consul:latest
|
image: consul:latest
|
||||||
environment:
|
environment:
|
||||||
BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1
|
BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1
|
||||||
commands:
|
commands:
|
||||||
- ash test_automatic/start_test.sh
|
- ash test/runner.sh
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: signature
|
kind: signature
|
||||||
hmac: a4455c124ee87ca8b0ef1779560703573f3a3f24d406e4cb281b9e0dab4ceeda
|
hmac: ff246a04c3df8a2f39c8b446dea920622d61950e6caaac886931bdb05d0706ed
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,4 +1,4 @@
|
||||||
bottin
|
bottin
|
||||||
bottin.static
|
bottin.static
|
||||||
config.json
|
config.json
|
||||||
test_automatic/integration
|
test/test
|
||||||
|
|
2
main.go
2
main.go
|
@ -330,7 +330,7 @@ func (server *Server) init() error {
|
||||||
}
|
}
|
||||||
admin_pass_str = base64.RawURLEncoding.EncodeToString(admin_pass)
|
admin_pass_str = base64.RawURLEncoding.EncodeToString(admin_pass)
|
||||||
} else {
|
} 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))
|
admin_pass_hash := SSHAEncode([]byte(admin_pass_str))
|
||||||
|
|
160
test/bottin_test.go
Normal file
160
test/bottin_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
281
test/create.go
Normal file
281
test/create.go
Normal 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
173
test/functionTest.go
Normal 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
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
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/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/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 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
|
@ -7,10 +8,12 @@ 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/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 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
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/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 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
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/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=
|
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-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 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
138
test/handler.go
Normal file
138
test/handler.go
Normal 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
67
test/request.go
Normal 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
12
test/runner.sh
Executable 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=
|
Binary file not shown.
Binary file not shown.
|
@ -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
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
Loading…
Reference in a new issue