Compare commits

..

23 commits

Author SHA1 Message Date
MrArmonius 5f0f6a730a Add goldap source files + modified import 2021-07-13 15:23:50 +02:00
MrArmonius 7c8bd455e6 Delete goldap, switch to another branch 2021-07-13 15:09:16 +02:00
MrArmonius 2f09e10933 suppress kill in the script(provoked error 143) 2021-07-09 16:46:00 +02:00
MrArmonius 49fcbcae7c Exit 0 in the script 2021-07-09 16:43:43 +02:00
MrArmonius 3f8e633045 Change return code 143 to 0 2021-07-09 16:36:23 +02:00
MrArmonius e4778a9a89 Add launch script and remove openssl 2021-07-09 16:34:27 +02:00
MrArmonius eb7f00c526 Add config.json.test to use in Go test 2021-07-09 16:32:02 +02:00
MrArmonius a9b04a255f Test bottin comportment 2021-07-09 16:27:15 +02:00
MrArmonius 3e658ac201 Add sign Drone and suppress opennssl package in script 2021-07-09 16:24:03 +02:00
MrArmonius a9d9484f23 Add sign and transform bash to ash script 2021-07-09 16:04:19 +02:00
MrArmonius 974d8806d8 Add sign and bash command 2021-07-09 15:55:59 +02:00
MrArmonius 986ef6d59d Add sign to .drone 2021-07-09 15:53:12 +02:00
MrArmonius 09512a271d Test .drone ls step bootin 2021-07-09 15:47:54 +02:00
MrArmonius 139a40bdf3 Ajout d'une step pour lancer le test integration 2021-07-09 15:37:07 +02:00
MrArmonius bfd12a48c4 Add test_automatic/integration binarie 2021-07-09 15:29:09 +02:00
MrArmonius aeac8faf15 Ajout Nouvelle Version integration.go 2021-07-09 15:27:13 +02:00
MrArmonius 2f350965ce Modification server init()
Lecture Password
Ajout d'une lecture de password à travers la variable d'environnement
"BOTTIN_DEFAULT_ADMIN_PW" sinon génère aléatoirement
2021-07-09 15:24:33 +02:00
MrArmonius 5fbe39c38a Création script bash de test 2021-07-09 15:23:21 +02:00
MrArmonius f93f9e6958 Rajout script bash pour drone 2021-07-08 16:56:09 +02:00
MrArmonius f294b66874 test_automatique fini et fonctionnel 2021-07-08 16:22:30 +02:00
MrArmonius 32cce4c4d3 Ajout des fichiers WireSharks scan pour problème messageID 2021-07-08 10:45:05 +02:00
MrArmonius 9e4b4a0162 Mise en place du code source goldap
Changement des imports dans les fichiers go de bottin et ldapserver.
Rajout d'un fichier txt dans test-automatique pour reproduire le bug des
requêtes ldap.
2021-07-07 18:26:02 +02:00
MrArmonius 75f4a916c3 Tests écrit en go sur la réaction du serveur Bottin
Tests réalisés:
- création aléatoires des Users et Groups- LDAP ADD
- vérifications des attributs sur Consul et ceux qu'on a ajouté - LDAP
  Search
- modifications des attributs des users et groups puis vérifications -
  LDAP Modify
2021-07-07 01:49:33 +02:00
33 changed files with 560 additions and 1464 deletions

26
.drone.yml Normal file
View file

@ -0,0 +1,26 @@
---
kind: pipeline
name: bottin
steps:
- name: build
image: golang:stretch
commands:
- go get -d -v
- go build -v
- cd test_automatic
- go get -d -v
- go build -v
- name: test_bottin
image: consul:latest
environment:
BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1
commands:
- ash test_automatic/start_test.sh
---
kind: signature
hmac: a4455c124ee87ca8b0ef1779560703573f3a3f24d406e4cb281b9e0dab4ceeda
...

4
.gitignore vendored
View file

@ -1,6 +1,4 @@
bottin
bottin.static
config.json
test/test
result
ldap.json
test_automatic/integration

View file

@ -1,15 +0,0 @@
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:1.15.4
environment:
BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1
commands:
- ash test/runner.sh

5
Dockerfile Normal file
View file

@ -0,0 +1,5 @@
FROM scratch
ADD bottin.static /bottin
ENTRYPOINT ["/bottin"]

View file

