Compare commits

..

16 Commits

Author SHA1 Message Date
Chris F Ravenscroft 54727ce9af Merge branch 'master' into pr-470 2026-07-18 14:55:52 -07:00
kt fb65713fbd fix: reject disabled users on all backends in Bind (#470)
A user with disabled = true could still bind on the config backend, since
configHandler.FindUser and LDAPOpsHelper.Bind never checked Disabled while the
SQL backends drop such users at lookup. Enforce it in Bind so every backend
behaves the same.
2026-07-18 14:53:08 -07:00
Chris F Ravenscroft c8e7a7e7e6 fix: use configHandler.Bind rather than helper.Bind in test harness 2026-07-18 14:44:15 -07:00
Chris F Ravenscroft cb121cdc2c fix: team atlanta arbitrary password (#471) 2026-07-18 14:32:05 -07:00
Jörn Friedrich Dreyer 4b2b1398b8 Fix typo in contributing section of README (#468) 2026-07-18 13:31:54 -07:00
kanywst 2e125a4a2f fix: reject disabled users on all backends in Bind
A user with disabled = true could still bind on the config backend, since
configHandler.FindUser and LDAPOpsHelper.Bind never checked Disabled while the
SQL backends drop such users at lookup. Enforce it in Bind so every backend
behaves the same.
2026-07-19 00:10:27 +09:00
Chris F Ravenscroft be396ae635 ci: release 2.5.0 (#454) 2026-04-19 14:18:14 -07:00
Chris F Ravenscroft 719f6a2a2d chore: upgrade to distroless debian13 2026-04-19 14:12:37 -07:00
Chris F Ravenscroft ff7a9e6215 chore: force release 2.5.0
Release-As: 2.5.0
2026-04-19 12:25:51 -07:00
Chris F Ravenscroft 45185dafd1 chore: release 2.5.0 2026-04-19 12:21:27 -07:00
Chris F Ravenscroft aedc0f2867 chore: update readme (#467)
* Initial support for RISC-V

Add build for riscv64/linux; not available for darwin and windows.

* chore: update readme

---------

Co-authored-by: E Shattow <eshattow@users.noreply.github.com>
2026-04-19 12:18:08 -07:00
E Shattow 0774c95c1a Initial support for RISC-V (#465)
feat: Add build for riscv64/linux — not available for darwin and windows.
2026-04-19 11:56:24 -07:00
E Shattow 9e04c8b756 Use dynamic GOOS and GOARCH in integration-test.sh (#464)
Drop hard-coded linuxamd64 from path in v2/scripts/ci/integration-test.sh and use instead dynamic GOOS and GOARCH env.
2026-04-19 11:46:45 -07:00
E Shattow d3df7252ec Bump runner to Ubuntu 24.04 LTS (#462) 2026-04-19 11:39:38 -07:00
Chris F Ravenscroft 5123a3da8e chore: upgrade release-please to v4 syntax 2026-04-19 11:38:15 -07:00
Chris F Ravenscroft eabf67e266 chore: debug, openldap, samba (#466)
new target: builddebug
active directory proxying
QoL ldifs for openldap testing
2026-04-19 11:26:13 -07:00
16 changed files with 448 additions and 44 deletions
+1 -1
View File
@@ -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]
+5 -12
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
{
"v2": "2.5.0"
}
+7 -8
View File
@@ -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.
[![Gitter](https://badges.gitter.im/glauth/community.svg)](https://gitter.im/glauth/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Matrix](https://img.shields.io/badge/chat-%2fjoin%20%23glauth_community:gitter.im-green)](hey)
# GLAuth: LDAP authentication server for developers
[![Official GLAuth Website](https://img.shields.io/badge/Official%20GLAuth%20Website-glauth.github.io-brightgreen?style=plastic)](https://glauth.github.io/)
[![kittens dns](https://img.shields.io/badge/kittens%20dns-github-blue?style=plastic)](https://github.com/fusion/kittendns)
[![wing vpn](https://img.shields.io/badge/wing%20vpn-github-orange?style=plastic)](https://github.com/fusion/wing-vpn)
Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ configurable backends.
![GitHub all releases](https://img.shields.io/github/downloads/glauth/glauth/total)
![Docker pulls](https://badgen.net/docker/pulls/glauth/glauth)
![GitHub last commit (branch)](https://img.shields.io/github/last-commit/glauth/glauth/master)
![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability-percentage/glauth/glauth)
* 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
[![Stargazers over time](https://starchart.cc/glauth/glauth.svg)](https://starchart.cc/glauth/glauth)
+13
View File
@@ -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"
}
+40
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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" \
+1 -1
View File
@@ -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
View File
@@ -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)
}
+1 -1
View File
@@ -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
+69
View File
@@ -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)
}
}
+2
View File
@@ -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.
+26
View File
@@ -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)
+141
View File
@@ -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)
}
}
+3 -3
View File
@@ -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"