diff --git a/.drone.yml b/.drone.yml index 8a79d64..c7fb744 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,19 +8,18 @@ steps: commands: - go get -d -v - go build -v - - cd test_automatic - - go get -d -v - - go build -v + - cd test + - go test -i -c -o test - name: test_bottin image: consul:latest environment: BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1 commands: - - ash test_automatic/start_test.sh + - ash test/runner.sh --- kind: signature -hmac: a4455c124ee87ca8b0ef1779560703573f3a3f24d406e4cb281b9e0dab4ceeda +hmac: ff246a04c3df8a2f39c8b446dea920622d61950e6caaac886931bdb05d0706ed ... diff --git a/.gitignore b/.gitignore index 9edb3e5..2a2f53c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ bottin bottin.static config.json -test_automatic/integration +test/test diff --git a/main.go b/main.go index 91c3bbd..b490aa7 100644 --- a/main.go +++ b/main.go @@ -330,7 +330,7 @@ func (server *Server) init() error { } admin_pass_str = base64.RawURLEncoding.EncodeToString(admin_pass) } else { - server.logger.Printf("It seems that exists a password in environnement variable") + server.logger.Debug("BOTTIN_DEFAULT_ADMIN_PW environment variable is set, using it for admin's password") } admin_pass_hash := SSHAEncode([]byte(admin_pass_str)) diff --git a/test/bottin_test.go b/test/bottin_test.go new file mode 100644 index 0000000..00c0da9 --- /dev/null +++ b/test/bottin_test.go @@ -0,0 +1,160 @@ +package main + +import ( + "testing" +) + +func TestAddThenDelete(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +func TestConfirmAddAttributes(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Test search_attribute to confirm the Add + if ok, err := inst.CompareOurDataWithConsul(); !ok { + t.Error(err) + } + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +//Modifyrequest Test +func TestModifyRequest(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Test modify all data (groups and users) + err = inst.ModifyRandomAllData() + if err != nil { + t.Error(err) + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +func TestModifyRequestAndCheck(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Test modify all data (groups and users) + err = inst.ModifyRandomAllData() + if err != nil { + t.Error(err) + } + + //Check if the data was modify on Consul + if ok, err := inst.CompareOurDataWithConsul(); !ok { + t.Error(err) + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +func TestAddUserInGroup(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Add users in group + err = inst.AddAllUsersInGroup() + if err != nil { + t.Error(err) + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +func TestDeleteGroupsAfterAddedUsers(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Add users in group + err = inst.AddAllUsersInGroup() + if err != nil { + t.Error(err) + } + + //Delete the half groups + number := len(inst.dataGroups) / 2 + err = inst.clean(inst.dataGroups[0:number]) + if err != nil { + t.Error(err) + } + inst.dataGroups = inst.dataGroups[number:len(inst.dataGroups)] + + //Check all the groups in memberOf exist + ok, err := inst.CheckMemberOf() + if err != nil { + t.Error(err) + } + if !ok { + t.Errorf("Found group in memberOf that isn't in Consul.") + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +//Example of paralellism Test +func TestPrincipal(t *testing.T) { + + t.Run("A=1", TestAddThenDelete) + t.Run("A=2", TestModifyRequest) + if !testing.Short() { + t.Run("B=1", TestConfirmAddAttributes) + t.Run("B=2", TestModifyRequestAndCheck) + t.Run("C=1", TestAddUserInGroup) + t.Run("C=2", TestDeleteGroupsAfterAddedUsers) + } + +} diff --git a/test_automatic/config.json.test b/test/config.json.test similarity index 100% rename from test_automatic/config.json.test rename to test/config.json.test diff --git a/test/create.go b/test/create.go new file mode 100644 index 0000000..4ad8106 --- /dev/null +++ b/test/create.go @@ -0,0 +1,281 @@ +package main + +import ( + "fmt" + "strings" + "sync" + + "github.com/go-ldap/ldap/v3" + "github.com/sirupsen/logrus" +) + +//Mux value, this value permits do not have two identicals values in the parallel instances +type StoreAllCN struct { + mu sync.Mutex + cn map[string]struct{} +} + +var allNames = StoreAllCN{cn: make(map[string]struct{})} + +//Type used for the tests +type attributes struct { + Name string + Data []string +} + +type data_DN struct { + DN string + Attributes []attributes +} + +type instance struct { + numberUsers, numberGroups int + dataGroups, dataUsers []data_DN + logging *ldap.Conn +} + +//Create a new object instance +//With this instance, we can obtain an isolated container where +//we have our users and groups. It allows to run tests in parallel. +func NewInstance(numberUsers, numberGroups int) (*instance, error) { + l, err := Connect() + if err != nil { + return nil, err + } + + logging.Level = logrus.InfoLevel + + inst := instance{ + numberUsers: numberUsers, + numberGroups: numberGroups, + dataGroups: []data_DN{}, + dataUsers: []data_DN{}, + logging: l, + } + + err = inst.createOrganizationnalUnit() + if ldap.IsErrorWithCode(err, uint16(68)) { + logging.Warn("OrganizationnalUnit already created") + err = nil + } + if err != nil { + return nil, err + } + + err = inst.CreateGroups() + if err != nil { + return nil, err + } + err = inst.CreateUsers() + if err != nil { + return nil, err + } + + return &inst, nil +} + +//Part: Created users or groups or OU + +func (inst *instance) createOrganizationnalUnit() error { + dn := []string{"ou=groups,dc=deuxfleurs,dc=fr", "ou=users,dc=deuxfleurs,dc=fr"} + attributes := []map[string][]string{{ + "description": []string{"OrganizationalUnit qui regroupe tous les groupes"}, + "objectclass": []string{"organizationalUnit", "top"}, + "ou": []string{"groups"}, + "structuralobjectclass": []string{"organizationalUnit"}, + }, + { + "description": []string{"OrganizationalUnit qui regroupe tous les users"}, + "objectclass": []string{"organizationalUnit", "top"}, + "ou": []string{"users"}, + "structuralobjectclass": []string{"organizationalUnit"}, + }, + } + + for index := range dn { + err := inst.Add_Request(dn[index], attributes[index]) + if err != nil { + return err + } + } + return nil + +} + +//Part: Create User or group + +func (inst *instance) CreateUsers() (err error) { + + dn := "cn=%s,ou=users,dc=deuxfleurs,dc=fr" + attributes := map[string][]string{ + "displayname": {}, + "objectclass": {"inetOrgPerson", "organizationalPerson", "person", "top"}, + "structuralobjectclass": {"inetOrgPerson"}, + } + + du, err := inst.create(dn, []string{"displayname"}, inst.numberUsers, attributes, inst.dataUsers) + if err == nil { + inst.dataUsers = du + } + return err +} + +func (inst *instance) CreateGroups() error { + dn := "cn=%s,ou=groups,dc=deuxfleurs,dc=fr" + attributes := map[string][]string{ + "description": {}, + "objectclass": {"groupOfNames", "top"}, + "structuralobjectclass": {"groupOfNames"}, + } + + dg, err := inst.create(dn, []string{"description"}, inst.numberGroups, attributes, inst.dataGroups) + if err == nil { + inst.dataGroups = dg + } + + return err +} + +//Hard Function: She does: +//- generate an unique name +//- store the Data of each AddRequest in instance struct +//- send AddRequest to Bottin +func (inst *instance) create(dn string, unique_attr []string, number int, attributes map[string][]string, data []data_DN) ([]data_DN, error) { + for i := 0; i < number; i++ { + name := inst.GenerateName() + + datDn := data_DN{DN: fmt.Sprintf(dn, name)} + + for _, value := range unique_attr { + attributes[value] = []string{name} + } + + datDn.Attributes = MapAttToStruct(attributes) + data = append(data, datDn) + + err := inst.Add_Request(fmt.Sprintf(dn, name), attributes) + if err != nil { + return nil, err + } + } + return data, nil +} + +//Part: clean + +func (inst *instance) Clean() error { + err := inst.CleanGroups() + if err != nil { + return err + } + err = inst.CleanUsers() + return err +} + +func (inst *instance) CleanUsers() error { + err := inst.clean(inst.dataUsers) + if err != nil { + return err + } + inst.dataUsers = []data_DN{} + return err +} + +func (inst *instance) CleanGroups() error { + err := inst.clean(inst.dataGroups) + if err != nil { + return err + } + inst.dataGroups = []data_DN{} + return err +} + +func (inst *instance) clean(stock []data_DN) error { + logging.Debugf("Delete %d elements.", len(stock)) + for _, value := range stock { + err := inst.Delete_Request(value.DN) + if err != nil { + return err + } + } + return nil +} + +//Part: Verify if a data_Dn is a group or an user +func (inst *instance) VerifyUser(user data_DN) (bool, error) { + dn := "ou=users,dc=deuxfleurs,dc=fr" + cn := strings.Split(user.DN, ",")[0] + filter := fmt.Sprintf("(%s)", cn) + + res, err := inst.Search_Request(dn, filter, []string{"cn"}) + + return len(res.Entries) == 1, err +} + +func (inst *instance) VerifyGroup(group data_DN) (bool, error) { + dn := "ou=groups,dc=deuxfleurs,dc=fr" + cn := strings.Split(group.DN, ",")[0] + filter := fmt.Sprintf("(%s)", cn) + + res, err := inst.Search_Request(dn, filter, []string{"cn"}) + + return len(res.Entries) == 1, err +} + +//Part: Add user in a group +func (inst *instance) AddUserInGroup(user, group data_DN) error { + + err := inst.Modify_Request(group.DN, nil, nil, map[string][]string{ + "member": {user.DN}, + }) + return err +} + +func (inst *instance) AddUserSliceInGroup(users_cn []string, group_dn string) error { + + err := inst.Modify_Request(group_dn, nil, nil, map[string][]string{ + "member": users_cn, + }) + return err +} + +//Part: modify, add, delete data_DN struct + +func AddAtt(name string, data []string, dat data_DN) data_DN { + dat.Attributes = append(dat.Attributes, attributes{ + Name: name, + Data: data, + }) + + logging.Debug(fmt.Sprintf("Attributes %s add from %s.", name, dat.DN)) + return dat +} + +func DelAtt(name string, dat data_DN) data_DN { + for index, value := range dat.Attributes { + if value.Name == name { + dat.Attributes[index] = dat.Attributes[len(dat.Attributes)-1] + //tmp := dat.Attributes[:len(dat.Attributes)-1] + dat.Attributes = []attributes{} + logging.Debugf("Attributes %s delete from %s.", name, dat.DN) + return dat + } + } + logging.Debugf("Can't delete attribute %s from %s.", name, dat.DN) + return dat +} + +func ReplaceAtt(name string, data []string, dat data_DN) data_DN { + for index, value := range dat.Attributes { + if value.Name == name { + dat.Attributes[index] = attributes{ + Name: name, + Data: data, + } + logging.Debugf("Replace attributes %s from %s succesful..", name, dat.DN) + return dat + } + } + logging.Debugf("Can't replace attributes %s from %s.", name, dat.DN) + return dat +} diff --git a/test/functionTest.go b/test/functionTest.go new file mode 100644 index 0000000..e8d95ab --- /dev/null +++ b/test/functionTest.go @@ -0,0 +1,173 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/go-ldap/ldap/v3" +) + +const default_users, default_groups = 1000, 1000 + +func Init() (*instance, error) { + inst, err := NewInstance(default_users, default_groups) + return inst, err +} + +//Part to compare our datas +func (inst *instance) CompareOurDataWithConsul() (bool, error) { + if ok, err := inst.VerifyOurData(inst.dataUsers); !ok { + return false, err + } + if ok, err := inst.VerifyOurData(inst.dataGroups); !ok { + return false, err + } + return true, nil +} + +func (inst *instance) VerifyOurData(tabData []data_DN) (bool, error) { + for _, value := range tabData { + names := getNamesAtt(value) + cn := strings.Split(value.DN, ",")[0] + res, err := inst.Search_Request(value.DN, fmt.Sprintf("(&(%s))", cn), names) + if err != nil { + return false, err + } + if len(res.Entries) != 1 { + return false, fmt.Errorf("expected 1 entry, but found %d entry/ies", len(res.Entries)) + } + if !Compare(value, res.Entries[0]) { + return false, fmt.Errorf("no match with the DN: %s", value.DN) + } + } + return true, nil +} + +func Compare(dat data_DN, ent *ldap.Entry) bool { + for _, value := range dat.Attributes { + logging.Debugf("Attributes from %s is now: %s.", dat.DN, dat.Attributes) + entVal := GetAttributeValuesBottin(ent, value.Name) + logging.Debugf("Values of the Entry: attributName: %s, Values: %s.", value.Name, entVal) + if !CompareSliceString(entVal, value.Data) { + logging.Debugf("Values expected: %s, values found: %s.", value.Data, entVal) + return false + } + } + return true +} + +//Part modify datas +func (inst *instance) ModifyRandomAllData() error { + dg, err := inst.ModifyRandom(inst.dataGroups, []string{"description"}) + if err != nil { + return err + } else { + inst.dataGroups = dg + } + + dg, err = inst.ModifyRandom(inst.dataUsers, []string{"displayname"}) + if err != nil { + return err + } else { + inst.dataUsers = dg + } + return nil +} + +//Function which modify random way the attributes in attName of a data_DN's slice, it can delete, replace and delete +//The function modify also in the dat object +func (inst *instance) ModifyRandom(dat []data_DN, attName []string) ([]data_DN, error) { + for index, value := range dat { + del := make(map[string][]string) + add := make(map[string][]string) + replace := make(map[string][]string) + + for _, att := range attName { + + switch selNumber := R.Intn(3); selNumber { + case 0: + del[att] = []string{} + value = DelAtt(att, value) + logging.Debug(fmt.Sprintf("Delete the attribute %s of the DN %s.", att, value.DN)) + case 1: + name := inst.GenerateName() + value = AddAtt(name, []string{name}, value) + add[name] = []string{name} + logging.Debug(fmt.Sprintf("Add the attribute %s with value %s of the DN %s.", name, name, value.DN)) + case 2: + name := inst.GenerateName() + value = ReplaceAtt(att, []string{name}, value) + replace[att] = []string{name} + logging.Debug(fmt.Sprintf("Replace the attribute %s with value %s of the DN %s.", att, name, value.DN)) + } + + } + + err := inst.Modify_Request(value.DN, add, del, replace) + if err != nil { + return dat, err + } + dat[index] = value + } + return dat, nil +} + +//Add all users in a random group +func (inst *instance) AddAllUsersInGroup() error { + for _, value := range inst.dataGroups { + valueRand := (len(inst.dataUsers) + 1) / 30 + if valueRand == 0 { + valueRand = 1 + } + numberOfMembers := R.Intn(valueRand) + 1 + logging.Debugf("%s will be have %d members.", value.DN, numberOfMembers) + + groupMemory := make(map[int]struct{}) + users_cn := []string{} + + for i := 0; i < numberOfMembers; i++ { + selectGroup := R.Intn(len(inst.dataUsers)) + for _, ok := groupMemory[selectGroup]; ok; _, ok = groupMemory[selectGroup] { + selectGroup = R.Intn(len(inst.dataUsers)) + + logging.Debugf("Search an other member. The value is %d , and we have %d members available.", selectGroup, len(inst.dataUsers)) + } + groupMemory[selectGroup] = struct{}{} + + users_cn = append(users_cn, inst.dataGroups[selectGroup].DN) + + } + err := inst.AddUserSliceInGroup(users_cn, value.DN) + if err != nil { + return err + } + + } + return nil +} + +//Check if the groups in memberOf exist in Consul +func (inst *instance) CheckMemberOf() (bool, error) { + for _, value := range inst.dataUsers { + cn := strings.Split(value.DN, ",")[0] + res, err := inst.Search_Request(value.DN, fmt.Sprintf("(&(%s))", cn), []string{"memberOf"}) + if err != nil { + return false, err + } + if len(res.Entries) != 1 { + return false, fmt.Errorf("expected 1 entry, but found %d entry/ies", len(res.Entries)) + } + attValues := GetAttributeValuesBottin(res.Entries[0], "memberOf") + for _, dnGroup := range attValues { + logging.Debugf("Verify if the group %s exist...", dnGroup) + ok, err := inst.VerifyGroup(data_DN{DN: dnGroup}) + if err != nil { + return false, err + } + if !ok { + return false, fmt.Errorf("don't found the group: %s", dnGroup) + } + } + } + return true, nil +} diff --git a/test_automatic/go.mod b/test/go.mod similarity index 100% rename from test_automatic/go.mod rename to test/go.mod diff --git a/test_automatic/go.sum b/test/go.sum similarity index 90% rename from test_automatic/go.sum rename to test/go.sum index 8e94420..9c882d3 100644 --- a/test_automatic/go.sum +++ b/test/go.sum @@ -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/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= @@ -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/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= diff --git a/test/handler.go b/test/handler.go new file mode 100644 index 0000000..0e7a95b --- /dev/null +++ b/test/handler.go @@ -0,0 +1,138 @@ +package main + +import ( + "fmt" + "math/rand" + "os" + + ldap "github.com/go-ldap/ldap/v3" + "github.com/sirupsen/logrus" +) + +const maxlength_generateName, minlength_generateName = 25, 3 + +const bindusername = "cn=admin,dc=deuxfleurs,dc=fr" +const adresse = "127.0.0.1" +const port = 1389 + +var logging = logrus.New() + +const seed = 654258 + +var R = rand.New(rand.NewSource(seed)) + +var bindpassword = "sf7yO52NCuE" + +//Handler just to facilite the print error +func PrintError(LDAPError error) { + if LDAPError != nil { + logging.Fatal(LDAPError) + } +} + +//Generate an unique name, which store in all_names +func (inst *instance) GenerateName() (name string) { + alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + length := R.Intn(maxlength_generateName) + minlength_generateName + + //Check if this name not exist already + //Lock thhis variable because she is hared with other goroutine + allNames.mu.Lock() + for only_one := true; only_one; _, only_one = allNames.cn[name] { + //Create the name + for i := 0; i < length; i++ { + name += string(alphabet[R.Intn(len(alphabet))]) + } + + } + //Add the new name in the map to store this one + allNames.cn[name] = struct{}{} + allNames.mu.Unlock() + logging.Debug(fmt.Sprintf("Name generated: %s.", name)) + return +} + +//Handler to around the bug with MessageId +func (inst *instance) Reconnect() (err error) { + inst.logging.Close() + inst.logging, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", adresse, port)) + if err != nil { + return + } + err = inst.logging.Bind(bindusername, bindpassword) + //logging.Debug("Reconnect succesful") + return +} + +//Transform attributes in map format to the struct attributes +func MapAttToStruct(att map[string][]string) []attributes { + resultat := []attributes{} + for key, value := range att { + logging.Debug(fmt.Sprintf("Transform: key: %s, values: %s to attributes struct.\n", key, value)) + resultat = append(resultat, attributes{ + Name: key, + Data: value, + }) + } + return resultat +} + +func Connect() (*ldap.Conn, error) { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", adresse, port)) + if err != nil { + return nil, err + } + + if key, ok := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW"); ok { + bindpassword = key + } + + err = l.Bind(bindusername, bindpassword) + logging.Debug("Connection succesful") + return l, err +} + +//Handler to get only attributes names +func getNamesAtt(dat data_DN) []string { + resultat := []string{} + for _, values := range dat.Attributes { + resultat = append(resultat, values.Name) + } + return resultat +} + +//Handler to compare slice string +func CompareSliceString(string1, string2 []string) bool { + if len(string1) != len(string2) { + return false + } else { + for index := range string1 { + if string1[index] != string2[index] { + return false + } + } + } + return true +} + +//Handler to remove an element in slice string +func DeleteElementSliceString(s string, sSlice []string) []string { + + for index, value := range sSlice { + if value == s { + sSlice[index] = sSlice[len(sSlice)-1] + return sSlice[:len(sSlice)-1] + } + } + return sSlice +} + +//Get attributes entry values bottin bug +func GetAttributeValuesBottin(ent *ldap.Entry, name string) (res []string) { + for _, val := range ent.Attributes { + if val.Name == name { + res = append(res, val.Values...) + } + } + return +} diff --git a/test/request.go b/test/request.go new file mode 100644 index 0000000..b693211 --- /dev/null +++ b/test/request.go @@ -0,0 +1,67 @@ +package main + +import ( + ldap "github.com/go-ldap/ldap/v3" +) + +func (inst *instance) Add_Request(dn string, attributes map[string][]string) error { + //Create the AddRequest + req := ldap.NewAddRequest(dn, nil) + for key, value := range attributes { + req.Attribute(key, value) + } + + //Send the request + err := inst.logging.Add(req) + //@FIXME: Remove when you try to correct the bug MessageID + inst.Reconnect() + return err + +} + +//Use enum to select Replace,Delete,Modify +func (inst *instance) Modify_Request(dn string, add_attributes, delete_attributes, replace_attributes map[string][]string) error { + modifyReq := ldap.NewModifyRequest(dn, nil) + + for key, value := range add_attributes { + modifyReq.Add(key, value) + } + + for key, value := range delete_attributes { + modifyReq.Delete(key, value) + } + + for key, value := range replace_attributes { + modifyReq.Replace(key, value) + } + + err := inst.logging.Modify(modifyReq) + //@FIXME: Remove when you try to correct the bug MessageID + inst.Reconnect() + return err +} + +func (inst *instance) Delete_Request(dn string) error { + del := ldap.NewDelRequest(dn, nil) + + err := inst.logging.Del(del) + //@FIXME: Remove when you try to correct the bug MessageID + inst.Reconnect() + return err +} + +func (inst *instance) Search_Request(dn, filter string, name_attributes []string) (*ldap.SearchResult, error) { + searchReq := ldap.NewSearchRequest( + dn, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + filter, + name_attributes, + nil, + ) + + res, err := inst.logging.Search(searchReq) + logging.Debugf("Search Request made with: dn: %s, filter: %s, attributes: %s. \n", dn, filter, name_attributes) + //@FIXME: Remove when you try to correct the bug MessageID + inst.Reconnect() + return res, err +} diff --git a/test/runner.sh b/test/runner.sh new file mode 100755 index 0000000..0b97068 --- /dev/null +++ b/test/runner.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -ex + +echo $BOTTIN_DEFAULT_ADMIN_PW +consul agent -dev > /dev/null 2>&1 & +sleep 2 +cp test/config.json.test config.json +./bottin > /dev/null 2>&1 & +sleep 1 +./test/test -test.v -test.failfast -test.short -test.run TestPrincipal +./test/test -test.v -test.failfast -test.run TestPrincipal/B= diff --git a/test_automatic/Scan_Bad_Packets.pcapng b/test_automatic/Scan_Bad_Packets.pcapng deleted file mode 100644 index cc209f5..0000000 Binary files a/test_automatic/Scan_Bad_Packets.pcapng and /dev/null differ diff --git a/test_automatic/Scan_Good_Packets.pcapng b/test_automatic/Scan_Good_Packets.pcapng deleted file mode 100644 index 5c87f82..0000000 Binary files a/test_automatic/Scan_Good_Packets.pcapng and /dev/null differ diff --git a/test_automatic/integration.go b/test_automatic/integration.go deleted file mode 100644 index d55d280..0000000 --- a/test_automatic/integration.go +++ /dev/null @@ -1,431 +0,0 @@ -package main - -import ( - "github.com/go-ldap/ldap/v3" - "fmt" - log "github.com/sirupsen/logrus" - "math/rand" - "strings" - "errors" - "os" -) - - -const bindusername = "cn=admin,dc=deuxfleurs,dc=fr" -const adresse = "127.0.0.1" -const port = 1389 -var bindpassword string - -var all_names = make(map[string]struct{}) - - - -func printError(LDAPError error) { - if LDAPError != nil { - log.Fatal(LDAPError) - } -} - -func createOU(l *ldap.Conn) error { - - req := ldap.NewAddRequest("ou=groups,dc=deuxfleurs,dc=fr",nil) - req.Attribute("description",[]string{"OrganizationalUnit qui regroupe tous les groupes"}) - req.Attribute("objectclass",[]string{"organizationalUnit", "top"}) - req.Attribute("ou",[]string{"groups"}) - req.Attribute("structuralobjectclass", []string{"organizationalUnit"}) - - err := l.Add(req) - if err != nil { - return err - } - - req = ldap.NewAddRequest("ou=users,dc=deuxfleurs,dc=fr",nil) - req.Attribute("description",[]string{"OrganizationalUnit qui regroupe tous les utilisateurs"}) - req.Attribute("objectclass",[]string{"organizationalUnit", "top"}) - req.Attribute("ou",[]string{"users"}) - req.Attribute("structuralobjectclass", []string{"organizationalUnit"}) - - err = l.Add(req) - return err -} - -func generateName(r *rand.Rand) (name string) { - for only_one := true; only_one; _, only_one = all_names[name]{ - name = fmt.Sprintf("%d",r.Int()) - } - all_names[name] = struct{}{} - log.Debug(fmt.Sprintf("Name generated: %s.\n", name)) - return -} - -func createGroup(r *rand.Rand, l *ldap.Conn) (tab_AddRequest []ldap.AddRequest, err error) { - StructuralObjectClass := []string{"groupOfNames"} - ObjectClass := []string{"groupOfNames","top"} - - - - for i := 0; i<20; i++ { - //Generate name and check if he is unique - name := generateName(r) - - req := ldap.NewAddRequest(fmt.Sprintf("cn=%s,ou=groups,dc=deuxfleurs,dc=fr",name),nil) - req.Attribute("description",[]string{generateName(r)}) - req.Attribute("objectclass",ObjectClass) - req.Attribute("structuralobjectclass",StructuralObjectClass) - - err = l.Add(req) - if err != nil { - log.Warn(fmt.Sprintf("Erreur survenue sur la création du [%d] groupe.\n",i)) - return nil, err - } - tab_AddRequest = append(tab_AddRequest, *req) - - } - return -} - -func createUser(r *rand.Rand, l *ldap.Conn) (tab_AddRequest []ldap.AddRequest, err error) { - StructuralObjectClass := []string{"inetOrgPerson"} - ObjectClass := []string{"inetOrgPerson","organizationalPerson","person","top"} - - for i := 0; i<20; i++ { - name := generateName(r) - - req := ldap.NewAddRequest(fmt.Sprintf("cn=%s,ou=users,dc=deuxfleurs,dc=fr",name),nil) - req.Attribute("displayname",[]string{generateName(r)}) - req.Attribute("objectclass",ObjectClass) - req.Attribute("structuralobjectclass",StructuralObjectClass) - - err = l.Add(req) - if err != nil { - log.Warn(fmt.Sprintf("Erreur survenue sur la création du [%d] user.\n",i)) - return nil, err - } - tab_AddRequest = append(tab_AddRequest, *req) - - } - return -} - -func search_attributes(tab_Attributes []ldap.Attribute, tipe string) (*ldap.Attribute) { - for _,att := range tab_Attributes { - if att.Type == tipe { - return &att - } - } - return nil -// return ldap.Attribute{} -} - -func test_attributes(l *ldap.Conn, tab_AddRequest []ldap.AddRequest, filter_objectclass, user_or_group string) (err error) { - for _, addRequest := range tab_AddRequest { - - //On prend le cn en supposant qu'il est unique - cn := strings.Split(addRequest.DN,",")[0] - - //On crée la requête pour la recherche - search_req := ldap.NewSearchRequest( - fmt.Sprintf("ou=%s,dc=deuxfleurs,dc=fr",user_or_group), - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - fmt.Sprintf("(&(objectclass=%s)(%s))", filter_objectclass,cn), - []string{"displayname","objectclass","structuralobjectclass"}, - nil, - ) - - //On lance la recherche - result, err := l.Search(search_req) - if err != nil { - return err - } - if len(result.Entries) != 1 { - return errors.New("Test a trouvé plusieurs displaynames en commun ou en a trouvé aucun") - } - - //On compare les attributs qu'on a reçu avec les attributs qu'on a envoyé - result_attributes := result.Entries[0].Attributes - log.Debug(fmt.Sprintf("La longueur est de %d, contient : \n %s.\n",len(result_attributes), result_attributes)) - - //Notre recherche crée un attribut par valeur, même si les valeurs viennent du même nom d'attribut - //Par exemple: objectclass possède 4 valeurs. Alors on aura 4 EntryAttribute qui contient chacune une des 4 valeurs de l'attribut objectclass - - //j est l'indice qui représente la j-ème valeur de notre attribut - var j int - var att *ldap.Attribute - for i,attributes := range result_attributes { - //On cherche l'attribut de l'user i qui a le même nom que celui qu'on a reçu et qu'on traite dans cette boucle - if j == 0 { - att = search_attributes(addRequest.Attributes, attributes.Name) - if att == nil { - return errors.New(fmt.Sprintf("Error: test_attributes - Don't find match name attributes. We search %s.\n", attributes.Name)) - } - } - log.Debug(fmt.Sprintf("Le nom de l'attribut est %s, sa valeur est: \n %s.", att.Type, att)) - - if j >= len(att.Vals) || att.Vals[j] != attributes.Values[0] { - return errors.New(fmt.Sprintf("Error: test_attributes - Theses values aren't the same: %d, %d",att.Vals, attributes.Values)) - } - - if i+1 < len(result_attributes) && result_attributes[i+1].Name == attributes.Name { - j += 1 - } else { j = 0} - } - - } - return nil -} - -func clean(l *ldap.Conn, AddReq_users, AddReq_groups []ldap.AddRequest,user, group bool) (err error){ - log.Debug("Debut clean") - if(user) { - for _,req := range AddReq_users { - delReq := ldap.NewDelRequest(req.DN,nil) - err = l.Del(delReq) - if err != nil { - return - } - } - } - if group { - for _,req := range AddReq_groups { - delReq := ldap.NewDelRequest(req.DN, nil) - err = l.Del(delReq) - if err != nil { - return - } - } - } - defer log.Debug("Fin clean") - return -} - -func test_modify_attributes(l *ldap.Conn, r *rand.Rand, tab_AddReq []ldap.AddRequest, tab_type_name []string) (err error) { - for _, AddReq := range tab_AddReq { - modReq := ldap.NewModifyRequest(AddReq.DN,nil) - for _, type_name := range tab_type_name { - newName := generateName(r) - modReq.Replace(type_name, []string{newName}) - att := search_attributes(AddReq.Attributes, type_name) - att.Vals[0] = newName - } - err = l.Modify(modReq) - if err != nil { - return - } - - } - return -} - -func add_user_in_groups(l *ldap.Conn, r *rand.Rand, users, groups []ldap.AddRequest) (err error) { - for _,group := range groups { - numberUsers := r.Intn(19) + 1 //Always a minimum of 1 user - list_users := []string{} - for i:=0; i < numberUsers; i++ { - list_users = append(list_users, users[i].DN) - } - modifyReq := ldap.NewModifyRequest( group.DN, nil) - modifyReq.Add("member", list_users) - - err = l.Modify(modifyReq) - if err != nil { - log.Warn(fmt.Sprintf("Error: ModifyReq failed, func:add_users_in_groups from group:\n %d",group)) - return - } - } - return -} - -func delete_groups(l *ldap.Conn, groups []ldap.AddRequest) (list map[string][]string ,err error) { - list = make(map[string][]string) - for _, group := range groups { - //Get lists_users - cn := strings.Split(group.DN,",")[0] - search_req := ldap.NewSearchRequest( - "ou=groups,dc=deuxfleurs,dc=fr", - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - fmt.Sprintf("(&(objectclass=groupOfNames)(%s))",cn), - []string{"member"}, - nil, - ) - res , err := l.Search(search_req) - if err != nil { - log.Warn(fmt.Sprintf("Error Search: func: delete_groups_and_check_memberOf, from group: \n %d", group)) - return list, err - } - if len(res.Entries) != 1 { - err = errors.New(fmt.Sprintf("SearchResult get: %s, SearchResult wanted: 1", len(res.Entries))) - return list, err - } - EntryAtt := res.Entries[0].Attributes - list_users := []string{} - for _, att := range EntryAtt { - list_users = append(list_users ,att.Values[0]) - } - - //Del group - del := ldap.NewDelRequest( group.DN, nil) - err = l.Del(del) - if err != nil { - return list, err - } - list[group.DN] = list_users - } - return -} - -func check_memberOf(l *ldap.Conn, list map[string][]string) (err error) { - //Check the memberOf of all users - for groupeDN,_ := range list{ - search_req := ldap.NewSearchRequest( - "ou=users,dc=deuxfleurs,dc=fr", - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,0 ,0, false, - fmt.Sprintf("(&(objectclass=inetOrgPerson)(memberOf=%s))",groupeDN), - []string{"cn"}, - nil, - ) - res, err := l.Search(search_req) - if err != nil { - return err - } - if len(res.Entries) != 0 { - err = errors.New(fmt.Sprintf("L'user '%s' a encore le DN d'un groupe supprimé: %s",res.Entries[0].Attributes[0].Values[0],groupeDN)) - return err - } - } - return err -} - -func reconnect(l *ldap.Conn) (l_nouv *ldap.Conn, err error){ - l.Close() - l_nouv, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d",adresse,port)) - if err != nil { - return - } - err = l_nouv.Bind(bindusername, bindpassword) - return -} - - -func main() { - var ok bool - bindpassword, ok = os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW") - if !ok { - if len(os.Args) == 2 { - bindpassword = os.Args[1] - } else { - bindpassword = "" - } - } - - log.Info(fmt.Sprintf("Password selected: %s",bindpassword)) - //log.SetLevel(log.TraceLevel) - - //Create a connection with Bottin server - l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", adresse, port)) - //l.Debug = true - printError(err) - - //Bind with the admin account generated - err = l.Bind(bindusername, bindpassword) - printError(err) - - //Create our object Rand, it's important to always have the same values - source := rand.NewSource(666475745) - r := rand.New(source) - log.Info(fmt.Sprintf("The seed of the rand object is %d.\n",r.Seed)) - - //Create user and groups OrgaUnit - err = createOU(l) - if ldap.IsErrorWithCode(err, uint16(68)) { - log.Warn("Les OrganizationalUnit users et groups sont déjà présents.") - }else { - printError(err) - log.Info("Création des OU de groups et users") - } - - //Create random groups - tab_AddRequest_groups, err := createGroup(r, l) - printError(err) - log.Info(fmt.Sprintf("Création des groupes aléatoirement réussi: %d\n", len(tab_AddRequest_groups))) - - //Create random users - tab_AddRequest_users, err := createUser(r, l) - printError(err) - log.Info(fmt.Sprintf("Création des users aléatoirement réussi: %d\n", len(tab_AddRequest_users))) - - //Search and compare attribute Users. (We keep Attribute object from 'Create random users' and compare with the result of our search) - err = test_attributes(l,tab_AddRequest_users, "inetOrgPerson","users") - printError(err) - log.Info("Tous les attributs users insérés dans Consul ont été vérifiés..\n") - - //Search and compare attributes Groups - err = test_attributes(l,tab_AddRequest_groups, "groupOfNames","groups") - printError(err) - log.Info("Tous les attributs groups insérés dans Consul ont été vérifiés.\n") - - - //Close the connection and open an other. If we don't do this, bottin server send a wrong answer. Comment this part if you want to try this - l,err = reconnect(l) - printError(err) - //Modify attributes users and groups. - - //Modify users' attributes and check them - - log.Debug(fmt.Sprintf("Les valeurs sont:\n %s", tab_AddRequest_users)) - err = test_modify_attributes(l, r, tab_AddRequest_users, []string{"displayname"}) - printError(err) - log.Debug("Modifications users faites") - - //Check if the attributes are correct: - err = test_attributes(l,tab_AddRequest_users, "inetOrgPerson", "users") - printError(err) - log.Info("Les modifications ont bien été prises en compte") - log.Debug(fmt.Sprintf("Les nouvelles valeurs sont:\n %s", tab_AddRequest_users)) - - - - - //Modify users' attributes and check them - err = test_modify_attributes(l, r, tab_AddRequest_groups, []string{"description"}) - printError(err) - log.Info("Modifications groups faites") - - //Check if the attributes are correct: - err = test_attributes(l,tab_AddRequest_groups, "groupOfNames", "groups") - printError(err) - log.Info("Les modifications ont bien été prises en compte") - - //Close the connection - l, err = reconnect(l) - printError(err) - - //Add users in group, search them, delete several samples and search again to be sur it's good - err = add_user_in_groups(l, r, tab_AddRequest_users, tab_AddRequest_groups) - printError(err) - log.Info("Ajout d'users dans les groupes fait") - - //Close the connection - l, err = reconnect(l) - printError(err) - - list, err := delete_groups(l, tab_AddRequest_groups) - printError(err) - log.Info("groupe supprimé") - - - l,err = reconnect(l) - printError(err) - - err = check_memberOf(l, list) - printError(err) - log.Info("Le memberOf a été correctement vidé") - - //Clean: Delete all users and groups (not OU users and groups) - err = clean(l, tab_AddRequest_users, tab_AddRequest_groups, true, false) - printError(err) - log.Info("Clean succes") - - defer os.Exit(0) - return - -} diff --git a/test_automatic/rapport_beug_bottin.txt b/test_automatic/rapport_beug_bottin.txt deleted file mode 100644 index 1669e14..0000000 --- a/test_automatic/rapport_beug_bottin.txt +++ /dev/null @@ -1,40 +0,0 @@ -Introduction et Observation premières: - - Lors de la réalisation de mon code Go, j'ai trouvé un beug qui provoquait l'arrêt de mon programme car Bottin envoyé une réponse erronée à mon programme. - Dans logs de Bottin je ne voyais aucune erreur, Bottin n'avait pas cessé de fonctionner. Il s'arrêtait à chaque fois sur mes requêtes Del (mes dernières). - - -Reproduction du beug: - - Pour reproduire le beug, il suffit de lancer le programme interrogation.go et de commenter les lignes suivantes : - -260 //Close the connection and open an other. If we don't do this, bottin server send a wrong answer. Comment this part if you want to try this -261 l.Close() -262 l, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d",adresse, port)) -263 printError(err) -264 err = l.Bind(bindusername, bindpassword) -265 printError(err) - - Ainsi on obtient l'erreur suivante: - -2021/07/07 01:25:51 Received unexpected message -128, false - - -Test réalisé pour comprendre la source du problème: - - Ma première hypothèses fut que j'envoyais trop de requêtes dans un court laps de temps et ainsi Bottin ou Consul ne pouvait pas suivre. -J'ai placé un sleep de 50 puis de 100 Millisecondes entre chaque requête, j'obtenais toujours la même erreur. -J'ai essayé de mettre un sleep de 10 secondes avant mes requêtes de suppression mais j'obtenais toujours la même chose. - -Hack pour résoudre: - - La première solution qui a fonctionné était de réduire le nombre de requêtes en n'exécutant pas certains tests random. - La dernière solutions qui est utilisée: - Fermée la connexion puis la réouvrir permet de palier à ce problème - -Hypothèses du problème ?: - - Existerait-il un Buffer par Bind qui se remplirait ? Et lorsque celui-ci est plein, ne renvoie pas d'erreur juste n'arrive plus à répondre. - -Erwan DUFOUR -Deuxfleurs diff --git a/test_automatic/start_test.sh b/test_automatic/start_test.sh deleted file mode 100755 index e7a1712..0000000 --- a/test_automatic/start_test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -#export BOTTIN_DEFAULT_ADMIN_PW=$(openssl rand -base64 24) -echo $BOTTIN_DEFAULT_ADMIN_PW -consul agent -dev > /dev/null 2>&1 & -sleep 2 -cp test_automatic/config.json.test config.json -./bottin > /dev/null 2>&1 & -sleep 1 -./test_automatic/integration -rm config.json -exit 0