Compare commits

..

No commits in common. "main" and "change/password_hashing" have entirely different histories.

10 changed files with 25 additions and 410 deletions

View file

@ -1,3 +1,7 @@
---
kind: pipeline
name: bottin
steps: steps:
- name: build - name: build
image: golang:stretch image: golang:stretch
@ -8,8 +12,14 @@ steps:
- go test -i -c -o test - go test -i -c -o test
- name: test_bottin - name: test_bottin
image: consul:1.15.4 image: consul:latest
environment: environment:
BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1 BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1
commands: commands:
- ash test/runner.sh - ash test/runner.sh
---
kind: signature
hmac: ff246a04c3df8a2f39c8b446dea920622d61950e6caaac886931bdb05d0706ed
...

2
.gitignore vendored
View file

@ -2,5 +2,3 @@ bottin
bottin.static bottin.static
config.json config.json
test/test test/test
result
ldap.json

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 # 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" /> <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) - 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`. 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 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`. To build Bottin, clone this repository outside of your `$GOPATH`.
Then, run `make` in the root of the repo. 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 ## 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;
};
}

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

@ -213,11 +213,10 @@ func (server *Server) handleSearchInternal(state *State, w ldap.ResponseWriter,
continue continue
} }
// Send result // Send result
resultVals := []message.AttributeValue{}
for _, v := range val { 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) w.Write(e)
} }

70
ssha.go
View file

@ -2,16 +2,8 @@ package main
import ( import (
"errors" "errors"
"strings"
"bytes" "github.com/jsimonetti/pwscheme/ssha"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"fmt"
log "github.com/sirupsen/logrus"
//"github.com/jsimonetti/pwscheme/ssha"
"github.com/jsimonetti/pwscheme/ssha256" "github.com/jsimonetti/pwscheme/ssha256"
"github.com/jsimonetti/pwscheme/ssha512" "github.com/jsimonetti/pwscheme/ssha512"
) )
@ -34,12 +26,9 @@ func SSHAMatches(encodedPassPhrase string, rawPassPhrase string) (bool, error) {
return false, errors.New("invalid password hash stored") return false, errors.New("invalid password hash stored")
} }
var is_ok bool
switch hashType { switch hashType {
case SSHA: case SSHA:
is_ok = LegacySSHAMatches(encodedPassPhrase, []byte(rawPassPhrase)) return ssha.Validate(rawPassPhrase, encodedPassPhrase)
return is_ok, nil
//return ssha.Validate(rawPassPhrase, encodedPassPhrase)
case SSHA256: case SSHA256:
return ssha256.Validate(rawPassPhrase, encodedPassPhrase) return ssha256.Validate(rawPassPhrase, encodedPassPhrase)
case SSHA512: case SSHA512:
@ -50,64 +39,15 @@ func SSHAMatches(encodedPassPhrase string, rawPassPhrase string) (bool, error) {
} }
func determineHashType(hash string) (string, error) { func determineHashType(hash string) (string, error) {
if len(hash) >= 7 && strings.ToUpper(string(hash[0:6])) == SSHA { if len(hash) >= 7 && string(hash[0:6]) == SSHA {
return SSHA, nil return SSHA, nil
} }
if len(hash) >= 10 && strings.ToUpper(string(hash[0:9])) == SSHA256 { if len(hash) >= 10 && string(hash[0:9]) == SSHA256 {
return SSHA256, nil return SSHA256, nil
} }
if len(hash) >= 10 && strings.ToUpper(string(hash[0:9])) == SSHA512 { if len(hash) >= 10 && string(hash[0:9]) == SSHA512 {
return SSHA512, nil return SSHA512, nil
} }
return "", errors.New("no valid hash found") return "", errors.New("no valid hash found")
} }
// --- legacy
// Encode encodes the []byte of raw password
func LegacySSHAEncode(rawPassPhrase []byte) string {
hash := legacyMakeSSHAHash(rawPassPhrase, legacyMakeSalt())
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 {
if !strings.EqualFold(encodedPassPhrase[:6], "{ssha}") {
return false
}
bhash, err := base64.StdEncoding.DecodeString(encodedPassPhrase[6:])
if err != nil {
return false
}
salt := bhash[20:]
newssha := legacyMakeSSHAHash(rawPassPhrase, salt)
if bytes.Compare(newssha, bhash) != 0 {
return false
}
return true
}
// makeSalt make a 32 byte array containing random bytes.
func legacyMakeSalt() []byte {
sbytes := make([]byte, 32)
_, err := rand.Read(sbytes)
if err != nil {
log.Panicf("Could not read random bytes: %s", err)
}
return sbytes
}
// 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 {
sha := sha1.New()
sha.Write(passphrase)
sha.Write(salt)
h := sha.Sum(nil)
return append(h, salt...)
}