diff --git a/.drone.yml b/.drone.yml index 9eea880..c7fb744 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,13 +1,25 @@ --- -pipeline: - build: - image: golang:stretch - commands: - - go get -d -v - - go build -v +kind: pipeline +name: bottin + +steps: +- name: build + image: golang:stretch + commands: + - go get -d -v + - go build -v + - cd test + - go test -i -c -o test + +- name: test_bottin + image: consul:latest + environment: + BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1 + commands: + - ash test/runner.sh --- kind: signature -hmac: 8f49fdf0e4abb0790827eed7cac8eedd5e11705d1fa01954a84929933eb7b254 +hmac: ff246a04c3df8a2f39c8b446dea920622d61950e6caaac886931bdb05d0706ed ... diff --git a/.gitignore b/.gitignore index d739b6f..2a2f53c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ bottin bottin.static config.json +test/test diff --git a/go.mod b/go.mod index 75156e0..22c2023 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module bottin go 1.13 require ( + github.com/go-ldap/ldap/v3 v3.3.0 github.com/google/uuid v1.1.1 github.com/hashicorp/consul/api v1.3.0 github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 diff --git a/go.sum b/go.sum index c2db0e1..e4825c4 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -7,6 +9,11 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= +github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap v2.5.1+incompatible h1:Opaoft5zMW8IU/VRULB0eGMBQ9P5buRvCW6sFTRmMn8= +github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ= +github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= @@ -79,12 +86,19 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/main.go b/main.go index 13d3da6..b490aa7 100644 --- a/main.go +++ b/main.go @@ -320,12 +320,19 @@ func (server *Server) init() error { return err } - admin_pass := make([]byte, 8) - _, err = rand.Read(admin_pass) - if err != nil { - return err + + admin_pass_str, environnement_variable_exist := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW") + if !environnement_variable_exist { + admin_pass := make([]byte, 8) + _, err = rand.Read(admin_pass) + if err != nil { + return err + } + admin_pass_str = base64.RawURLEncoding.EncodeToString(admin_pass) + } else { + server.logger.Debug("BOTTIN_DEFAULT_ADMIN_PW environment variable is set, using it for admin's password") } - admin_pass_str := base64.RawURLEncoding.EncodeToString(admin_pass) + admin_pass_hash := SSHAEncode([]byte(admin_pass_str)) admin_dn := "cn=admin," + server.config.Suffix diff --git a/test/bottin_test.go b/test/bottin_test.go new file mode 100644 index 0000000..00c0da9 --- /dev/null +++ b/test/bottin_test.go @@ -0,0 +1,160 @@ +package main + +import ( + "testing" +) + +func TestAddThenDelete(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +func TestConfirmAddAttributes(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Test search_attribute to confirm the Add + if ok, err := inst.CompareOurDataWithConsul(); !ok { + t.Error(err) + } + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +//Modifyrequest Test +func TestModifyRequest(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Test modify all data (groups and users) + err = inst.ModifyRandomAllData() + if err != nil { + t.Error(err) + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +func TestModifyRequestAndCheck(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Test modify all data (groups and users) + err = inst.ModifyRandomAllData() + if err != nil { + t.Error(err) + } + + //Check if the data was modify on Consul + if ok, err := inst.CompareOurDataWithConsul(); !ok { + t.Error(err) + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +func TestAddUserInGroup(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Add users in group + err = inst.AddAllUsersInGroup() + if err != nil { + t.Error(err) + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +func TestDeleteGroupsAfterAddedUsers(t *testing.T) { + t.Parallel() + //SetUp - Create Users and Groups + inst, err := Init() + if err != nil { + t.Error(err) + } + + //Add users in group + err = inst.AddAllUsersInGroup() + if err != nil { + t.Error(err) + } + + //Delete the half groups + number := len(inst.dataGroups) / 2 + err = inst.clean(inst.dataGroups[0:number]) + if err != nil { + t.Error(err) + } + inst.dataGroups = inst.dataGroups[number:len(inst.dataGroups)] + + //Check all the groups in memberOf exist + ok, err := inst.CheckMemberOf() + if err != nil { + t.Error(err) + } + if !ok { + t.Errorf("Found group in memberOf that isn't in Consul.") + } + + //TearDown - Delete all the users and groups created + err = inst.Clean() + if err != nil { + t.Error(err) + } +} + +//Example of paralellism Test +func TestPrincipal(t *testing.T) { + + t.Run("A=1", TestAddThenDelete) + t.Run("A=2", TestModifyRequest) + if !testing.Short() { + t.Run("B=1", TestConfirmAddAttributes) + t.Run("B=2", TestModifyRequestAndCheck) + t.Run("C=1", TestAddUserInGroup) + t.Run("C=2", TestDeleteGroupsAfterAddedUsers) + } + +} diff --git a/test/config.json.test b/test/config.json.test new file mode 100644 index 0000000..bc1eeec --- /dev/null +++ b/test/config.json.test @@ -0,0 +1,13 @@ +{ + "suffix": "dc=deuxfleurs,dc=fr", + "bind": "127.0.0.1:1389", + "acl": [ + "ANONYMOUS::bind:*,ou=users,dc=deuxfleurs,dc=fr:", + "ANONYMOUS::bind:cn=admin,dc=deuxfleurs,dc=fr:", + "*,dc=deuxfleurs,dc=fr::read:*:* !userpassword", + "*::read modify:SELF:*", + "cn=admin,dc=deuxfleurs,dc=fr::read add modify delete:*:*", + "*:cn=admin,ou=groups,dc=deuxfleurs,dc=fr:read add modify delete:*:*" + ] +} + diff --git a/test/create.go b/test/create.go new file mode 100644 index 0000000..4ad8106 --- /dev/null +++ b/test/create.go @@ -0,0 +1,281 @@ +package main + +import ( + "fmt" + "strings" + "sync" + + "github.com/go-ldap/ldap/v3" + "github.com/sirupsen/logrus" +) + +//Mux value, this value permits do not have two identicals values in the parallel instances +type StoreAllCN struct { + mu sync.Mutex + cn map[string]struct{} +} + +var allNames = StoreAllCN{cn: make(map[string]struct{})} + +//Type used for the tests +type attributes struct { + Name string + Data []string +} + +type data_DN struct { + DN string + Attributes []attributes +} + +type instance struct { + numberUsers, numberGroups int + dataGroups, dataUsers []data_DN + logging *ldap.Conn +} + +//Create a new object instance +//With this instance, we can obtain an isolated container where +//we have our users and groups. It allows to run tests in parallel. +func NewInstance(numberUsers, numberGroups int) (*instance, error) { + l, err := Connect() + if err != nil { + return nil, err + } + + logging.Level = logrus.InfoLevel + + inst := instance{ + numberUsers: numberUsers, + numberGroups: numberGroups, + dataGroups: []data_DN{}, + dataUsers: []data_DN{}, + logging: l, + } + + err = inst.createOrganizationnalUnit() + if ldap.IsErrorWithCode(err, uint16(68)) { + logging.Warn("OrganizationnalUnit already created") + err = nil + } + if err != nil { + return nil, err + } + + err = inst.CreateGroups() + if err != nil { + return nil, err + } + err = inst.CreateUsers() + if err != nil { + return nil, err + } + + return &inst, nil +} + +//Part: Created users or groups or OU + +func (inst *instance) createOrganizationnalUnit() error { + dn := []string{"ou=groups,dc=deuxfleurs,dc=fr", "ou=users,dc=deuxfleurs,dc=fr"} + attributes := []map[string][]string{{ + "description": []string{"OrganizationalUnit qui regroupe tous les groupes"}, + "objectclass": []string{"organizationalUnit", "top"}, + "ou": []string{"groups"}, + "structuralobjectclass": []string{"organizationalUnit"}, + }, + { + "description": []string{"OrganizationalUnit qui regroupe tous les users"}, + "objectclass": []string{"organizationalUnit", "top"}, + "ou": []string{"users"}, + "structuralobjectclass": []string{"organizationalUnit"}, + }, + } + + for index := range dn { + err := inst.Add_Request(dn[index], attributes[index]) + if err != nil { + return err + } + } + return nil + +} + +//Part: Create User or group + +func (inst *instance) CreateUsers() (err error) { + + dn := "cn=%s,ou=users,dc=deuxfleurs,dc=fr" + attributes := map[string][]string{ + "displayname": {}, + "objectclass": {"inetOrgPerson", "organizationalPerson", "person", "top"}, + "structuralobjectclass": {"inetOrgPerson"}, + } + + du, err := inst.create(dn, []string{"displayname"}, inst.numberUsers, attributes, inst.dataUsers) + if err == nil { + inst.dataUsers = du + } + return err +} + +func (inst *instance) CreateGroups() error { + dn := "cn=%s,ou=groups,dc=deuxfleurs,dc=fr" + attributes := map[string][]string{ + "description": {}, + "objectclass": {"groupOfNames", "top"}, + "structuralobjectclass": {"groupOfNames"}, + } + + dg, err := inst.create(dn, []string{"description"}, inst.numberGroups, attributes, inst.dataGroups) + if err == nil { + inst.dataGroups = dg + } + + return err +} + +//Hard Function: She does: +//- generate an unique name +//- store the Data of each AddRequest in instance struct +//- send AddRequest to Bottin +func (inst *instance) create(dn string, unique_attr []string, number int, attributes map[string][]string, data []data_DN) ([]data_DN, error) { + for i := 0; i < number; i++ { + name := inst.GenerateName() + + datDn := data_DN{DN: fmt.Sprintf(dn, name)} + + for _, value := range unique_attr { + attributes[value] = []string{name} + } + + datDn.Attributes = MapAttToStruct(attributes) + data = append(data, datDn) + + err := inst.Add_Request(fmt.Sprintf(dn, name), attributes) + if err != nil { + return nil, err + } + } + return data, nil +} + +//Part: clean + +func (inst *instance) Clean() error { + err := inst.CleanGroups() + if err != nil { + return err + } + err = inst.CleanUsers() + return err +} + +func (inst *instance) CleanUsers() error { + err := inst.clean(inst.dataUsers) + if err != nil { + return err + } + inst.dataUsers = []data_DN{} + return err +} + +func (inst *instance) CleanGroups() error { + err := inst.clean(inst.dataGroups) + if err != nil { + return err + } + inst.dataGroups = []data_DN{} + return err +} + +func (inst *instance) clean(stock []data_DN) error { + logging.Debugf("Delete %d elements.", len(stock)) + for _, value := range stock { + err := inst.Delete_Request(value.DN) + if err != nil { + return err + } + } + return nil +} + +//Part: Verify if a data_Dn is a group or an user +func (inst *instance) VerifyUser(user data_DN) (bool, error) { + dn := "ou=users,dc=deuxfleurs,dc=fr" + cn := strings.Split(user.DN, ",")[0] + filter := fmt.Sprintf("(%s)", cn) + + res, err := inst.Search_Request(dn, filter, []string{"cn"}) + + return len(res.Entries) == 1, err +} + +func (inst *instance) VerifyGroup(group data_DN) (bool, error) { + dn := "ou=groups,dc=deuxfleurs,dc=fr" + cn := strings.Split(group.DN, ",")[0] + filter := fmt.Sprintf("(%s)", cn) + + res, err := inst.Search_Request(dn, filter, []string{"cn"}) + + return len(res.Entries) == 1, err +} + +//Part: Add user in a group +func (inst *instance) AddUserInGroup(user, group data_DN) error { + + err := inst.Modify_Request(group.DN, nil, nil, map[string][]string{ + "member": {user.DN}, + }) + return err +} + +func (inst *instance) AddUserSliceInGroup(users_cn []string, group_dn string) error { + + err := inst.Modify_Request(group_dn, nil, nil, map[string][]string{ + "member": users_cn, + }) + return err +} + +//Part: modify, add, delete data_DN struct + +func AddAtt(name string, data []string, dat data_DN) data_DN { + dat.Attributes = append(dat.Attributes, attributes{ + Name: name, + Data: data, + }) + + logging.Debug(fmt.Sprintf("Attributes %s add from %s.", name, dat.DN)) + return dat +} + +func DelAtt(name string, dat data_DN) data_DN { + for index, value := range dat.Attributes { + if value.Name == name { + dat.Attributes[index] = dat.Attributes[len(dat.Attributes)-1] + //tmp := dat.Attributes[:len(dat.Attributes)-1] + dat.Attributes = []attributes{} + logging.Debugf("Attributes %s delete from %s.", name, dat.DN) + return dat + } + } + logging.Debugf("Can't delete attribute %s from %s.", name, dat.DN) + return dat +} + +func ReplaceAtt(name string, data []string, dat data_DN) data_DN { + for index, value := range dat.Attributes { + if value.Name == name { + dat.Attributes[index] = attributes{ + Name: name, + Data: data, + } + logging.Debugf("Replace attributes %s from %s succesful..", name, dat.DN) + return dat + } + } + logging.Debugf("Can't replace attributes %s from %s.", name, dat.DN) + return dat +} diff --git a/test/functionTest.go b/test/functionTest.go new file mode 100644 index 0000000..e8d95ab --- /dev/null +++ b/test/functionTest.go @@ -0,0 +1,173 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/go-ldap/ldap/v3" +) + +const default_users, default_groups = 1000, 1000 + +func Init() (*instance, error) { + inst, err := NewInstance(default_users, default_groups) + return inst, err +} + +//Part to compare our datas +func (inst *instance) CompareOurDataWithConsul() (bool, error) { + if ok, err := inst.VerifyOurData(inst.dataUsers); !ok { + return false, err + } + if ok, err := inst.VerifyOurData(inst.dataGroups); !ok { + return false, err + } + return true, nil +} + +func (inst *instance) VerifyOurData(tabData []data_DN) (bool, error) { + for _, value := range tabData { + names := getNamesAtt(value) + cn := strings.Split(value.DN, ",")[0] + res, err := inst.Search_Request(value.DN, fmt.Sprintf("(&(%s))", cn), names) + if err != nil { + return false, err + } + if len(res.Entries) != 1 { + return false, fmt.Errorf("expected 1 entry, but found %d entry/ies", len(res.Entries)) + } + if !Compare(value, res.Entries[0]) { + return false, fmt.Errorf("no match with the DN: %s", value.DN) + } + } + return true, nil +} + +func Compare(dat data_DN, ent *ldap.Entry) bool { + for _, value := range dat.Attributes { + logging.Debugf("Attributes from %s is now: %s.", dat.DN, dat.Attributes) + entVal := GetAttributeValuesBottin(ent, value.Name) + logging.Debugf("Values of the Entry: attributName: %s, Values: %s.", value.Name, entVal) + if !CompareSliceString(entVal, value.Data) { + logging.Debugf("Values expected: %s, values found: %s.", value.Data, entVal) + return false + } + } + return true +} + +//Part modify datas +func (inst *instance) ModifyRandomAllData() error { + dg, err := inst.ModifyRandom(inst.dataGroups, []string{"description"}) + if err != nil { + return err + } else { + inst.dataGroups = dg + } + + dg, err = inst.ModifyRandom(inst.dataUsers, []string{"displayname"}) + if err != nil { + return err + } else { + inst.dataUsers = dg + } + return nil +} + +//Function which modify random way the attributes in attName of a data_DN's slice, it can delete, replace and delete +//The function modify also in the dat object +func (inst *instance) ModifyRandom(dat []data_DN, attName []string) ([]data_DN, error) { + for index, value := range dat { + del := make(map[string][]string) + add := make(map[string][]string) + replace := make(map[string][]string) + + for _, att := range attName { + + switch selNumber := R.Intn(3); selNumber { + case 0: + del[att] = []string{} + value = DelAtt(att, value) + logging.Debug(fmt.Sprintf("Delete the attribute %s of the DN %s.", att, value.DN)) + case 1: + name := inst.GenerateName() + value = AddAtt(name, []string{name}, value) + add[name] = []string{name} + logging.Debug(fmt.Sprintf("Add the attribute %s with value %s of the DN %s.", name, name, value.DN)) + case 2: + name := inst.GenerateName() + value = ReplaceAtt(att, []string{name}, value) + replace[att] = []string{name} + logging.Debug(fmt.Sprintf("Replace the attribute %s with value %s of the DN %s.", att, name, value.DN)) + } + + } + + err := inst.Modify_Request(value.DN, add, del, replace) + if err != nil { + return dat, err + } + dat[index] = value + } + return dat, nil +} + +//Add all users in a random group +func (inst *instance) AddAllUsersInGroup() error { + for _, value := range inst.dataGroups { + valueRand := (len(inst.dataUsers) + 1) / 30 + if valueRand == 0 { + valueRand = 1 + } + numberOfMembers := R.Intn(valueRand) + 1 + logging.Debugf("%s will be have %d members.", value.DN, numberOfMembers) + + groupMemory := make(map[int]struct{}) + users_cn := []string{} + + for i := 0; i < numberOfMembers; i++ { + selectGroup := R.Intn(len(inst.dataUsers)) + for _, ok := groupMemory[selectGroup]; ok; _, ok = groupMemory[selectGroup] { + selectGroup = R.Intn(len(inst.dataUsers)) + + logging.Debugf("Search an other member. The value is %d , and we have %d members available.", selectGroup, len(inst.dataUsers)) + } + groupMemory[selectGroup] = struct{}{} + + users_cn = append(users_cn, inst.dataGroups[selectGroup].DN) + + } + err := inst.AddUserSliceInGroup(users_cn, value.DN) + if err != nil { + return err + } + + } + return nil +} + +//Check if the groups in memberOf exist in Consul +func (inst *instance) CheckMemberOf() (bool, error) { + for _, value := range inst.dataUsers { + cn := strings.Split(value.DN, ",")[0] + res, err := inst.Search_Request(value.DN, fmt.Sprintf("(&(%s))", cn), []string{"memberOf"}) + if err != nil { + return false, err + } + if len(res.Entries) != 1 { + return false, fmt.Errorf("expected 1 entry, but found %d entry/ies", len(res.Entries)) + } + attValues := GetAttributeValuesBottin(res.Entries[0], "memberOf") + for _, dnGroup := range attValues { + logging.Debugf("Verify if the group %s exist...", dnGroup) + ok, err := inst.VerifyGroup(data_DN{DN: dnGroup}) + if err != nil { + return false, err + } + if !ok { + return false, fmt.Errorf("don't found the group: %s", dnGroup) + } + } + } + return true, nil +} diff --git a/test/go.mod b/test/go.mod new file mode 100644 index 0000000..74ed1ce --- /dev/null +++ b/test/go.mod @@ -0,0 +1,8 @@ +module bottin/integration + +go 1.14 + +require ( + github.com/go-ldap/ldap/v3 v3.3.0 + github.com/sirupsen/logrus v1.4.2 +) diff --git a/test/go.sum b/test/go.sum new file mode 100644 index 0000000..9c882d3 --- /dev/null +++ b/test/go.sum @@ -0,0 +1,26 @@ +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= +github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ= +github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/test/handler.go b/test/handler.go new file mode 100644 index 0000000..0e7a95b --- /dev/null +++ b/test/handler.go @@ -0,0 +1,138 @@ +package main + +import ( + "fmt" + "math/rand" + "os" + + ldap "github.com/go-ldap/ldap/v3" + "github.com/sirupsen/logrus" +) + +const maxlength_generateName, minlength_generateName = 25, 3 + +const bindusername = "cn=admin,dc=deuxfleurs,dc=fr" +const adresse = "127.0.0.1" +const port = 1389 + +var logging = logrus.New() + +const seed = 654258 + +var R = rand.New(rand.NewSource(seed)) + +var bindpassword = "sf7yO52NCuE" + +//Handler just to facilite the print error +func PrintError(LDAPError error) { + if LDAPError != nil { + logging.Fatal(LDAPError) + } +} + +//Generate an unique name, which store in all_names +func (inst *instance) GenerateName() (name string) { + alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + length := R.Intn(maxlength_generateName) + minlength_generateName + + //Check if this name not exist already + //Lock thhis variable because she is hared with other goroutine + allNames.mu.Lock() + for only_one := true; only_one; _, only_one = allNames.cn[name] { + //Create the name + for i := 0; i < length; i++ { + name += string(alphabet[R.Intn(len(alphabet))]) + } + + } + //Add the new name in the map to store this one + allNames.cn[name] = struct{}{} + allNames.mu.Unlock() + logging.Debug(fmt.Sprintf("Name generated: %s.", name)) + return +} + +//Handler to around the bug with MessageId +func (inst *instance) Reconnect() (err error) { + inst.logging.Close() + inst.logging, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", adresse, port)) + if err != nil { + return + } + err = inst.logging.Bind(bindusername, bindpassword) + //logging.Debug("Reconnect succesful") + return +} + +//Transform attributes in map format to the struct attributes +func MapAttToStruct(att map[string][]string) []attributes { + resultat := []attributes{} + for key, value := range att { + logging.Debug(fmt.Sprintf("Transform: key: %s, values: %s to attributes struct.\n", key, value)) + resultat = append(resultat, attributes{ + Name: key, + Data: value, + }) + } + return resultat +} + +func Connect() (*ldap.Conn, error) { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", adresse, port)) + if err != nil { + return nil, err + } + + if key, ok := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW"); ok { + bindpassword = key + } + + err = l.Bind(bindusername, bindpassword) + logging.Debug("Connection succesful") + return l, err +} + +//Handler to get only attributes names +func getNamesAtt(dat data_DN) []string { + resultat := []string{} + for _, values := range dat.Attributes { + resultat = append(resultat, values.Name) + } + return resultat +} + +//Handler to compare slice string +func CompareSliceString(string1, string2 []string) bool { + if len(string1) != len(string2) { + return false + } else { + for index := range string1 { + if string1[index] != string2[index] { + return false + } + } + } + return true +} + +//Handler to remove an element in slice string +func DeleteElementSliceString(s string, sSlice []string) []string { + + for index, value := range sSlice { + if value == s { + sSlice[index] = sSlice[len(sSlice)-1] + return sSlice[:len(sSlice)-1] + } + } + return sSlice +} + +//Get attributes entry values bottin bug +func GetAttributeValuesBottin(ent *ldap.Entry, name string) (res []string) { + for _, val := range ent.Attributes { + if val.Name == name { + res = append(res, val.Values...) + } + } + return +} diff --git a/test/request.go b/test/request.go new file mode 100644 index 0000000..b693211 --- /dev/null +++ b/test/request.go @@ -0,0 +1,67 @@ +package main + +import ( + ldap "github.com/go-ldap/ldap/v3" +) + +func (inst *instance) Add_Request(dn string, attributes map[string][]string) error { + //Create the AddRequest + req := ldap.NewAddRequest(dn, nil) + for key, value := range attributes { + req.Attribute(key, value) + } + + //Send the request + err := inst.logging.Add(req) + //@FIXME: Remove when you try to correct the bug MessageID + inst.Reconnect() + return err + +} + +//Use enum to select Replace,Delete,Modify +func (inst *instance) Modify_Request(dn string, add_attributes, delete_attributes, replace_attributes map[string][]string) error { + modifyReq := ldap.NewModifyRequest(dn, nil) + + for key, value := range add_attributes { + modifyReq.Add(key, value) + } + + for key, value := range delete_attributes { + modifyReq.Delete(key, value) + } + + for key, value := range replace_attributes { + modifyReq.Replace(key, value) + } + + err := inst.logging.Modify(modifyReq) + //@FIXME: Remove when you try to correct the bug MessageID + inst.Reconnect() + return err +} + +func (inst *instance) Delete_Request(dn string) error { + del := ldap.NewDelRequest(dn, nil) + + err := inst.logging.Del(del) + //@FIXME: Remove when you try to correct the bug MessageID + inst.Reconnect() + return err +} + +func (inst *instance) Search_Request(dn, filter string, name_attributes []string) (*ldap.SearchResult, error) { + searchReq := ldap.NewSearchRequest( + dn, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + filter, + name_attributes, + nil, + ) + + res, err := inst.logging.Search(searchReq) + logging.Debugf("Search Request made with: dn: %s, filter: %s, attributes: %s. \n", dn, filter, name_attributes) + //@FIXME: Remove when you try to correct the bug MessageID + inst.Reconnect() + return res, err +} diff --git a/test/runner.sh b/test/runner.sh new file mode 100755 index 0000000..0b97068 --- /dev/null +++ b/test/runner.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -ex + +echo $BOTTIN_DEFAULT_ADMIN_PW +consul agent -dev > /dev/null 2>&1 & +sleep 2 +cp test/config.json.test config.json +./bottin > /dev/null 2>&1 & +sleep 1 +./test/test -test.v -test.failfast -test.short -test.run TestPrincipal +./test/test -test.v -test.failfast -test.run TestPrincipal/B=