@ -1,6 +1,6 @@
# Bottin
[![status-badge](https://woodpecker.deuxfleurs.fr/api/badges/38/status.svg)](https://woodpecker.deuxfleurs.fr/repos/38)
[![Build Status](https://drone.deuxfleurs.fr/api/badges/Deuxfleurs/bottin/status.svg?ref=refs/heads/main)](https://drone.deuxfleurs.fr/Deuxfleurs/bottin)
<img src="https://git.deuxfleurs.fr/Deuxfleurs/bottin/raw/branch/main/bottin.png" style="height: 150px; display: block; margin-left: auto; margin-right: auto" />
@ -24,7 +24,7 @@ Features:
- Access control through an ACL (hardcoded in the configuration file)
A Docker image is provided on the [Docker hub](https://hub.docker.com/r/dxflrs/bottin) (built in `default.nix`).
A Docker image is provided on the [Docker hub](https://hub.docker.com/r/lxpz/bottin_amd64).
An example for running Bottin on a Nomad cluster can be found in `bottin.hcl.example`.
Bottin takes a single command line argument, `-config <filename>`, which is the
@ -44,17 +44,6 @@ Bottin requires go 1.13 or later.
To build Bottin, clone this repository outside of your `$GOPATH`.
Then, run `make` in the root of the repo.
## Releasing
```bash
nix-build -A bin
nix-build -A docker
```
```bash
docker load < $(nix-build -A docker)
docker push dxflrs/bottin:???
```
## Server initialization

View file

@ -1,55 +0,0 @@
let
pkgsSrc = fetchTarball {
# As of 2022-07-19
url = "https://github.com/NixOS/nixpkgs/archive/d2db10786f27619d5519b12b03fb10dc8ca95e59.tar.gz";
sha256 = "0s9gigs3ylnq5b94rfcmxvrmmr3kzhs497gksajf638d5bv7zcl5";
};
gomod2nix = fetchGit {
url = "https://github.com/tweag/gomod2nix.git";
ref = "master";
rev = "40d32f82fc60d66402eb0972e6e368aeab3faf58";
};
pkgs = import pkgsSrc {
overlays = [
(self: super: {
gomod = super.callPackage "${gomod2nix}/builder/" { };
})
];
};
in rec {
bin = pkgs.gomod.buildGoApplication {
pname = "bottin-bin";
version = "0.1.0";
src = builtins.filterSource
(path: type: (builtins.match ".*/test/.*\\.(go|sum|mod)" path) == null)
./.;
modules = ./gomod2nix.toml;
CGO_ENABLED=0;
meta = with pkgs.lib; {
description = "A cloud-native LDAP server backed by a Consul datastore";
homepage = "https://git.deuxfleurs.fr/Deuxfleurs/bottin";
license = licenses.gpl3Plus;
platforms = platforms.linux;
};
};
pkg = pkgs.stdenv.mkDerivation {
pname = "bottin";
version = "0.1.0";
unpackPhase = "true";
installPhase = ''
mkdir -p $out/
cp ${bin}/bin/bottin $out/bottin
'';
};
docker = pkgs.dockerTools.buildImage {
name = "dxflrs/bottin";
config = {
Entrypoint = [ "${pkg}/bottin" ];
WorkingDir = "/";
};
};
}

View file

@ -1,79 +0,0 @@
{
"nodes": {
"gomod2nix": {
"inputs": {
"nixpkgs": "nixpkgs",
"utils": "utils"
},
"locked": {
"lastModified": 1655245309,
"narHash": "sha256-d/YPoQ/vFn1+GTmSdvbSBSTOai61FONxB4+Lt6w/IVI=",
"owner": "tweag",
"repo": "gomod2nix",
"rev": "40d32f82fc60d66402eb0972e6e368aeab3faf58",
"type": "github"
},
"original": {
"owner": "tweag",
"repo": "gomod2nix",
"rev": "40d32f82fc60d66402eb0972e6e368aeab3faf58",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1653581809,
"narHash": "sha256-Uvka0V5MTGbeOfWte25+tfRL3moECDh1VwokWSZUdoY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "83658b28fe638a170a19b8933aa008b30640fbd1",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1669764884,
"narHash": "sha256-1qWR/5+WtqxSedrFbUbM3zPMO7Ec2CGWaxtK4z4DdvY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "0244e143dc943bcf661fdaf581f01eb0f5000fcf",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"rev": "0244e143dc943bcf661fdaf581f01eb0f5000fcf",
"type": "github"
}
},
"root": {
"inputs": {
"gomod2nix": "gomod2nix",
"nixpkgs": "nixpkgs_2"
}
},
"utils": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View file

@ -1,39 +0,0 @@
{
description = "A cloud-native LDAP server backed by a Consul datastore";
inputs.nixpkgs.url = "github:nixos/nixpkgs/0244e143dc943bcf661fdaf581f01eb0f5000fcf";
inputs.gomod2nix.url = "github:tweag/gomod2nix/40d32f82fc60d66402eb0972e6e368aeab3faf58";
outputs = { self, nixpkgs, gomod2nix }:
let
pkgs = import nixpkgs {
system = "x86_64-linux";
overlays = [
(self: super: {
gomod = super.callPackage "${gomod2nix}/builder/" { };
})
];
};
bottin = pkgs.gomod.buildGoApplication {
pname = "bottin";
version = "0.1.0";
src = builtins.filterSource
(path: type: (builtins.match ".*/test/.*\\.(go|sum|mod)" path) == null)
./.;
modules = ./gomod2nix.toml;
CGO_ENABLED=0;
meta = with pkgs.lib; {
description = "A cloud-native LDAP server backed by a Consul datastore";
homepage = "https://git.deuxfleurs.fr/Deuxfleurs/bottin";
license = licenses.gpl3Plus;
platforms = platforms.linux;
};
};
in
{
packages.x86_64-linux.bottin = bottin;
packages.x86_64-linux.default = self.packages.x86_64-linux.bottin;
};
}

4
go.mod
View file

@ -3,9 +3,9 @@ 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/jsimonetti/pwscheme v0.0.0-20220125093853-4d9895f5db73
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
github.com/sirupsen/logrus v1.4.2
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect
)

14
go.sum
View file

@ -1,3 +1,5 @@
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/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=
@ -42,10 +49,10 @@ github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG67
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/jsimonetti/pwscheme v0.0.0-20220125093853-4d9895f5db73 h1:ZhC4QngptYaGx53+ph1RjxcH8fkCozBaY+935TNX4i8=
github.com/jsimonetti/pwscheme v0.0.0-20220125093853-4d9895f5db73/go.mod h1:t0Q9JvoMTfTYdAWIk2MF69iz+Qpdk9D+PgVu6fVmaDI=
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/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 h1:wIONC+HMNRqmWBjuMxhatuSzHaljStc4gjDeKycxy0A=
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
@ -77,13 +84,14 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
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 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
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=

View file

@ -223,33 +223,19 @@ func parseInt64(bytes []byte) (ret int64, err error) {
return
}
func sizeInt64(i int64) int {
n := 1
for i > 127 {
n++
i >>= 8
func sizeInt64(i int64) (size int) {
for ; i != 0 || size == 0; i >>= 8 {
size++
}
for i < -128 {
n++
i >>= 8
}
return n
return
}
func writeInt64(bytes *Bytes, i int64) int {
n := sizeInt64(i)
buf := [8]byte{}
for j := 0; j < n; j++ {
b := i >> uint((n-1-j)*8)
buf[j] = byte(b)
func writeInt64(bytes *Bytes, i int64) (size int) {
for ; i != 0 || size == 0; i >>= 8 { // Write at least one byte even if the value is 0
bytes.writeBytes([]byte{byte(i)})
size++
}
bytes.writeBytes(buf[:n])
return n
return
}
// parseInt treats the given bytes as a big-endian, signed integer and returns
@ -727,13 +713,7 @@ func writeTagAndLength(bytes *Bytes, t TagAndLength) (size int) {
panic("Can't have a negative length")
} else if t.Length >= 128 {
lengthBytes := 0
val := t.Length
for val > 0 {
lengthBytes++
bytes.writeBytes([]byte{byte(val & 0xff)})
val >>= 8
}
lengthBytes := writeInt64(bytes, int64(t.Length))
bytes.writeBytes([]byte{byte(0x80 | byte(lengthBytes))})
size += lengthBytes + 1

View file

@ -1,54 +0,0 @@
package message
import (
"testing"
"bytes"
)
func TestSizeInt64(t *testing.T) {
s := sizeInt64(0)
if s != 1 {
t.Errorf("computed size is %d, expected 1", s)
}
s = sizeInt64(127)
if s != 1 {
t.Errorf("computed size is %d, expected 1", s)
}
s = sizeInt64(128)
if s != 2 {
t.Errorf("computed size is %d, expected 2", s)
}
s = sizeInt64(50000)
if s != 3 {
t.Errorf("computed size is %d, expected 3", s)
}
s = sizeInt64(-12345)
if s != 2 {
t.Errorf("computed size is %d, expected 2", s)
}
}
func TestWriteInt64(t *testing.T) {
vtests := []int64{0, 127, 128, 50000, -12345}
expsize := []int{1, 1, 2, 3, 2}
expresult := [][]byte{{0x00}, {0x7F}, {0x00, 0x80}, {0x00, 0xc3, 0x50}, {0xcf, 0xc7}}
for idx, v := range vtests {
fs := sizeInt64(v)
b := NewBytes(fs, make([]byte, fs))
t.Log("computing", v)
s := writeInt64(b, v)
if s != expsize[idx] {
t.Errorf("computed size is %d, expected %d", s, expsize[idx])
}
if !bytes.Equal(b.Bytes(), expresult[idx]) {
t.Errorf("wrong computed bytes, got %v, expected %v", b.Bytes(), expresult[idx])
}
a, e := parseInt64(b.Bytes())
t.Log("parse", a, e)
}
}

View file

@ -1,76 +0,0 @@
package message
import (
"testing"
"fmt"
)
func toHex(b []byte) (r string) {
r = "[ "
for _, e := range b {
r += fmt.Sprintf("0x%x ", e)
}
return r + "]"
}
func TestMessageID(t *testing.T) {
m := NewLDAPMessageWithProtocolOp(UnbindRequest{})
m.SetMessageID(128)
buf, err := m.Write()
if err != nil {
t.Errorf("marshalling failed with %v", err)
}
t.Logf("%v", toHex(buf.Bytes()))
ret, err := ReadLDAPMessage(NewBytes(0, buf.Bytes()))
if err != nil {
t.Errorf("unmarshalling failed with %v", err)
}
if _, ok := ret.ProtocolOp().(UnbindRequest); !ok {
t.Errorf("should be an unbind request")
}
if ret.MessageID() != 128 {
t.Errorf("Expect message id 128, got %d", ret.MessageID())
}
t.Log("Done, marshal/unmarshall worked")
}
func TestSearchEntry(t *testing.T) {
m := NewLDAPMessageWithProtocolOp(SearchResultEntry{
objectName:"cn=êige€nbgtz,ou=users,dc=deuxfleurs,dc=fr",
attributes: PartialAttributeList{
PartialAttribute{
type_:"displayname",
vals:[]AttributeValue{"êiGe€NBgTZ"},
},
PartialAttribute{
type_:"objectclass",
vals:[]AttributeValue{"inetOrgPerson"},
},
PartialAttribute{
type_:"objectclass",
vals:[]AttributeValue{"organizationalPerson"},
},
PartialAttribute{
type_:"objectclass",
vals:[]AttributeValue{"person"},
},
PartialAttribute{
type_:"objectclass",
vals:[]AttributeValue{"top"},
},
PartialAttribute{
type_:"structuralobjectclass",
vals:[]AttributeValue{"inetOrgPerson"},
},
},
})
m.SetMessageID(24)
buf, err := m.Write()
if err != nil {
t.Errorf("marshalling failed with %v", err)
}
if buf.Bytes()[0] != 0x30 {
t.Logf("Malformed message: %v", toHex(buf.Bytes()))
}
}

View file

@ -1,153 +0,0 @@
schema = 3
[mod]
[mod."github.com/armon/circbuf"]
version = "v0.0.0-20150827004946-bbbad097214e"
hash = "sha256-klQjllsJZqZ2KPNx1mZT9XP+UAJkuBhmTnZdNlAflEM="
[mod."github.com/armon/go-metrics"]
version = "v0.0.0-20180917152333-f0300d1749da"
hash = "sha256-+zqX1hlJgc+IrXRzBQDMhR8GYQdc0Oj6PiIDfctgh44="
[mod."github.com/armon/go-radix"]
version = "v0.0.0-20180808171621-7fddfc383310"
hash = "sha256-ZHU4pyBqHHRuQJuYr2K+LqeAnLX9peX07cmSYK+GDHk="
[mod."github.com/bgentry/speakeasy"]
version = "v0.1.0"
hash = "sha256-Gt1vj6CFovLnO6wX5u2O4UfecY9V2J9WGw1ez4HMrgk="
[mod."github.com/davecgh/go-spew"]
version = "v1.1.1"
hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI="
[mod."github.com/fatih/color"]
version = "v1.7.0"
hash = "sha256-4In7ef7it7d+6oGUJ3pkD0V+lsL40hVtYdy2KD2ovn0="
[mod."github.com/google/btree"]
version = "v0.0.0-20180813153112-4030bb1f1f0c"
hash = "sha256-5gr0RMnlvrzCke3kwpkf92WvW3x5nnKZesoulyoYRC0="
[mod."github.com/google/uuid"]
version = "v1.1.1"
hash = "sha256-66PXC/RCPUyhS9PhkIPQFR3tbM2zZYDNPGXN7JJj3UE="
[mod."github.com/hashicorp/consul/api"]
version = "v1.3.0"
hash = "sha256-fMORNFAWK2GkRbBl/KzNHrvtCfwz/7P66HzDaE4GnuE="
[mod."github.com/hashicorp/consul/sdk"]
version = "v0.3.0"
hash = "sha256-lF47JPGfmeGjpuQw9VSNs2Mi+G7FQLKrrHteX3iXfpU="
[mod."github.com/hashicorp/errwrap"]
version = "v1.0.0"
hash = "sha256-LGSLrefkABG1kH1i+GUWiD2/ggJxiZEJ+D2YNbhZjmo="
[mod."github.com/hashicorp/go-cleanhttp"]
version = "v0.5.1"
hash = "sha256-c54zcHr9THj3MQk7hrDQcpjOcQi1MvXZ4Kpin6EbfR4="
[mod."github.com/hashicorp/go-immutable-radix"]
version = "v1.0.0"
hash = "sha256-JmNxdGaJG63Ty/sVnPjqvTyA4/k5wkzZ/QvpMK2uduw="
[mod."github.com/hashicorp/go-msgpack"]
version = "v0.5.3"
hash = "sha256-2OUYjD/Jt12TFBrtH0wRqg+lzRljDxSIhk2CqBLUczo="
[mod."github.com/hashicorp/go-multierror"]
version = "v1.0.0"
hash = "sha256-iXzjerl96o7QDiSwQjbak8R/t+YzZeoUqm59TCmy3gI="
[mod."github.com/hashicorp/go-rootcerts"]
version = "v1.0.0"
hash = "sha256-4NZJAT5/vocyto+dv6FmW4kFiYldmNvewowsYK/LiTI="
[mod."github.com/hashicorp/go-sockaddr"]
version = "v1.0.0"
hash = "sha256-orG+SHVsp5lgNRCErmhMLABVFQ3ZWfYIJ/4LTFzlvao="
[mod."github.com/hashicorp/go-syslog"]
version = "v1.0.0"
hash = "sha256-YRuq6oPMwAFVY7mvwpMDvZqGwNnb5CjBYyKI/x5mbCc="
[mod."github.com/hashicorp/go-uuid"]
version = "v1.0.1"
hash = "sha256-s1wIvBu37z4U3qK9sdUR1CtbD39N6RwfX4HgDCpCa0s="
[mod."github.com/hashicorp/go.net"]
version = "v0.0.1"
hash = "sha256-JKal3E+wPO+Hk838opKV4HHKB4O72Xy+77ncXlLkWRk="
[mod."github.com/hashicorp/golang-lru"]
version = "v0.5.0"
hash = "sha256-Lo0UyOZQ89iK0Ui3Hfk5VkWqxEzLw6Lclq4EM8VlYoo="
[mod."github.com/hashicorp/logutils"]
version = "v1.0.0"
hash = "sha256-e8t8Dm8sp/PzKClN1TOmFcrTAWNh4mZHSW7cAjVx3Bw="
[mod."github.com/hashicorp/mdns"]
version = "v1.0.0"
hash = "sha256-ravx4tklQG43OEjPiJn68iJM9ODZ6hgU0idFCEOiJGM="
[mod."github.com/hashicorp/memberlist"]
version = "v0.1.3"
hash = "sha256-IsxqevYulPt+2VGtlq068Jyq1YfIk4Ohh9TgakIGxnc="
[mod."github.com/hashicorp/serf"]
version = "v0.8.2"
hash = "sha256-diRxWOouFLTO75f2E9NlrKgie/qsT+gOOrrbf4tACHw="
[mod."github.com/jsimonetti/pwscheme"]
version = "v0.0.0-20220125093853-4d9895f5db73"
hash = "sha256-YF3RKU/4CWvLPgGzUd7Hk/2+41OUFuRWZgzQuqcsKg0="
[mod."github.com/konsorten/go-windows-terminal-sequences"]
version = "v1.0.1"
hash = "sha256-Nwp+Cza9dIu3ogVGip6wyOjWwwaq+2hU3eYIe4R7kNE="
[mod."github.com/mattn/go-colorable"]
version = "v0.0.9"
hash = "sha256-fVPF8VxbdggLAZnaexMl6Id1WjXKImzVySxKfa+ukts="
[mod."github.com/mattn/go-isatty"]
version = "v0.0.3"
hash = "sha256-9ogEEd8/kl/dImldzdBmTFdepvB0dVXEzN4o8bEqhBs="
[mod."github.com/miekg/dns"]
version = "v1.0.14"
hash = "sha256-OeijUgBaEmDapclTxfvjIqrjh4qZu3+DQpHelGGI4aA="
[mod."github.com/mitchellh/cli"]
version = "v1.0.0"
hash = "sha256-4nG7AhRcjTRCwUCdnaNaFrAKDxEEoiihaCA4lk+uM8U="
[mod."github.com/mitchellh/go-homedir"]
version = "v1.0.0"
hash = "sha256-eGmBNNTuDSMwicjTNgB54IEJuK1tgOLDJ3NHTpQCHzg="
[mod."github.com/mitchellh/go-testing-interface"]
version = "v1.0.0"
hash = "sha256-/Dpv/4i5xuK8hDH+q8YTdF6Jg6NNtfO4Wqig2JCWgrY="
[mod."github.com/mitchellh/gox"]
version = "v0.4.0"
hash = "sha256-GV3LYxzJt8YVbnSac2orlj2QR3MX/YIDrLkSkPhsjuA="
[mod."github.com/mitchellh/iochan"]
version = "v1.0.0"
hash = "sha256-b5Tp7cw/e8mL++IjsebbmKWXtb9Hrzu4Fc6M4tZKFhU="
[mod."github.com/mitchellh/mapstructure"]
version = "v1.1.2"
hash = "sha256-OU9HZYHtl0qaqMFd84w7snkkRuRY6UMSsfCnL5HYdw0="
[mod."github.com/pascaldekloe/goe"]
version = "v0.0.0-20180627143212-57f6aae5913c"
hash = "sha256-2KUjqrEC/BwkTZRxImazcI/C3H7QmXfNrlt8slwdDbc="
[mod."github.com/pkg/errors"]
version = "v0.8.1"
hash = "sha256-oe3iddfoLRwpC3ki5fifHf2ZFprtg99iNak50shiuDw="
[mod."github.com/pmezard/go-difflib"]
version = "v1.0.0"
hash = "sha256-/FtmHnaGjdvEIKAJtrUfEhV7EVo5A/eYrtdnUkuxLDA="
[mod."github.com/posener/complete"]
version = "v1.1.1"
hash = "sha256-heyPMSBzVlx7ZKgTyzl/xmUfZw3EZCcvGFGrRMIbIr8="
[mod."github.com/ryanuber/columnize"]
version = "v0.0.0-20160712163229-9b3edd62028f"
hash = "sha256-RLUQcU6Z03upKe08v6rjn9/tkyrQsgmpdEmBtWaLQfk="
[mod."github.com/sean-/seed"]
version = "v0.0.0-20170313163322-e2103e2c3529"
hash = "sha256-RQQTjvf8Y91jP5FGOyEnGMFw7zCrcSnUU4eH2CXKkT4="
[mod."github.com/sirupsen/logrus"]
version = "v1.4.2"
hash = "sha256-3QzWUsapCmg3F7JqUuINT3/UG097uzLff6iCcCgQ43o="
[mod."github.com/stretchr/objx"]
version = "v0.1.1"
hash = "sha256-HdGVZCuy7VprC5W9UxGbDmXqsKADMjpEDht7ilGVLco="
[mod."github.com/stretchr/testify"]
version = "v1.3.0"
hash = "sha256-+mSebBNccNcxbY462iKTNTWmd5ZuUkUqFebccn3EtIA="
[mod."golang.org/x/crypto"]
version = "v0.0.0-20200604202706-70a84ac30bf9"
hash = "sha256-I5ov2lV6ubB3H3pn6DFN1VPLyxeLfPUaOx2PJ9K1KH8="
[mod."golang.org/x/net"]
version = "v0.0.0-20190404232315-eb5bcb51f2a3"
hash = "sha256-kwP+BDfPsZEeeceyg4H5LgdTDT8O8YWbkpCkpyuPJJo="
[mod."golang.org/x/sync"]
version = "v0.0.0-20181221193216-37e7f081c4d4"
hash = "sha256-FUpv9bmGLDEjCxXdvR0CRDqOKRY0xrHRPzOsyQyvYK0="
[mod."golang.org/x/sys"]
version = "v0.0.0-20190422165155-953cdadca894"
hash = "sha256-YkQjSZaiVFxrmXYSKmqrGeIO3RZ95fc3dWyCaR+SosY="
[mod."golang.org/x/text"]
version = "v0.3.0"
hash = "sha256-0FFbaxF1ZuAQF3sCcA85e8MO6prFeHint36inija4NY="

View file

@ -206,10 +206,7 @@ func (c *client) close() {
}
func (c *client) writeMessage(m *ldap.LDAPMessage) {
data, err := m.Write()
if err != nil {
Logger.Errorf("bottin: unable to marshal response message: %v", err)
}
data, _ := m.Write()
//Logger.Printf(">>> %d - %s - hex=%x", c.Numero, m.ProtocolOpName(), data.Bytes())
Logger.Tracef(">>> [%d] %#v", c.Numero, m)

41
main.go
View file

@ -12,10 +12,10 @@ import (
"os/signal"
"syscall"
message "bottin/goldap"
ldap "bottin/ldapserver"
consul "github.com/hashicorp/consul/api"
message "bottin/goldap"
log "github.com/sirupsen/logrus"
)
@ -320,6 +320,7 @@ func (server *Server) init() error {
return err
}
admin_pass_str, environnement_variable_exist := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW")
if !environnement_variable_exist {
admin_pass := make([]byte, 8)
@ -328,15 +329,11 @@ func (server *Server) init() error {
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")
} else {
server.logger.Printf("It seems that exists a password in environnement variable")
}
admin_pass_hash, err := SSHAEncode(admin_pass_str)
if err != nil {
server.logger.Error("can't create admin password")
panic(err)
}
admin_pass_hash := SSHAEncode([]byte(admin_pass_str))
admin_dn := "cn=admin," + server.config.Suffix
admin_attributes := Entry{
@ -437,8 +434,8 @@ func (server *Server) handleBindInternal(state *State, r *message.BindRequest) (
}
for _, hash := range passwd {
valid, err := SSHAMatches(hash, string(r.AuthenticationSimple()))
if valid && err == nil {
valid := SSHAMatches(hash, []byte(r.AuthenticationSimple()))
if valid {
groups, err := server.getAttribute(string(r.Name()), ATTR_MEMBEROF)
if err != nil {
return ldap.LDAPResultOperationsError, err
@ -447,32 +444,8 @@ func (server *Server) handleBindInternal(state *State, r *message.BindRequest) (
user: string(r.Name()),
groups: groups,
}
updatePasswordHash(string(r.AuthenticationSimple()), hash, server, string(r.Name()))
return ldap.LDAPResultSuccess, nil
} else {
return ldap.LDAPResultInvalidCredentials, fmt.Errorf("can't authenticate: %w", err)
}
}
return ldap.LDAPResultInvalidCredentials, fmt.Errorf("No password match")
}
// Update the hash if it's not already SSHA512
func updatePasswordHash(password string, currentHash string, server *Server, dn string) {
hashType, err := determineHashType(currentHash)
if err != nil {
server.logger.Errorf("can't determine hash type of password")
return
}
if hashType != SSHA512 {
reencodedPassword, err := SSHAEncode(password)
if err != nil {
server.logger.Errorf("can't encode password")
return
}
server.putAttributes(dn, Entry{
ATTR_USERPASSWORD: []string{reencodedPassword},
})
}
}

View file

@ -213,11 +213,10 @@ func (server *Server) handleSearchInternal(state *State, w ldap.ResponseWriter,
continue
}
// Send result
resultVals := []message.AttributeValue{}
for _, v := range val {
resultVals = append(resultVals, message.AttributeValue(v))
e.AddAttribute(message.AttributeDescription(attr),
message.AttributeValue(v))
}
e.AddAttribute(message.AttributeDescription(attr), resultVals...)
}
w.Write(e)
}

70
ssha.go
View file

@ -1,79 +1,25 @@
package main
import (
"errors"
"strings"
"bytes"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"fmt"
"strings"
log "github.com/sirupsen/logrus"
//"github.com/jsimonetti/pwscheme/ssha"
"github.com/jsimonetti/pwscheme/ssha256"
"github.com/jsimonetti/pwscheme/ssha512"
)
const (
SSHA = "{SSHA}"
SSHA256 = "{SSHA256}"
SSHA512 = "{SSHA512}"
)
// Encode encodes the string to ssha512
func SSHAEncode(rawPassPhrase string) (string, error) {
return ssha512.Generate(rawPassPhrase, 16)
}
// Matches matches the encoded password and the raw password
func SSHAMatches(encodedPassPhrase string, rawPassPhrase string) (bool, error) {
hashType, err := determineHashType(encodedPassPhrase)
if err != nil {
return false, errors.New("invalid password hash stored")
}
var is_ok bool
switch hashType {
case SSHA:
is_ok = LegacySSHAMatches(encodedPassPhrase, []byte(rawPassPhrase))
return is_ok, nil
//return ssha.Validate(rawPassPhrase, encodedPassPhrase)
case SSHA256:
return ssha256.Validate(rawPassPhrase, encodedPassPhrase)
case SSHA512:
return ssha512.Validate(rawPassPhrase, encodedPassPhrase)
}
return false, errors.New("no matching hash type found")
}
func determineHashType(hash string) (string, error) {
if len(hash) >= 7 && strings.ToUpper(string(hash[0:6])) == SSHA {
return SSHA, nil
}
if len(hash) >= 10 && strings.ToUpper(string(hash[0:9])) == SSHA256 {
return SSHA256, nil
}
if len(hash) >= 10 && strings.ToUpper(string(hash[0:9])) == SSHA512 {
return SSHA512, nil
}
return "", errors.New("no valid hash found")
}
// --- legacy
// Encode encodes the []byte of raw password
func LegacySSHAEncode(rawPassPhrase []byte) string {
hash := legacyMakeSSHAHash(rawPassPhrase, legacyMakeSalt())
func SSHAEncode(rawPassPhrase []byte) string {
hash := makeSSHAHash(rawPassPhrase, makeSalt())
b64 := base64.StdEncoding.EncodeToString(hash)
return fmt.Sprintf("{ssha}%s", b64)
}
// Matches matches the encoded password and the raw password
func LegacySSHAMatches(encodedPassPhrase string, rawPassPhrase []byte) bool {
func SSHAMatches(encodedPassPhrase string, rawPassPhrase []byte) bool {
if !strings.EqualFold(encodedPassPhrase[:6], "{ssha}") {
return false
}
@ -84,7 +30,7 @@ func LegacySSHAMatches(encodedPassPhrase string, rawPassPhrase []byte) bool {
}
salt := bhash[20:]
newssha := legacyMakeSSHAHash(rawPassPhrase, salt)
newssha := makeSSHAHash(rawPassPhrase, salt)
if bytes.Compare(newssha, bhash) != 0 {
return false
@ -93,7 +39,7 @@ func LegacySSHAMatches(encodedPassPhrase string, rawPassPhrase []byte) bool {
}
// makeSalt make a 32 byte array containing random bytes.
func legacyMakeSalt() []byte {
func makeSalt() []byte {
sbytes := make([]byte, 32)
_, err := rand.Read(sbytes)
if err != nil {
@ -103,7 +49,7 @@ func legacyMakeSalt() []byte {
}
// makeSSHAHash make hasing using SHA-1 with salt. This is not the final output though. You need to append {SSHA} string with base64 of this hash.
func legacyMakeSSHAHash(passphrase, salt []byte) []byte {
func makeSSHAHash(passphrase, salt []byte) []byte {
sha := sha1.New()
sha.Write(passphrase)
sha.Write(salt)

View file

@ -1,158 +0,0 @@
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=Add and delete", TestAddThenDelete)
t.Run("A=Modify", TestModifyRequest)
if !testing.Short() {
t.Run("B=Add attributes", TestConfirmAddAttributes)
t.Run("B=Modify and check", TestModifyRequestAndCheck)
t.Run("C=Add user in group", TestAddUserInGroup)
t.Run("C=Delete group", TestDeleteGroupsAfterAddedUsers)
}
}

View file

@ -1,281 +0,0 @@
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
}

View file

@ -1,173 +0,0 @@
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
}

View file

@ -1,128 +0,0 @@
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 := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
"Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "é", "è", "ê", "ë", "à", "@", "â", "ä", "û", "ü", "ù", "$", "£", "%", "ø", "€"}
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 += 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
}
//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
}
//l.Debug.Enable(true)
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
}

View file

@ -1,59 +0,0 @@
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)
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)
return err
}
func (inst *instance) Delete_Request(dn string) error {
del := ldap.NewDelRequest(dn, nil)
err := inst.logging.Del(del)
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)
return res, err
}

View file

@ -1,16 +0,0 @@
#!/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 > /tmp/bottin.log 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=
jobs
kill %2
kill %1

Binary file not shown.

Binary file not shown.

View file

@ -1,6 +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/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=
@ -8,12 +7,10 @@ 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=

View file

@ -0,0 +1,431 @@
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
}

View file

@ -0,0 +1,40 @@
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

12
test_automatic/start_test.sh Executable file
View file

@ -0,0 +1,12 @@
#!/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

View file

@ -7,9 +7,8 @@ import (
ldap "bottin/ldapserver"
message "bottin/goldap"
consul "github.com/hashicorp/consul/api"
message "bottin/goldap"
)
// Generic item modification function --------
@ -39,7 +38,7 @@ func (server *Server) putAttributes(dn string, attrs Entry) error {
// Retreieve previously existing attributes, which we will use to delete
// entries with the wrong case
previous_pairs, _, err := server.kv.List(prefix+"/attribute=", &server.readOpts)
previous_pairs, _, err := server.kv.List(prefix + "/attribute=", &server.readOpts)
if err != nil {
return err
}
@ -66,24 +65,6 @@ func (server *Server) putAttributes(dn string, attrs Entry) error {
}
}
// if the password is not yet hashed we hash it
if k == ATTR_USERPASSWORD {
tmpValues := []string{}
for _, pw := range values {
_, err := determineHashType(pw)
if err != nil {
encodedPassword, err := SSHAEncode(pw)
if err != nil {
return err
}
tmpValues = append(tmpValues, encodedPassword)
} else {
tmpValues = append(tmpValues, pw)
}
}
values = tmpValues
}
// If we have zero values, delete associated k/v pair
// Otherwise, write new values
if len(values) == 0 {