mirror of
https://github.com/glauth/glauth.git
synced 2026-10-27 06:26:59 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 54727ce9af | |||
| fb65713fbd | |||
| c8e7a7e7e6 | |||
| cb121cdc2c | |||
| 4b2b1398b8 | |||
| 2e125a4a2f | |||
| be396ae635 | |||
| 719f6a2a2d | |||
| ff7a9e6215 | |||
| 45185dafd1 | |||
| aedc0f2867 | |||
| 0774c95c1a | |||
| 9e04c8b756 | |||
| d3df7252ec | |||
| 5123a3da8e | |||
| eabf67e266 |
@@ -7,7 +7,7 @@ on:
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.21.x]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
name: release-please
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
@@ -8,20 +9,12 @@ permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
name: release-please
|
||||
|
||||
jobs:
|
||||
release-please:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: google-github-actions/release-please-action@v3
|
||||
- uses: google-github-actions/release-please-action@v4
|
||||
with:
|
||||
release-type: go
|
||||
package-name: GLAuth
|
||||
path: v2
|
||||
default-branch: master
|
||||
pull-request-title-pattern: "ci: release ${version}"
|
||||
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
||||
extra-files: |
|
||||
v2/internal/version/const.go
|
||||
config-file: release-please-config.json
|
||||
manifest-file: .release-please-manifest.json
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"v2": "2.5.0"
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
# GLAuth: LDAP authentication server for developers
|
||||
Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ configurable backends.
|
||||
|
||||
[](https://gitter.im/glauth/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](hey)
|
||||
# GLAuth: LDAP authentication server for developers
|
||||
[](https://glauth.github.io/)
|
||||
[](https://github.com/fusion/kittendns)
|
||||
[](https://github.com/fusion/wing-vpn)
|
||||
|
||||
Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ configurable backends.
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
* Centrally manage accounts across your infrastructure
|
||||
* Centrally manage SSH keys, Linux accounts, and passwords for cloud servers.
|
||||
@@ -21,7 +21,7 @@ Use it to centralize account management across your Linux servers, your OSX mach
|
||||
|
||||
### Contributing
|
||||
- Please base all Pull Requests on [dev](https://github.com/glauth/glauth/tree/dev), not master.
|
||||
- Format your code autonmatically using `gofmt -d ./` before committing
|
||||
- Format your code automatically using `gofmt -d ./` before committing
|
||||
|
||||
### Quickstart
|
||||
This quickstart is a great way to try out GLAuth in a non-production environment. *Be warned that you should take the extra steps to setup SSL (TLS) for production use!*
|
||||
@@ -110,4 +110,3 @@ For advanced users, GLAuth supports pluggable backends. Currently, it can use a
|
||||
# Stargazers over time
|
||||
|
||||
[](https://starchart.cc/glauth/glauth)
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"packages": {
|
||||
"v2": {
|
||||
"release-type": "go",
|
||||
"package-name": "GLAuth",
|
||||
"pull-request-title-pattern": "ci: release ${version}",
|
||||
"extra-files": [
|
||||
"v2/internal/version/const.go"
|
||||
]
|
||||
}
|
||||
},
|
||||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
|
||||
}
|
||||
@@ -1,5 +1,45 @@
|
||||
# Changelog
|
||||
|
||||
## [2.5.0](https://github.com/glauth/glauth/compare/GLAuth-v2.5.0...GLAuth-v2.5.0) (2026-04-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow tracing configuration via main config ([f692394](https://github.com/glauth/glauth/commit/f692394942aa0f93a1aa1572331fd1c3e3553156))
|
||||
* alt container build registry ([f8dee8d](https://github.com/glauth/glauth/commit/f8dee8da879232f5c6ae2a2a19565562c7d0d9fd))
|
||||
* embedded plugins ([c0ed908](https://github.com/glauth/glauth/commit/c0ed90822fd4d2ee7104a1979d423a0c93d758ac))
|
||||
* introduce context for otlp spans into handler pkg ([d05630f](https://github.com/glauth/glauth/commit/d05630f66b80a776fd406782ad1fde5c6c66eac6))
|
||||
* introduce context for otlp spans into plugins pkg ([46e49b6](https://github.com/glauth/glauth/commit/46e49b6976318a9f3670b88bdcb3411dfac0a17c))
|
||||
* introduce legacy behavior, based on past versions ([#318](https://github.com/glauth/glauth/issues/318)) ([34a6b30](https://github.com/glauth/glauth/commit/34a6b30b8fe69c90cab09eb85890ffdfe8fc80fd))
|
||||
* introduce otelsql ([2ca5312](https://github.com/glauth/glauth/commit/2ca53126965aa7d42b23aee15f750df12822d454))
|
||||
* introduce otlp tracer ([0cf0403](https://github.com/glauth/glauth/commit/0cf04037a2a7b38c8ed7af2451b115f48c5427b5))
|
||||
* proxying ldap paging compat ([#422](https://github.com/glauth/glauth/issues/422)) ([8476506](https://github.com/glauth/glauth/commit/8476506b8c51e993eb90c73d612f69fec5350b89))
|
||||
* starttls and ldaps now matching features ([#458](https://github.com/glauth/glauth/issues/458)) ([d68c78a](https://github.com/glauth/glauth/commit/d68c78a18b150679038e5ce549b6b227c31518d7))
|
||||
* wire up basic tracer ([1c2b23c](https://github.com/glauth/glauth/commit/1c2b23c00ff85b83a6d2e4bf4a9a68081aaf2777))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 246 and [#252](https://github.com/glauth/glauth/issues/252) ([ceefc66](https://github.com/glauth/glauth/commit/ceefc66dbba9ae054958269825de31d22d30e262))
|
||||
* config watcher now updates whenf ile added to dir ([#424](https://github.com/glauth/glauth/issues/424)) ([a90f37b](https://github.com/glauth/glauth/commit/a90f37b23b76af22c700392d68bf227b7aee1894))
|
||||
* Credentials leaked on INFO log level ([#451](https://github.com/glauth/glauth/issues/451)) ([645dbb2](https://github.com/glauth/glauth/commit/645dbb226683ae57f3d5382e21ea42444e940752))
|
||||
* drop vendored toml ([24455e3](https://github.com/glauth/glauth/commit/24455e39889716ce7ae1e7e8a7dacfa7d4c96080))
|
||||
* formatting ([c90cbfe](https://github.com/glauth/glauth/commit/c90cbfe5fb090fc19a55d64e01cd0e31c38bf2f0))
|
||||
* go test not checking otp within allowed basedn ([#403](https://github.com/glauth/glauth/issues/403)) ([ed52a91](https://github.com/glauth/glauth/commit/ed52a91ec4117ff58fe606a1d8ba10786501a1e5))
|
||||
* move all toml parsing into a new internal package, drop the mappings in favour of toml.Primitive decoding ([3ba8e11](https://github.com/glauth/glauth/commit/3ba8e1113217be647d240261322453d213ea7da4))
|
||||
* patch group name fetches for the rest of handlers ([#450](https://github.com/glauth/glauth/issues/450)) ([b498736](https://github.com/glauth/glauth/commit/b498736cfdd0ee6f1585d682a5c54c9d927b8dbb))
|
||||
* remove spinlock in monitoring code ([#405](https://github.com/glauth/glauth/issues/405)) ([a2c151b](https://github.com/glauth/glauth/commit/a2c151b0d025332462369a846a51ef4deff5332b))
|
||||
* removed config setup from main, reshoring log configuration ([5fe8aca](https://github.com/glauth/glauth/commit/5fe8aca852bdfe7e375ea99b87074bae35fc3407))
|
||||
* update ldap library to fix [#389](https://github.com/glauth/glauth/issues/389) ([#430](https://github.com/glauth/glauth/issues/430)) ([a5ecc26](https://github.com/glauth/glauth/commit/a5ecc26e064cea9e4dab3dd234ebd5d5eb06be0e))
|
||||
* update tracing code to work with breaking otlp 1.20 changes ([1a37396](https://github.com/glauth/glauth/commit/1a3739610997b58100040d0c8a405596fccc8e23))
|
||||
* upgrade to use BurntSushi/toml ([f9addbc](https://github.com/glauth/glauth/commit/f9addbc2c5b13ccc2779dbabc4c55bc8706f53d6))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* force release 2.5.0 ([ff7a9e6](https://github.com/glauth/glauth/commit/ff7a9e6215857e0dd3450504b7b063409ccfc374))
|
||||
* release 2.3.1 ([0bf3d4a](https://github.com/glauth/glauth/commit/0bf3d4a82a8451e7bbda74e3730345aab5a855a7))
|
||||
|
||||
## [2.4.0](https://github.com/glauth/glauth/compare/v2.3.2...v2.4.0) (2024-08-04)
|
||||
|
||||
|
||||
|
||||
+18
-12
@@ -24,8 +24,8 @@ TRIM_FLAGS>?=-trimpath
|
||||
BUILD_TAGS?=noembed
|
||||
|
||||
# Targets
|
||||
MAIN_TARGETS?=linux/amd64,linux/386,linux/arm64,linux/arm-7,darwin/amd64,darwin/arm64,windows/amd64,windows/386
|
||||
PLUGIN_TARGETS?=linux/amd64,linux/386,linux/arm64,linux/arm-7,darwin/amd64,darwin/arm64
|
||||
MAIN_TARGETS?=linux/amd64,linux/386,linux/arm64,linux/arm-7,linux/riscv64,darwin/amd64,darwin/arm64,windows/amd64,windows/386
|
||||
PLUGIN_TARGETS?=linux/amd64,linux/386,linux/arm64,linux/arm-7,linux/riscv64,darwin/amd64,darwin/arm64
|
||||
EMBED_TARGETS?=windows/amd64
|
||||
|
||||
# For release process
|
||||
@@ -58,10 +58,9 @@ mocks:
|
||||
vet:
|
||||
-go vet ./...
|
||||
.PHONY: vet
|
||||
#test: @ runs the integration test on linuxamd64 (eventually allow the binary to be set)
|
||||
#test: @ runs package, root integration, and shell integration tests
|
||||
test: mocks vet
|
||||
# also run unit tests for packages, skip glauth_test.go for now
|
||||
go test -v -cover -coverprofile coverage.out ./pkg/... ./internal/...
|
||||
go test -v -cover -coverprofile coverage.out ./pkg/... ./internal/... .
|
||||
$(MAKE) runtest
|
||||
#all: @ run build process for all binaries
|
||||
all: setup binaries verify
|
||||
@@ -73,7 +72,7 @@ fast: setup linuxamd64
|
||||
fastdebug: setup linuxamd64debug
|
||||
|
||||
#binaries: @ list of binary formats to build
|
||||
binaries: linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64
|
||||
binaries: linux386 linuxamd64 linuxarm linuxarm64 linuxriscv64 darwinamd64 darwinarm64 win386 winamd64
|
||||
|
||||
#setup: @ setup commands to always run
|
||||
setup: getdeps format
|
||||
@@ -154,6 +153,9 @@ linuxarm:
|
||||
linuxarm64:
|
||||
GOOS=linux GOARCH=arm64 $(MAKE) build
|
||||
|
||||
linuxriscv64:
|
||||
GOOS=linux GOARCH=riscv64 $(MAKE) build
|
||||
|
||||
testing:
|
||||
mkdir -p pkg/embed && \
|
||||
cat pkg/plugins/glauth-sqlite/sqlite.go | sed -e 's/func main() {}//' -e 's/^package main/package embed/' > pkg/embed/sqlite.go && \
|
||||
@@ -174,7 +176,7 @@ winamd64:
|
||||
GOOS=windows GOARCH=amd64 $(MAKE) build
|
||||
|
||||
verify:
|
||||
@for binary in linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64; do cd bin/$$binary && sha256sum glauth.sha256 -c && cd ../..; done
|
||||
@for binary in linux386 linuxamd64 linuxarm linuxarm64 linuxriscv64 darwinamd64 darwinarm64 win386 winamd64; do cd bin/$$binary && sha256sum glauth.sha256 -c && cd ../..; done
|
||||
|
||||
pull-plugin-dependencies:
|
||||
(for pkg in $$(cat $$M/go.mod | awk '/^require [a-z]/{print $$2} /^\)/{rblock=0} rblock{if($$4!="indirect" && $$1!~"glauth"){print $$1}} /^require \(/{rblock=1}'); do if [ "$$(grep $$pkg go.mod &>/dev/null)" = "" ]; then go get $$pkg; fi; done)
|
||||
@@ -270,8 +272,9 @@ builddockermain:
|
||||
cp -f bin/glauth-linux-amd64 docker/assets/linux/amd64/glauth && \
|
||||
cp -f bin/glauth-linux-arm64 docker/assets/linux/arm64/glauth && \
|
||||
cp -f bin/glauth-linux-arm-7 docker/assets/linux/arm/v7/glauth && \
|
||||
docker buildx build --tag ghcr.io/$$REPO/glauth:$$TAG -t ghcr.io/$$REPO/glauth:latest -f docker/Dockerfile-standalone --platform linux/amd64,linux/arm64,linux/arm/v7 --push docker && \
|
||||
docker buildx build --tag $$REPO/glauth:$$TAG -t $$REPO/glauth:latest -f docker/Dockerfile-standalone --platform linux/amd64,linux/arm64,linux/arm/v7 --push docker
|
||||
cp -f bin/glauth-linux-riscv64 docker/assets/linux/riscv64/glauth && \
|
||||
docker buildx build --tag ghcr.io/$$REPO/glauth:$$TAG -t ghcr.io/$$REPO/glauth:latest -f docker/Dockerfile-standalone --platform linux/amd64,linux/arm64,linux/arm/v7,linux/riscv64 --push docker && \
|
||||
docker buildx build --tag $$REPO/glauth:$$TAG -t $$REPO/glauth:latest -f docker/Dockerfile-standalone --platform linux/amd64,linux/arm64,linux/arm/v7,linux/riscv64 --push docker
|
||||
|
||||
builddockerplugins:
|
||||
$(if $(TAG),,$(error ❌ Must set TAG))
|
||||
@@ -280,14 +283,17 @@ builddockerplugins:
|
||||
cp -f bin/sqlite-linux-amd64.so docker/assets/linux/amd64/sqlite.so && \
|
||||
cp -f bin/sqlite-linux-arm64.so docker/assets/linux/arm64/sqlite.so && \
|
||||
cp -f bin/sqlite-linux-arm-7.so docker/assets/linux/arm/v7/sqlite.so && \
|
||||
cp -f bin/sqlite-linux-riscv64.so docker/assets/linux/riscv64/sqlite.so && \
|
||||
cp -f bin/mysql-linux-amd64.so docker/assets/linux/amd64/mysql.so && \
|
||||
cp -f bin/mysql-linux-arm64.so docker/assets/linux/arm64/mysql.so && \
|
||||
cp -f bin/mysql-linux-arm-7.so docker/assets/linux/arm/v7/mysql.so && \
|
||||
cp -f bin/mysql-linux-riscv64.so docker/assets/linux/riscv64/mysql.so && \
|
||||
cp -f bin/postgres-linux-amd64.so docker/assets/linux/amd64/postgres.so && \
|
||||
cp -f bin/postgres-linux-arm64.so docker/assets/linux/arm64/postgres.so && \
|
||||
cp -f bin/postgres-linux-arm-7.so docker/assets/linux/arm/v7/postgres.so && \
|
||||
docker buildx build --tag ghcr.io/$$REPO/glauth-plugins:$$TAG -t ghcr.io/$$REPO/glauth-plugins:latest -f docker/Dockerfile-plugins --platform linux/amd64,linux/arm64,linux/arm/v7 --push docker && \
|
||||
docker buildx build --tag $$REPO/glauth-plugins:$$TAG -t $$REPO/glauth-plugins:latest -f docker/Dockerfile-plugins --platform linux/amd64,linux/arm64,linux/arm/v7 --push docker
|
||||
cp -f bin/postgres-linux-riscv64.so docker/assets/linux/riscv64/postgres.so && \
|
||||
docker buildx build --tag ghcr.io/$$REPO/glauth-plugins:$$TAG -t ghcr.io/$$REPO/glauth-plugins:latest -f docker/Dockerfile-plugins --platform linux/amd64,linux/arm64,linux/arm/v7,linux/riscv64 --push docker && \
|
||||
docker buildx build --tag $$REPO/glauth-plugins:$$TAG -t $$REPO/glauth-plugins:latest -f docker/Dockerfile-plugins --platform linux/amd64,linux/arm64,linux/arm/v7,linux/riscv64 --push docker
|
||||
|
||||
builddocker: _checkdockerauth builddockermain builddockerplugins
|
||||
|
||||
@@ -335,6 +341,6 @@ testdocker:
|
||||
|
||||
.PHONY: all \
|
||||
prepbuild run test fast binaries setup getdeps runtest updatetest format devrun \
|
||||
linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64 verify \
|
||||
linux386 linuxamd64 linuxarm linuxarm64 linuxriscv64 darwinamd64 darwinarm64 win386 winamd64 verify \
|
||||
pull-plugin pull-base-plugins forget-plugin forget-plugins buildmain buildplugin build \
|
||||
buildplugins builddockermain builddockerplugins builddocker testdocker buildeverything
|
||||
|
||||
@@ -10,7 +10,7 @@ RUN apk add --no-cache git make busybox-static dumb-init
|
||||
# Run Step
|
||||
#################
|
||||
|
||||
FROM gcr.io/distroless/base-debian11 as run
|
||||
FROM gcr.io/distroless/base-debian13 as run
|
||||
|
||||
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>" \
|
||||
org.opencontainers.image.title="GLauth" \
|
||||
|
||||
@@ -10,7 +10,7 @@ RUN apk add --no-cache git make busybox-static dumb-init
|
||||
# Run Step
|
||||
#################
|
||||
|
||||
FROM gcr.io/distroless/base-debian11 as run
|
||||
FROM gcr.io/distroless/base-debian13 as run
|
||||
|
||||
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>" \
|
||||
org.opencontainers.image.title="GLauth" \
|
||||
|
||||
+117
-5
@@ -1,8 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -23,10 +27,22 @@ type testEnv struct {
|
||||
checkemployeetype string
|
||||
}
|
||||
|
||||
func glauthBinary() string {
|
||||
if binary := os.Getenv("GLAUTH_TEST_BINARY"); binary != "" {
|
||||
return binary
|
||||
}
|
||||
|
||||
qualified := filepath.Join("bin", runtime.GOOS+runtime.GOARCH, "glauth")
|
||||
if _, err := os.Stat(qualified); err == nil {
|
||||
return qualified
|
||||
}
|
||||
return "bin/glauth"
|
||||
}
|
||||
|
||||
func TestProperBuild(t *testing.T) {
|
||||
info, err := os.Stat("bin/glauth")
|
||||
info, err := os.Stat(glauthBinary())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mode := uint32(info.Mode())
|
||||
if mode&0b001001001 == 0 {
|
||||
@@ -34,6 +50,102 @@ func TestProperBuild(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigBackendRejectsPasswordlessUserBinds(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port := listener.Addr().(*net.TCPAddr).Port
|
||||
listener.Close()
|
||||
|
||||
configPath := filepath.Join(t.TempDir(), "passwordless.cfg")
|
||||
config := fmt.Sprintf(`debug = false
|
||||
|
||||
[ldap]
|
||||
enabled = true
|
||||
listen = "127.0.0.1:%d"
|
||||
tls = false
|
||||
|
||||
[ldaps]
|
||||
enabled = false
|
||||
|
||||
[backend]
|
||||
datastore = "config"
|
||||
baseDN = "dc=glauth,dc=com"
|
||||
|
||||
[behaviors]
|
||||
LimitFailedBinds = false
|
||||
|
||||
[[groups]]
|
||||
name = "svc"
|
||||
gidnumber = 5501
|
||||
|
||||
[[users]]
|
||||
name = "nopass"
|
||||
uidnumber = 5001
|
||||
primarygroup = 5501
|
||||
[[users.capabilities]]
|
||||
action = "search"
|
||||
object = "*"
|
||||
`, port)
|
||||
if err := os.WriteFile(configPath, []byte(config), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
svc := startSvc(SD, glauthBinary(), "-c", configPath)
|
||||
defer stopSvc(svc)
|
||||
|
||||
bindDN := "cn=nopass,dc=glauth,dc=com"
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
password string
|
||||
want string
|
||||
}{
|
||||
{name: "arbitrary password", password: "definitely-wrong", want: "exit status 49"},
|
||||
{name: "empty password", password: "", want: "exit status 53"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := doRunGetFirst(0, "ldapsearch", "-LLL", "-H", fmt.Sprintf("ldap://127.0.0.1:%d", port), "-D", bindDN, "-w", tc.password, "-x", "-b", "dc=glauth,dc=com", "(cn=nopass)")
|
||||
if got != tc.want {
|
||||
t.Fatalf("bind result = %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ldapOnlyConfig(t *testing.T, source string) string {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
contents := string(data)
|
||||
sectionMarker := "\n[ldaps]\n"
|
||||
markerStart := strings.Index(contents, sectionMarker)
|
||||
if markerStart < 0 {
|
||||
t.Fatalf("%s has no [ldaps] section", source)
|
||||
}
|
||||
start := markerStart + 1
|
||||
relEnd := strings.Index(contents[start+len("[ldaps]"):], "\n[")
|
||||
if relEnd < 0 {
|
||||
t.Fatalf("%s has no section after [ldaps]", source)
|
||||
}
|
||||
end := start + len("[ldaps]") + relEnd
|
||||
section := contents[start:end]
|
||||
sectionWithoutLDAPS := strings.Replace(section, "enabled = true", "enabled = false", 1)
|
||||
if section == sectionWithoutLDAPS {
|
||||
t.Fatalf("%s [ldaps] section does not enable LDAPS", source)
|
||||
}
|
||||
|
||||
contents = contents[:start] + sectionWithoutLDAPS + contents[end:]
|
||||
path := filepath.Join(t.TempDir(), filepath.Base(source))
|
||||
if err := os.WriteFile(path, []byte(contents), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
@@ -195,7 +307,7 @@ func TestSampleSimple(t *testing.T) {
|
||||
checkemployeetype: "cn=hackers",
|
||||
}
|
||||
|
||||
svc := startSvc(SD, "bin/glauth", "-c", "sample-simple.cfg")
|
||||
svc := startSvc(SD, glauthBinary(), "-c", ldapOnlyConfig(t, "sample-simple.cfg"))
|
||||
batteryOfTests(t, &env)
|
||||
stopSvc(svc)
|
||||
}
|
||||
@@ -221,7 +333,7 @@ func TestSQLitePlugin(t *testing.T) {
|
||||
checkemployeetype: "",
|
||||
}
|
||||
|
||||
svc := startSvc(SD, "bin/glauth", "-c", "pkg/plugins/glauth-sqlite/sample-database.cfg")
|
||||
svc := startSvc(SD, glauthBinary(), "-c", "pkg/plugins/glauth-sqlite/sample-database.cfg")
|
||||
batteryOfTests(t, &env)
|
||||
stopSvc(svc)
|
||||
}
|
||||
@@ -247,7 +359,7 @@ func TestLdapInjection(t *testing.T) {
|
||||
checkemployeetype: "",
|
||||
}
|
||||
|
||||
svc := startSvc(SD, "bin/glauth", "-c", "sample-ldap-injection.cfg")
|
||||
svc := startSvc(SD, glauthBinary(), "-c", "sample-ldap-injection.cfg")
|
||||
batteryOfTests(t, &env)
|
||||
stopSvc(svc)
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ package version
|
||||
|
||||
// Version is the tag of the latest release, it gets changed by the ci
|
||||
// process when a release is happening
|
||||
const Version = "v2.3.1" // x-release-please-version
|
||||
const Version = "v2.5.0" // x-release-please-version
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/ldap"
|
||||
)
|
||||
|
||||
type disabledBindTestMonitor struct{}
|
||||
|
||||
func (disabledBindTestMonitor) SetResponseTimeMetric(map[string]string, float64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (disabledBindTestMonitor) SetLDAPMetric(map[string]string, float64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestConfigBackendDisabledUserCannotBind(t *testing.T) {
|
||||
pw := "correct horse"
|
||||
raw, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.MinCost)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hexHash := hex.EncodeToString(raw)
|
||||
|
||||
cfg := &config.Config{}
|
||||
cfg.Users = []config.User{
|
||||
{Name: "activeuser", PrimaryGroup: 5501, PassBcrypt: hexHash, Disabled: false},
|
||||
{Name: "disableduser", PrimaryGroup: 5501, PassBcrypt: hexHash, Disabled: true},
|
||||
}
|
||||
|
||||
logger := zerolog.Nop()
|
||||
tracer := noop.NewTracerProvider().Tracer("f001")
|
||||
helper := NewLDAPOpsHelper(tracer)
|
||||
h := configHandler{
|
||||
backend: config.Backend{
|
||||
BaseDN: "dc=example,dc=com",
|
||||
NameFormatAsArray: []string{"cn"},
|
||||
GroupFormatAsArray: []string{"ou"},
|
||||
},
|
||||
log: &logger,
|
||||
cfg: cfg,
|
||||
ldohelper: helper,
|
||||
monitor: disabledBindTestMonitor{},
|
||||
tracer: tracer,
|
||||
}
|
||||
_, server := net.Pipe()
|
||||
defer server.Close()
|
||||
|
||||
bind := func(dn, pass string) ldap.LDAPResultCode {
|
||||
code, _ := h.Bind(dn, pass, server)
|
||||
return code
|
||||
}
|
||||
|
||||
if code := bind("cn=activeuser,dc=example,dc=com", pw); code != ldap.LDAPResultSuccess {
|
||||
t.Fatalf("active user with correct pw should bind, got %d", code)
|
||||
}
|
||||
if code := bind("cn=disableduser,dc=example,dc=com", pw); code != ldap.LDAPResultInvalidCredentials {
|
||||
t.Errorf("disabled user must be rejected; got code %d, want InvalidCredentials(49)", code)
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,8 @@ func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (result ld
|
||||
|
||||
validotp := false
|
||||
|
||||
// Local records augment proxy authentication with OTP/YubiKey checks;
|
||||
// password authentication remains delegated to the upstream LDAP server.
|
||||
// Find the user
|
||||
// We are going to go through all backends and ask
|
||||
// until we find our user or die of boredom.
|
||||
|
||||
@@ -65,6 +65,10 @@ func NewLDAPOpsHelper(tracer trace.Tracer) LDAPOpsHelper {
|
||||
return helper
|
||||
}
|
||||
|
||||
func isUnauthenticatedSimpleBind(bindDN, bindSimplePw string) bool {
|
||||
return bindDN != "" && bindSimplePw == ""
|
||||
}
|
||||
|
||||
func (l LDAPOpsHelper) Bind(ctx context.Context, h LDAPOpsHandler, bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.Bind")
|
||||
defer span.End()
|
||||
@@ -86,11 +90,24 @@ func (l LDAPOpsHelper) Bind(ctx context.Context, h LDAPOpsHandler, bindDN, bindS
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
// A non-empty DN with an empty password is LDAP's unauthenticated bind
|
||||
// mechanism. It must not establish the named user's authorization state.
|
||||
if isUnauthenticatedSimpleBind(bindDN, bindSimplePw) {
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("unauthenticated bind rejected")
|
||||
return ldap.LDAPResultUnwillingToPerform, nil
|
||||
}
|
||||
|
||||
user, ldapcode := l.findUser(ctx, h, bindDN, true /* checkGroup */)
|
||||
if ldapcode != ldap.LDAPResultSuccess {
|
||||
return ldapcode, nil
|
||||
}
|
||||
|
||||
// a disabled account must never authenticate, on any backend
|
||||
if user.Disabled {
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind attempt on disabled account")
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
validotp := false
|
||||
|
||||
if len(user.Yubikey) == 0 && len(user.OTPSecret) == 0 {
|
||||
@@ -175,6 +192,15 @@ func (l LDAPOpsHelper) Bind(ctx context.Context, h LDAPOpsHandler, bindDN, bindS
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
// Reaching this point without a configured primary password verifier must
|
||||
// not be treated as successful authentication. App-password matches and
|
||||
// custom authentication return above when they succeed.
|
||||
if user.PassBcrypt == "" && user.PassSHA256 == "" {
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("invalid credentials")
|
||||
l.maybePutInTimeout(ctx, h, conn, true)
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
// Now, check the pasword hash
|
||||
if user.PassBcrypt != "" {
|
||||
decoded, err := hex.DecodeString(user.PassBcrypt)
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/ldap"
|
||||
)
|
||||
|
||||
type bindTestHandler struct {
|
||||
backend config.Backend
|
||||
cfg config.Config
|
||||
log zerolog.Logger
|
||||
user config.User
|
||||
}
|
||||
|
||||
func (h *bindTestHandler) GetBackend() config.Backend { return h.backend }
|
||||
func (h *bindTestHandler) GetLog() *zerolog.Logger { return &h.log }
|
||||
func (h *bindTestHandler) GetCfg() *config.Config { return &h.cfg }
|
||||
func (h *bindTestHandler) GetYubikeyAuth() *yubigo.YubiAuth {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *bindTestHandler) FindUser(_ context.Context, userName string, _ bool) (bool, config.User, error) {
|
||||
if userName == h.user.Name {
|
||||
return true, h.user, nil
|
||||
}
|
||||
return false, config.User{}, nil
|
||||
}
|
||||
|
||||
func (h *bindTestHandler) FindGroup(context.Context, string) (bool, config.Group, error) {
|
||||
return false, config.Group{}, nil
|
||||
}
|
||||
|
||||
func (h *bindTestHandler) FindPosixAccounts(context.Context, string) ([]*ldap.Entry, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (h *bindTestHandler) FindPosixGroups(context.Context, string) ([]*ldap.Entry, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func sha256Password(password string) string {
|
||||
hash := sha256.Sum256([]byte(password))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func TestLDAPOpsHelperBindRejectsUnauthenticatedAndPasswordlessBinds(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
password string
|
||||
user config.User
|
||||
wantResult ldap.LDAPResultCode
|
||||
}{
|
||||
{
|
||||
name: "named empty password is unauthenticated bind",
|
||||
password: "",
|
||||
wantResult: ldap.LDAPResultUnwillingToPerform,
|
||||
},
|
||||
{
|
||||
name: "arbitrary password without verifier",
|
||||
password: "definitely-wrong",
|
||||
wantResult: ldap.LDAPResultInvalidCredentials,
|
||||
},
|
||||
{
|
||||
name: "failed app password does not fall through",
|
||||
user: config.User{
|
||||
Name: "nopass",
|
||||
PassAppSHA256: []string{sha256Password("expected")},
|
||||
},
|
||||
password: "definitely-wrong",
|
||||
wantResult: ldap.LDAPResultInvalidCredentials,
|
||||
},
|
||||
{
|
||||
name: "matching primary password succeeds",
|
||||
user: config.User{
|
||||
Name: "nopass",
|
||||
PassSHA256: sha256Password("correct"),
|
||||
},
|
||||
password: "correct",
|
||||
wantResult: ldap.LDAPResultSuccess,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
user := tt.user
|
||||
if user.Name == "" {
|
||||
user.Name = "nopass"
|
||||
}
|
||||
h := &bindTestHandler{
|
||||
backend: config.Backend{
|
||||
BaseDN: "dc=glauth,dc=com",
|
||||
NameFormatAsArray: []string{"cn"},
|
||||
GroupFormatAsArray: []string{"ou"},
|
||||
},
|
||||
user: user,
|
||||
log: zerolog.Nop(),
|
||||
}
|
||||
conn, peer := net.Pipe()
|
||||
defer conn.Close()
|
||||
defer peer.Close()
|
||||
|
||||
helper := NewLDAPOpsHelper(trace.NewNoopTracerProvider().Tracer("bind-test"))
|
||||
got, err := helper.Bind(context.Background(), h, "cn=nopass,dc=glauth,dc=com", tt.password, conn)
|
||||
if err != nil {
|
||||
t.Fatalf("Bind returned error: %v", err)
|
||||
}
|
||||
if got != tt.wantResult {
|
||||
t.Fatalf("Bind result = %v, want %v", got, tt.wantResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLDAPOpsHelperBindAllowsAnonymousBind(t *testing.T) {
|
||||
h := &bindTestHandler{
|
||||
backend: config.Backend{BaseDN: "dc=glauth,dc=com"},
|
||||
log: zerolog.Nop(),
|
||||
}
|
||||
conn, peer := net.Pipe()
|
||||
defer conn.Close()
|
||||
defer peer.Close()
|
||||
|
||||
helper := NewLDAPOpsHelper(trace.NewNoopTracerProvider().Tracer("bind-test"))
|
||||
got, err := helper.Bind(context.Background(), h, "", "", conn)
|
||||
if err != nil {
|
||||
t.Fatalf("Bind returned error: %v", err)
|
||||
}
|
||||
if got != ldap.LDAPResultSuccess {
|
||||
t.Fatalf("Bind result = %v, want %v", got, ldap.LDAPResultSuccess)
|
||||
}
|
||||
}
|
||||
@@ -33,14 +33,14 @@ fi
|
||||
echo "";
|
||||
echo ""
|
||||
echo "Version string of tested binary:"
|
||||
"$CI_BUILD_DIR/bin/linuxamd64/glauth" --version
|
||||
"$CI_BUILD_DIR/bin/$(go env GOOS)$(go env GOARCH)/glauth" --version
|
||||
echo ""
|
||||
|
||||
# Start in background, capture PID
|
||||
"$CI_BUILD_DIR/bin/linuxamd64/glauth" -c "$CI_BUILD_DIR/scripts/ci/test-config.cfg" &> /dev/null &
|
||||
"$CI_BUILD_DIR/bin/$(go env GOOS)$(go env GOARCH)/glauth" -c "$CI_BUILD_DIR/scripts/ci/test-config.cfg" &> /dev/null &
|
||||
|
||||
# Use this instead to see glauth logs while running
|
||||
# "$CI_BUILD_DIR/bin/linuxamd64/glauth" -c "$CI_BUILD_DIR/scripts/ci/test-config.cfg" &
|
||||
# "$CI_BUILD_DIR/bin/$(go env GOOS)$(go env GOARCH)/glauth" -c "$CI_BUILD_DIR/scripts/ci/test-config.cfg" &
|
||||
glauthPid="$!"
|
||||
|
||||
echo "Running glauth at PID=$glauthPid"
|
||||
|
||||
Reference in New Issue
Block a user