Compare commits

...

8 Commits

Author SHA1 Message Date
Chris F Ravenscroft 385cc335ca Dev (#254)
* use functional options pattern to inject logr (#124)

* use functional options pattern to inject logr

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* cleanup log formatting

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* allow clean shutdown (#126)

* fix owncloud posix query, log message and provisioning api results (#128)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* don't hardcode graphapi endpoint

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* add support for write handlers (#135)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* config: match shadowaccount objectlcass (#136)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* Makefile compatiblility (#134)

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* update travis.yml (#154)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* check owncloud status code is ok (#153)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* Bulid and push multiarch docker images (#142)

Fix #141.

With this change, a github action is added that builds a multiarch docker image on every commit, supporting x86_64, aarch64 and arm/v7.

When the action is triggered on a release or tag, it also uploads the versioned image to github container image registry.

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* don't mix graph and provisioning api (#157)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* Reuse http client as much as possible and allow insecure transport (#160)

* Allow using configmaps when deploying in kubernetes (#161)

* Ensure config watcher also works in kubernetes

When using configmaps in kubernetes, the file is a symlink, and then
file-watcher is not opdated with a write event. Instead it receives a
CHMOD and a REMOVE event.

This change adds two things
1) Removal of the current wacther and adding of a new watcher for the
same path
2) The do-reload conditional is updated to also include the remove
event.

* Align write and removed conditionals

* Fix API server json formating (#163) (#164)

* Update README.md (#167)

Fixing bad port in quickstart

* Use port 3893 with ldapsearch in example (#150)

* feat: add flags for ldap listen addresses (#169)

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Assets build fix. (#171)

* Docker build fix.

* Makefile fix.

* update readme, config and deps (#170)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* Remove byanke's donation fields

* Database plugins (#133)

This commit actually covers a few items. In future commits, I will keep features distinct. This is only happening this time around due to how long it took to merge this branch.

Covered:

- Database plugins (at this time: SQLite, MySQL, Postgres)
- Backends acting as middleware: added the [[Backends]] configuration directive while retaining backward compatibility with [Backend]
- Schema introspection (root DSE query with base scope)
- When proxying, insert queried attribute back in upstream response, if absent, so that the LDAP library does not filter out all entries
- When chaining backends, any backend can be used to inject OTP value in password, before reaching a non-OTP-aware backend (currently guarded by a True statement in case we find an issue (I did thoroughly test but you never know))
- Handling of special "1.1" attributes filter meaning "I do not want attributes" (RFC 4511, 4.5.1.8)
- Support for "want types only" queries, even when proxying

* Mac M1 Support and LDAP Req Attributes (#192)

* Mac M1 Support

* Augmented root DSS and schema discovery based on content of schema directory

* LDAP workaround where req. attribute gets injected in response now works with combined filters

* SubSchema query can return a minimal set, freeipa or openldap's schemas

* Feature/upgrade ldap library version (#194)

* Stronger, salted paswords using bcrypt. (#195)

* Fixed badges in README file and added a couple improvements (#196)

* fix lock for ownCloud / graph backend (#198)

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* the config struct is only needed by the config backend (#199)

* Variable "unixid" is now respectively "UIDNumber" and "GIDNumber" for… (#201)

* fix: keep watch config file when changed,renamed,removed (#189)

I will need to run `glauth.go` through `gofmt` myself but that is fine.

* Returning when unable to start config watcher (#203)

* Introducing goconvey testing and refactoring of config and ... (#204)

* Introducing goconvey testing and refactoring of config and plugin backends.

* gofmt, oops

* Refactored re-insertion of requested attributes

* Allow bind operations with no group provided (#205)

* Support for userPrincipalName binding and browsing. (#206)

* Support for userPrincipalName binding and browsing.

* Fixed travis CI test for userprincipalname

* fixing travis tests for more users

* Rate limit after failed binds (#207)

* Capabilities -- part 2 (#214) + do not dump config at startup

* Attempting migration from Travis to Github Actions

* Migration Step#2

* Migration Step#3

* Migration Step#4

* Migration Step#5

* Migration Step#6

* Migration Step#7

* Migration Step#8

* Migration Step#9

* Migration Step#9

* Migration dev...

* Migration dev...

* Migration dev...

* Migration dev...

* Docker with plugins, first step (#215)

* Docker with plugins, first step

* Build better docker images, including plugins

* Merge back from dev to feature branch step #2

* Added capabilities to CI

* - Now creating two docker images, including one with plugins
- Moving from Travis CI to GitHub Actions

* Plugins now run on distroless (#217)

* Getting rid of Travis CI now that it doesn't support FOSS anymore.

* Internal Stats -- performance view (#221)

* Local merge

* Update CI to forget about bindata

* Forgot to cleanup the cleanup...

* assets: use stdlib "embed" package (#200)

This should make it easier to include GLAuth in other projects using `go get`

* 'Airgapping' web assets for security and preventing breakage (#227)

* v2 -- V2 hierarchy (#228)

* Search refactoring, tree traversal and scope correctness (#229)

Search refactoring, tree traversal and scope correctness.

* Feature/multi cfg (#233)

* Feature/custom attributes (#240)

* Makefiles: platform releases (#241)

* Feature/release script (#242)

* Bug/fix docker build in v2 (#244)

* Fix #246 and #252

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Co-authored-by: Zhou Wenzong <wenzong@users.noreply.github.com>
Co-authored-by: Jairo Llopis <Yajo@users.noreply.github.com>
Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz>
Co-authored-by: Nicolai Willems <172633+nwillems@users.noreply.github.com>
Co-authored-by: Thibault Soubiran <soubi.thibault@gmail.com>
Co-authored-by: Clement JACOB <clems71@gmail.com>
Co-authored-by: Lutz Horn <code@lhorn.de>
Co-authored-by: Márk Sági-Kazár <sagikazarmark@users.noreply.github.com>
Co-authored-by: Peter Heise <pheise@koprolalie.com>
Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>
2022-02-27 16:35:57 -08:00
Chris F Ravenscroft e26219c2f4 Dev (#245) -- Docker fix for V2 hierarchy
Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Co-authored-by: Zhou Wenzong <wenzong@users.noreply.github.com>
Co-authored-by: Jairo Llopis <Yajo@users.noreply.github.com>
Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz>
Co-authored-by: Nicolai Willems <172633+nwillems@users.noreply.github.com>
Co-authored-by: Thibault Soubiran <soubi.thibault@gmail.com>
Co-authored-by: Clement JACOB <clems71@gmail.com>
Co-authored-by: Lutz Horn <code@lhorn.de>
Co-authored-by: Márk Sági-Kazár <sagikazarmark@users.noreply.github.com>
Co-authored-by: Peter Heise <pheise@koprolalie.com>
Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>
2021-11-26 17:42:20 -08:00
Chris F Ravenscroft e7195ed983 Dev to Master 2.1.0 RC1 (#243)
Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Co-authored-by: Zhou Wenzong <wenzong@users.noreply.github.com>
Co-authored-by: Jairo Llopis <Yajo@users.noreply.github.com>
Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz>
Co-authored-by: Nicolai Willems <172633+nwillems@users.noreply.github.com>
Co-authored-by: Thibault Soubiran <soubi.thibault@gmail.com>
Co-authored-by: Clement JACOB <clems71@gmail.com>
Co-authored-by: Lutz Horn <code@lhorn.de>
Co-authored-by: Márk Sági-Kazár <sagikazarmark@users.noreply.github.com>
Co-authored-by: Peter Heise <pheise@koprolalie.com>
Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>
2021-11-25 17:56:59 -08:00
Chris F Ravenscroft be26675ef8 Revert "Capabilities." from Master. (#211)
This reverts commit 8ef3e2b8d8.
2021-08-23 00:44:23 -07:00
Chris F Ravenscroft 8ef3e2b8d8 Capabilities. (#210) 2021-08-23 00:42:21 -07:00
Chris F Ravenscroft ccb839f0a5 Create and push docker image using podman (#209) 2021-08-22 13:46:49 -07:00
Jörn Friedrich Dreyer 1b92cfbae6 v2.0.0 RC1 (#125)
Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Co-authored-by: Zhou Wenzong <wenzong@users.noreply.github.com>
Co-authored-by: Jairo Llopis <Yajo@users.noreply.github.com>
Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz>
Co-authored-by: Nicolai Willems <172633+nwillems@users.noreply.github.com>
Co-authored-by: Thibault Soubiran <soubi.thibault@gmail.com>
Co-authored-by: Clement JACOB <clems71@gmail.com>
Co-authored-by: Lutz Horn <code@lhorn.de>
Co-authored-by: Márk Sági-Kazár <sagikazarmark@users.noreply.github.com>
Co-authored-by: Peter Heise <pheise@koprolalie.com>
Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: Chris F Ravenscroft <chris@voilaweb.com>
Co-authored-by: fanlix <fanlix@gmail.com>
2021-08-12 21:41:58 -07:00
Ben Yanke 5681dc4d02 Remove byanke's donation fields 2021-02-27 07:51:46 -06:00
226 changed files with 22550 additions and 867 deletions
+2 -2
View File
@@ -14,9 +14,9 @@ new functionality, consider adding tests covering your feature.
## CI
Each push to a branch connected to a PR will be run through GLAuth's
CI system. Please use these to your advantage. In particular, the Travis
CI system. Please use these to your advantage. In particular, the Github Actions
integration tests rely on the LDAP queries returning with a set result,
so if your changes will change the output, Travis will likely fail.
so if your changes will change the output, CI will likely fail.
To update, run `make fast && make updatetest && make test`. This will
delete the output snapshots provided and make new ones. You can then
+39
View File
@@ -0,0 +1,39 @@
name: ci
on:
pull_request:
push:
branches:
- dev
jobs:
build:
runs-on: ubuntu-20.04
strategy:
matrix:
go-version: [1.16.x, 1.17.x]
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Updating packages database
run: sudo apt-get -qq update
- name: Install CLI Dependencies
run: sudo apt-get install -y ldap-utils oathtool
- name: Checkout code
uses: actions/checkout@v2
- name: Fetch Go dependencies
working-directory: ./v2
run: go get -t -v ./...
- name: Fail if a .go file hasn't been formatted with 'go fmt'
working-directory: ./v2
run: diff -u <(echo -n) <(gofmt -d ./)
- name: Full compile test
working-directory: ./v2
run: make all
- name: Output hashes to the build log
working-directory: ./v2
run: cat bin/*/glauth.sha256
- name: Integration test with compiled binary
working-directory: ./v2
run: make test
+44
View File
@@ -0,0 +1,44 @@
name: docker
on:
pull_request:
push:
branches:
- master
release:
types:
- created
- edited
jobs:
build-push:
runs-on: ubuntu-latest
strategy:
fail-fast: false
env:
BUILDX_VERSION: v0.4.1
DOCKER_CLI_EXPERIMENTAL: enabled
steps:
- uses: actions/checkout@v2
- name: set up multiarch builder
working-directory: ./v2
run: |
set -ex
curl -sSL https://get.docker.com | bash
mkdir -p ~/.docker/cli-plugins
curl -sSLo ~/.docker/cli-plugins/buildx https://github.com/docker/buildx/releases/download/$BUILDX_VERSION/buildx-$BUILDX_VERSION.linux-amd64
chmod +x ~/.docker/cli-plugins/buildx
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker buildx create --name multiarch
docker buildx use multiarch
docker buildx inspect --bootstrap
curl -sSL https://gist.githubusercontent.com/ArturKlauser/0f0293c62f5626df0261ac994d8a46af/raw/33ef25b8a93f15310f0a4013deca3f8d20fc00a7/check-qemu-binfmt.sh | bash
- name: log in github registry
run: docker login --username "${{ github.actor }}" --password "${{ secrets.GITHUB_TOKEN }}" docker.pkg.github.com
- name: build multiarch images
working-directory: ./v2
run: docker buildx build -f Dockerfile-standalone -t docker.pkg.github.com/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]'):$(echo ${{ github.ref }} | tr / -) --platform linux/amd64,linux/arm64,linux/arm/v7 .
- name: push multiarch images
working-directory: ./v2
if: github.event.release.tag_name
run: docker buildx build --push -f Dockerfile-standalone -t docker.pkg.github.com/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]'):${{ github.event.release.tag_name }} --platform linux/amd64,linux/arm64,linux/arm/v7 .
+3 -5
View File
@@ -10,11 +10,9 @@ certs/
# Built binaries
bin
pkg/assets/bindata.go
.*.swp
# Ignoring travis CLI tool logs
*travis.log
certs/
certs/
# Local files
local/
-79
View File
@@ -1,79 +0,0 @@
language: go
# USE THIS alongside makefile tag detection: $TRAVIS_TAG
# if you change the version here, also update the travis deploy condition
go:
- 1.11.x # older version
- 1.12.x # last version
- 1.13.x # current version
# Speed up git clones
git:
depth: 1
# Anything in before_script that returns a nonzero exit code will
# flunk the build and immediately stop. It's sorta like having
# set -e enabled in bash.
before_script:
- travis_retry sudo apt-get -qq update && sudo apt-get install -y ldap-utils oathtool # need this for integration tests
- travis_retry sudo apt-get -qq update && sudo apt-get install -y go-bindata # need this for makefile
- GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/) # All the .go files, excluding vendor/
#- travis_retry go get github.com/golang/lint/golint # Linter
#- travis_retry go get honnef.co/go/tools/cmd/megacheck # Badass static analyzer/linter
# - travis_retry go get github.com/fzipp/gocyclo # Not sure what this is
# Run bindata for assets
- make bindata
# Fetch dependencies
- travis_retry go get -t -v ./...
script:
- env # Dump the env to make debugging easier
- diff -u <(echo -n) <(gofmt -d ./) # Fail if a .go file hasn't been formatted with 'go fmt'
# - go vet ./... " # go vet is the official Go static analyzer
# - go test -v -race ./... # Run all the tests with the race detector enabled
# - megacheck ./... # "go vet on steroids" + linter
# - gocyclo -over 19 $GO_FILES # forbid code with huge functions
# - golint -set_exit_status $(go list ./...) # one last linter
# Unit tests
# - tbd - use 'go test' here when we have unit tests written. example command below. This will generate output for codecov
# - go test -race -coverprofile=coverage.txt -covermode=atomic
# Full compile test
# - go build -o ./glauth glauth.go bindata.go ldapbackend.go webapi.go configbackend.go
- make all
# Output hashes to the build log
- cat bin/*.sha256
# Integration test with compiled binary
- make test
# after_success:
# - bash <(curl -s https://codecov.io/bash)
deploy:
provider: releases
api_key:
secure: lPI5FftcQB1eHihN9daeFOT97vZcfq2w0hRb6amC6U4P+K7Ih23R9NmWhDuvGM9CPLaAqgOKiHWIUnP4q9EeBeAUMgg6FI2syr/0nDYz5cHqZMMPWFgeDvn+HqfMdFIVeiKT80+uGY/JNsMI8lsJ0eILEp0GpyIZkoAewCEyxJ/fcLM5htGnQIQ8mQ+2ZXflvPofv6/RPHgu3ryC0icJFVMREIivp2QIZBpFnawnI+OE8ZzxD5R05gfg3IJtYtc/4diX4RPyqVTCqljU/x7/g9KgNsnaLGEz37pBUS0IQcq4ITX/cm4y5LLWPV6JugxjyTq1SXKJnHEgfEFk5/hPM7Q1DMZJ5tGpb2B4aSMwB6YHwvVW83ih96Vw8ZgIrxWT9LyF4n9AHakLhDIiJieDXa3dvG7U5oTxlSP/RJkXlMZEJ3lS/1RZ9vvQ0EOEMAO6ePQzSZ4EM/UShFalBI+fqDtzqxFGeeCWw6lwiKLF2GF7enT8+wLsWOMI43JM5uPAq2g0KFMtJEEcK08IFraObvK5bcFH7snCDtsAaLyjdkVIIBteNtfDsXQ8PMi4bQpLr5kpY68dkyuxDr4IpL1GKqlpe2LbjkzsGrq5wifcHMFmUkEfY/6/ELFc9fL+1ZnMAe7YiW41AvX+hrgVmc3M8zp1kVvy2oERV4LfV1vfaEU=
file_glob: true
file: bin/*
draft: true
name: "DRAFT RELEASE: $TRAVIS_TAG"
skip_cleanup: true
on:
branch: master
tags: true
condition: $TRAVIS_GO_VERSION =~ ^1\.13
repo: glauth/glauth
-59
View File
@@ -1,59 +0,0 @@
#################
# Build Step
#################
FROM golang:alpine as build
MAINTAINER Ben Yanke <ben@benyanke.com>
# Setup work env
RUN mkdir /app /tmp/gocode
ADD . /app/
WORKDIR /app
# Required envs for GO
ENV GOPATH=/tmp/gocode
ENV GOOS=linux
ENV GOARCH=amd64
ENV CGO_ENABLED=0
# Only needed for alpine builds
RUN apk add --no-cache git bzr make
# Install deps
RUN go get -d -v ./...
# Run go-bindata to embed data for API
RUN go get -u github.com/jteeuwen/go-bindata/... && $GOPATH/bin/go-bindata -pkg=assets -o=pkg/assets/bindata.go assets && gofmt -w pkg/assets/bindata.go
# Build and copy final result
RUN make linux64 && cp ./bin/glauth64 /app/glauth
#################
# Run Step
#################
FROM alpine as run
MAINTAINER Ben Yanke <ben@benyanke.com>
# Copies a sample config to be used if a volume isn't mounted with user's config
ADD sample-simple.cfg /app/config/config.cfg
# Copy binary from build container
COPY --from=build /app/glauth /app/glauth
# Copy docker specific scripts from build container
COPY --from=build /app/scripts/docker/start.sh /app/docker/
COPY --from=build /app/scripts/docker/default-config.cfg /app/docker/
# Install ldapsearch for container health checks, then ensure ldapsearch is installed
RUN apk update && apk add --no-cache dumb-init openldap-clients && which ldapsearch && rm -rf /var/cache/apk/*
# Install init
# Expose web and LDAP ports
EXPOSE 389 636 5555
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/bin/sh", "/app/docker/start.sh"]
+64
View File
@@ -0,0 +1,64 @@
#################
# Build Step
#################
FROM golang:buster as build
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
# Setup work env
RUN mkdir /app /tmp/gocode
ADD . /app/
WORKDIR /app
# Required envs for GO
ENV GOPATH=/tmp/gocode
ENV GOOS=linux
ENV GOARCH=amd64
ENV GO_ENABLED=1
# Only needed for alpine builds // also: busybox
RUN apt -qq update && apt -qq install -y git make build-essential busybox-static dumb-init
# Install deps
RUN go get -d -v ./...
# Build and copy final result
RUN uname -a
RUN uname -m
RUN bash -c '[[ $(uname -m) == x86_64 ]] && { make linux64 && cp ./bin/glauth64 /app/glauth; export PLUGIN_OS=linux PLUGIN_ARCH=amd64; make bin/sqlite.so bin/postgres.so bin/mysql.so && cp ./bin/*.so /app/; } || { true; }'
RUN bash -c '[[ $(uname -m) == aarch64 ]] && { make linuxarm64 && cp ./bin/glauth-arm64 /app/glauth; export PLUGIN_OS=linux PLUGIN_ARCH=arm64; make bin/sqlite.so bin/postgres.so bin/mysql.so && cp ./bin/*.so /app/; } || { true; }'
# Check glauth works
RUN /app/glauth --version
#################
# Run Step
#################
FROM gcr.io/distroless/base-debian10 as run
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
# Copy binary from build container
COPY --from=build /app/glauth /app/glauth
COPY --from=build /app/*.so /app/
# Copy docker specific scripts from build container
COPY --from=build /app/scripts/docker/start-plugins.sh /app/docker/
COPY --from=build /app/scripts/docker/default-config-plugins.cfg /app/docker/
COPY --from=build /app/scripts/docker/gl.db /app/docker/
# Just what we need
COPY --from=build /usr/bin/dumb-init /usr/bin/dumb-init
COPY --from=build /bin/busybox /bin/sh
COPY --from=build /bin/busybox /bin/ln
COPY --from=build /bin/busybox /bin/rm
# Debug env: COPY --from=build /bin/busybox /bin/busybox
RUN ln /bin/sh /usr/bin/cp && ln /bin/sh /usr/bin/mkdir && rm /bin/ln /bin/rm
# Install init
# Expose web and LDAP ports
EXPOSE 389 636 5555
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/bin/sh", "/app/docker/start-plugins.sh"]
+61
View File
@@ -0,0 +1,61 @@
#################
# Build Step
#################
FROM golang:alpine as build
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
# Setup work env
RUN mkdir /app /tmp/gocode
ADD . /app/
WORKDIR /app
# Required envs for GO
ENV GOPATH=/tmp/gocode
ENV GOOS=linux
ENV GOARCH=amd64
ENV CGO_ENABLED=0
# Only needed for alpine builds // also: busybox
RUN apk add --no-cache git make busybox-static dumb-init
# Install deps
RUN go get -d -v ./...
# Build and copy final result -- wrong file BTW
RUN uname -a
RUN if [ $(uname -m) == x86_64 ]; then make linux64 && cp ./bin/glauth64 /app/glauth; fi
RUN if [ $(uname -m) == aarch64 ]; then make linuxarm64 && cp ./bin/glauth-arm64 /app/glauth; fi
RUN if [ $(uname -m) == armv7l ]; then make linuxarm32 && cp ./bin/glauth-arm32 /app/glauth; fi
# Check glauth works
RUN /app/glauth --version
#################
# Run Step
#################
FROM gcr.io/distroless/base-debian10 as run
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
# Copy binary from build container
COPY --from=build /app/glauth /app/glauth
# Copy docker specific scripts from build container
COPY --from=build /app/scripts/docker/start-standalone.sh /app/docker/
COPY --from=build /app/scripts/docker/default-config-standalone.cfg /app/docker/
# Just what we need
COPY --from=build /usr/bin/dumb-init /usr/bin/dumb-init
COPY --from=build /bin/busybox.static /bin/sh
COPY --from=build /bin/busybox.static /bin/ln
COPY --from=build /bin/busybox.static /bin/rm
RUN ln /bin/sh /usr/bin/cp && ln /bin/sh /usr/bin/mkdir && rm /bin/ln /bin/rm
# Install init
# Expose web and LDAP ports
EXPOSE 389 636 5555
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/bin/sh", "/app/docker/start-standalone.sh"]
+19 -19
View File
@@ -1,8 +1,10 @@
VERSION=$(shell bin/glauth64 --version)
# This is the V1 Makefile.
# If you need to build a V1 target, remove the next line.
exit 0
GIT_COMMIT=$(shell git rev-list -1 HEAD )
BUILD_TIME=$(shell date --utc +%Y%m%d_%H%M%SZ)
GIT_CLEAN=$(shell git status | grep -E "working (tree|directory) clean" | wc -l)
BUILD_TIME=$(shell date -u +%Y%m%d_%H%M%SZ)
GIT_CLEAN=$(shell git status | grep -E "working (tree|directory) clean" | wc -l | sed 's/^[ ]*//')
# Last git tag
LAST_GIT_TAG=$(shell git describe --abbrev=0 --tags 2> /dev/null)
@@ -14,31 +16,34 @@ GIT_IS_TAG_COMMIT=$(shell git describe --abbrev=0 --tags > /dev/null 2> /dev/nul
GIT_BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
# Build variables
BUILD_VARS=-X main.GitCommit=${GIT_COMMIT} -X main.GitBranch=${GIT_BRANCH} -X main.BuildTime=${BUILD_TIME} -X main.GitClean=${GIT_CLEAN} -X main.LastGitTag=${LAST_GIT_TAG} -X main.GitTagIsCommit=${GIT_IS_TAG_COMMIT}
BUILD_VARS=-s -w -X main.GitCommit=${GIT_COMMIT} -X main.GitBranch=${GIT_BRANCH} -X main.BuildTime=${BUILD_TIME} -X main.GitClean=${GIT_CLEAN} -X main.LastGitTag=${LAST_GIT_TAG} -X main.GitTagIsCommit=${GIT_IS_TAG_COMMIT}
BUILD_FILES=glauth.go
TRIM_FLAGS=-gcflags "all=-trimpath=${PWD}" -asmflags "all=-trimpath=${PWD}"
# Plugins
include pkg/plugins/Makefile
#####################
# High level commands
#####################
# Build and run - used for development
run: setup devrun cleanup
run: setup devrun
# Run the integration test on linux64 (eventually allow the binary to be set)
test: runtest
# Run build process for all binaries
all: setup binaries verify cleanup
all: setup binaries verify
# Run build process for only linux64
fast: setup linux64 verify cleanup
fast: setup linux64 verify
# list of binary formats to build
binaries: linux32 linux64 linuxarm32 linuxarm64 darwin64 win32 win64
# Setup commands to always run
setup: getdeps bindata format
setup: getdeps format
#####################
# Subcommands
@@ -46,28 +51,20 @@ setup: getdeps bindata format
# Run integration test
runtest:
./scripts/travis/integration-test.sh cleanup
./scripts/ci/integration-test.sh cleanup
# Get all dependencies
getdeps:
go get -d ./...
updatetest:
./scripts/travis/integration-test.sh
bindata:
go get -u github.com/jteeuwen/go-bindata/... && ${GOPATH}/bin/go-bindata -pkg=assets -o=pkg/assets/bindata.go assets && gofmt -w pkg/assets/bindata.go
cleanup:
rm pkg/assets/bindata.go
./scripts/ci/integration-test.sh
format:
go fmt
devrun:
go run ${BUILD_FILES} -c glauth.cfg
go run ${BUILD_FILES} -c sample-simple.cfg
linux32:
GOOS=linux GOARCH=386 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth32 ${BUILD_FILES} && cd bin && sha256sum glauth32 > glauth32.sha256
@@ -84,6 +81,9 @@ linuxarm64:
darwin64:
GOOS=darwin GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauthOSX ${BUILD_FILES} && cd bin && sha256sum glauthOSX > glauthOSX.sha256
darwinarm64:
GOOS=darwin GOARCH=arm64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauthOSX-arm64 ${BUILD_FILES} && cd bin && sha256sum glauthOSX-arm64 > glauthOSX-arm64.sha256
win32:
GOOS=windows GOARCH=386 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-win32 ${BUILD_FILES} && cd bin && sha256sum glauth-win32 > glauth-win32.sha256
+160 -35
View File
@@ -1,26 +1,29 @@
# GLAuth: LDAP authentication server for developers
Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ configurable backends.
[![Travis Build - Master](https://img.shields.io/travis/glauth/glauth.svg)](https://travis-ci.org/glauth/glauth)
[![Last Commit](https://img.shields.io/github/last-commit/glauth/glauth.svg)](https://github.com/glauth/glauth/graphs/commit-activity)
[![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)
[![](https://img.shields.io/docker/build/glauth/glauth.svg)](https://hub.docker.com/r/glauth/glauth/)
[![DockerHub Image Size](https://img.shields.io/microbadger/image-size/glauth/glauth.svg)](https://hub.docker.com/r/glauth/glauth/)
![GitHub all releases](https://img.shields.io/github/downloads/glauth/glauth/total)
![Docker pulls](https://badgen.net/docker/pulls/glauth/glauth)
[![Maintainability](https://img.shields.io/codeclimate/maintainability/glauth/glauth.svg)](https://codeclimate.com/github/glauth/glauth/maintainability)
[![Test Coverage](https://img.shields.io/codeclimate/coverage/glauth/glauth.svg)](https://codeclimate.com/github/glauth/glauth/test_coverage)
![Docker Automated build](https://img.shields.io/docker/automated/glauth/glauth)
[![Donate via Paypal](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://paypal.me/benyanke)
![GitHub last commit (branch)](https://img.shields.io/github/last-commit/glauth/glauth/dev)
![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.
* Lightweight alternative to OpenLDAP and Active Directory for development, or a homelab.
* Store your user directory in a local file, S3 or proxy to existing LDAP servers.
* Store your user directory in a file, local or in S3; SQL database; or proxy to existing LDAP servers.
* Two Factor Authentication (transparent to applications)
* Multiple backends can be chained to inject features
Use it to centralize account management across your Linux servers, your OSX machines, and your support applications (Jenkins, Apache/Nginx, Graylog2, and many more!).
### Contributing
Please base all PRs on [dev](https://github.com/nmcclain/glauth/tree/dev), not master.
- 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
### 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!*
@@ -28,11 +31,9 @@ This quickstart is a great way to try out GLAuth in a non-production environment
1. Download a precompiled binary from the [releases](https://github.com/glauth/glauth/releases) page.
2. Download the [example config file](https://github.com/glauth/glauth/blob/master/sample-simple.cfg).
3. Start the GLAuth server, referencing the path to the desired config file with `-c`.
- `sudo ./glauth64 -c sample-simple.cfg`
- `./glauth64 -c sample-simple.cfg`
4. Test with traditional LDAP tools
- For example: `ldapsearch -LLL -H ldap://localhost:389 -D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com -w mysecret -x -bdc=glauth,dc=com cn=hackers`
- For example: `ldapsearch -LLL -H ldap://localhost:3893 -D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com -w mysecret -x -bdc=glauth,dc=com cn=hackers`
### Make Commands
@@ -44,6 +45,8 @@ Note - makefile uses git data to inject build-time variables. For best results,
*make run* - wrapper for the 'go run' command, setting up the needed tooling
*make plugins* - build additional (SQL) plugin backends
*make test* - run the integration test on linux64 binary
### Usage:
@@ -60,6 +63,10 @@ Options:
-K <aws_key_id> AWS Key ID.
-S <aws_secret_key> AWS Secret Key.
-r <aws_region> AWS Region [default: us-east-1].
--ldap <address> Listen address for the LDAP server.
--ldaps <address> Listen address for the LDAPS server.
--ldaps-cert <cert-file> Path to cert file for the LDAPS server.
--ldaps-key <key-file> Path to key file for the LDAPS server.
-h, --help Show this screen.
--version Show version.
```
@@ -76,15 +83,21 @@ Here's a sample config wth hardcoded users and groups:
baseDN = "dc=glauth,dc=com"
[[users]]
name = "hackers"
unixid = 5001
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
sshkeys = [ "ssh-dss AAAAB3..." ]
[[users]]
name = "uberhackers"
uidnumber = 5006
primarygroup = 5501
passbcrypt = "243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
[[groups]]
name = "superheros"
unixid = 5501
gidnumber = 5501
```
To create the password SHA hash, use this command: `echo -n "mysecret" | openssl dgst -sha256`
Instead of a local configuration file, GLAuth can fetch its configuration from S3. This is an easy way to ensure redundant GLAuth servers are always in-sync.
```unix
glauth -c s3://bucketname/glauth.cfg
@@ -96,6 +109,26 @@ In order to use S3, you must set your AWS credentials. Either:
More configuration options are documented here: https://github.com/glauth/glauth/blob/master/sample-simple.cfg
### Chaining backends
This can be used, for instance, to inject support for Two Factor Authentication for backends that do not support the feature natively:
```
[[backends]]
datastore = "ldap"
servers = ["ldap:s//localhost:390"]
[[backends]]
datastore = "config"
...
[[users]]
name = "hackers"
otpsecret = "................"
```
### Required Fields
* Name
* The user's username
@@ -139,6 +172,10 @@ More configuration options are documented here: https://github.com/glauth/glauth
* Specify OTP secret used to validate OTP passcode
* Example: 3hnvnk4ycv44glzigd6s25j4dougs3rk
* default = blank
* passappbcrypt
* Specify an array of app passwords which can also succesfully bind - these bypass the OTP check. Hash the same way as password.
* Example: ["c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa","4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88"]
* default = blank
* passappsha256
* Specify an array of app passwords which can also succesfully bind - these bypass the OTP check. Hash the same way as password.
* Example: ["c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa","4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88"]
@@ -148,15 +185,50 @@ More configuration options are documented here: https://github.com/glauth/glauth
* Example: cccjgjgkhcbb
* default = blank
### Capabilities
Introduced in 2.1.0, this feature continues improving the intrinsic security model of GLAuth.
While some level of access control is already enforced when using an LDAP backend, Capabilities are now part of the Config and Database backends.
Currently, one capability is recognized: "search" -- here is how to configure it in a Config yaml file:
```
...
[behaviors]
# Ignore all capabilities restrictions, for instance allowing every user to perform a search
IgnoreCapabilities = false
...
[[users]]
name = "hackers"
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
[[users.capabilities]]
action = "search"
object = "ou=someotherdn,dc=glauth,dc=com"
...
[[users]]
name = "serviceuser"
[[users.capabilities]]
action = "search"
object = "*"
...
```
For backward compatibility, you can set `IgnoreCapabilities` to "true"
If you are using a Database backend, check the plugins README for configuration information.
### OpenSSH keys:
GLAuth can store a user's SSH authorized keys. Add one or more keys per user as shown above, then setup the goklp helper: https://github.com/appliedtrust/goklp
### Strong Passwords
If you are currently using sha256 passwords (`passsha256` or `passappsha256`) moving to strong, salted paswords is recommended. Simply switch to `passbcrypt` and/or `passappbcrypt` password types. Currently (2021) 2<sup>12</sup> is a reasonably good value, depending our your server's CPU.
### Two Factor Authentication
GLAuth can be configured to accept OTP tokens as appended to a users password. Support is added for both **TOTP tokens** (often known by it's most prominent implementation, "Google Authenticator") and **Yubikey OTP tokens**.
When using 2FA, append the 2FA code to the end of the password when authenticating. For example, if your password is "monkey" and your otp is "123456", enter "monkey123456" as your password.
When using 2FA, append the 2FA code to the end of the password when authenticating. For example, if your password is "monkey" and your otp is "123456", enter "monkey123456" as your password.
#### TOTP Configuration
To enable TOTP authentication on a user, you can use a tool [like this](https://freeotp.github.io/qrcode.html) to generate a QR code (pick 'Timeout' and optionally let it generate a random secret for you), which can be scanned and used with the [Google Authenticator](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en) app. To enable TOTP authentication, configure the `otpsecret` for the user with the TOTP secret.
@@ -169,7 +241,7 @@ However, app passwords can be used without OTP as well.
#### Yubikey Configuration
For Yubikey OTP token authentication, first [configure your Yubikey](https://www.yubico.com/products/services-software/personalization-tools/yubikey-otp/). After this, make sure to [request a `Client ID` and `Secret key` pair](https://upgrade.yubico.com/getapikey/).
Now configure the the `yubikeyclientid` and `yubikeysecret` fields in the general section in the configuration file.
Now configure the `yubikeyclientid` and `yubikeysecret` fields in the general section in the configuration file.
To enable Yubikey OTP authentication for a user, you must specify their Yubikey ID on the users `yubikey` field. The Yubikey ID is the first 12 characters of the Yubikey OTP, as explained in the below chart.
@@ -194,27 +266,80 @@ Any of the architectures above will work for production. Just remember:
A small note about other architectures: while I expect the code is, for the most part, system-independent, there is not a good (and free) CI system which can be easily used to continuously test releases on ARM, BSD, Linux-32bit, and Windows. As such, all of the non-linux-64bit packages are provided as is. The extent of testing on these packages consists solely of cross-compiling for these architectures from a linux 64 bit system.
We will accept PRs which fix bugs on these platforms, but be aware these binaries will not be tested regularly, and instead are provided for the convenience of those who feel comfortable with this.
### Building:
You'll need go-bindata to build GLAuth. Then use the Makefile.
```unix
go get github.com/jteeuwen/go-bindata/...
make all
# Logging
- using logr with increasing verbosity
- 0 you always want to see this
- 1 common logging that you might *possibly* want to turn off (error)
- 2 warn
- 3 notice
- 4 info
- 6 debug
- 8 trace
- 10 I would like to performance test your log collection stack
- errors really are errors that cannot be handled or returned
- returning a proper LDAP error code is handling an error
# Testing
Of course, a core set of tests is being run by Github Actions CI. However, when developing new features/refactoring, a more comprehensive regression testing suite is needed.
You can run `go test` to execute the tests found in `glauth_test.go` -- better, if it is installed, you can run [goconvey](https://github.com/smartystreets/goconvey)
Since some tests cover TOTP, you will first need to install `oathtool` in your environment.
You also must create a symbolink link called `glauth` to make it easy for the test framework to find the executable. For instance:
```
cd bin
ln -s glauth64 glauth
```
In order to test GLAuth against an LDAP backend, you will need docker. Run this command:
```
docker run \
--rm \
-d \
-p 389:389 \
--name openldap-service \
--hostname ldap-service \
--env LDAP_ORGANISATION="GLauth" \
--env LDAP_DOMAIN="glauth.com" \
--env LDAP_ADMIN_PASSWORD="password" \
--env LDAP_CONFIG_PASSWORD="password" \
--env LDAP_BASE_DN="dc=glauth,dc=com" \
-v $PWD/misc/openldap/config:/etc/ldap/slapd.d \
-v $PWD/misc/openldap/db:/var/lib/ldap \
osixia/openldap:latest
```
Refer to [this page](https://github.com/glauth/glauth/wiki/Quick-openldap-setup-to-test-LDAP-backend) for a somewhat more in-depth overview of testing with OpenLDAP.
# Compatibility
While our stated goal for GLAuth is to provide the simplest possible authentication server, we keep finding an increasing number of client appliances that are asking fairly "existential" questions of the server. We have been working on providing answers these clients will find satisfactory.
### Root DSE
RFC 4512: "An LDAP server SHALL provide information about itself and other information that is specific to each server. This is represented as a group of attributes located in the root DSE"
Test: `ldapsearch -LLL -H ldap://localhost:3893 -D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com -w mysecret -x -s base "(objectclass=*)"`
### Subschema Discovery
RFC 4512: "To read schema attributes from the subschema (sub)entry, clients MUST issue a Search operation [RFC4511] where baseObject is the DN of the subschema (sub)entry..."
Test: `ldapsearch -LLL -o ldif-wrap=no -H ldap://localhost:3893 -D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com -w mysecret -x -bcn=schema -s base`
By default, this query will return a very minimal schema (~5 objects) -- you can ask GLAuth to return more comprehensive schemas by unpacking, in the `schema/` directory, the OpenLDAP or FreeIPA schema archives found in the `assets/` directory.
### LDAP Backend: "1.1" attribute
RFC 4511: "A list containing only the OID "1.1" indicates that no attributes are to be returned."
## Stargazers over time
[![Stargazers over time](https://starcharts.herokuapp.com/glauth/glauth.svg)](https://starcharts.herokuapp.com/glauth/glauth)
[![Stargazers over time](https://starchart.cc/glauth/glauth.svg)](https://starchart.cc/glauth/glauth)
### Support
Support the ongoing development of GLAuth!
**Paypal**
[![Donate via Paypal](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://paypal.me/benyanke)
**Bitcoin Address**
39z2Zkoc24LsuiqCQNFe7QrX4da3mzbGjK
+493 -48
View File
@@ -1,22 +1,32 @@
package main
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"reflect"
"strings"
"time"
"github.com/BurntSushi/toml"
"github.com/GeertJohan/yubigo"
"github.com/arl/statsviz"
"github.com/davecgh/go-spew/spew"
docopt "github.com/docopt/docopt-go"
"github.com/fsnotify/fsnotify"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/frontend"
gologgingr "github.com/glauth/glauth/pkg/gologgingr"
"github.com/glauth/glauth/pkg/server"
"github.com/glauth/glauth/pkg/stats"
"github.com/go-logr/logr"
"github.com/hydronica/toml"
"github.com/jinzhu/copier"
logging "github.com/op/go-logging"
"gopkg.in/amz.v1/aws"
"gopkg.in/amz.v1/s3"
"gopkg.in/amz.v3/aws"
"gopkg.in/amz.v3/s3"
)
// Set with buildtime vars
@@ -41,12 +51,16 @@ Options:
-K <aws_key_id> AWS Key ID.
-S <aws_secret_key> AWS Secret Key.
-r <aws_region> AWS Region [default: us-east-1].
--ldap <address> Listen address for the LDAP server.
--ldaps <address> Listen address for the LDAPS server.
--ldaps-cert <cert-file> Path to cert file for the LDAPS server.
--ldaps-key <key-file> Path to key file for the LDAPS server.
-h, --help Show this screen.
--version Show version.
`
var (
log *logging.Logger
log logr.Logger
args map[string]interface{}
stderr *logging.LogBackend
yubiAuth *yubigo.YubiAuth
@@ -98,13 +112,15 @@ func getVersionString() string {
func main() {
stderr = initLogging()
log.Debug("AP start")
log.V(6).Info("AP start")
if err := parseArgs(); err != nil {
log.Fatalf("Could not parse command-line arguments", err.Error())
log.Error(err, "Could not parse command-line arguments")
os.Exit(1)
}
if err := doConfig(); err != nil {
log.Fatalf("Configuration file error: %s", err.Error())
log.Error(err, "Configuration file error")
os.Exit(1)
}
startService()
@@ -116,49 +132,106 @@ func startService() {
// web API
if activeConfig.API.Enabled {
log.Debug("Web API enabled")
go frontend.RunAPI(log, activeConfig)
log.V(6).Info("Web API enabled")
if activeConfig.API.Internals {
statsviz.Register(
http.DefaultServeMux,
statsviz.Root("/internals"),
statsviz.SendFrequency(1000*time.Millisecond),
)
}
go frontend.RunAPI(
frontend.Logger(log),
frontend.Config(&activeConfig.API),
)
}
startConfigWatcher()
s, err := server.NewServer(log, activeConfig)
s, err := server.NewServer(
server.Logger(log),
server.Config(activeConfig),
)
if err != nil {
log.Fatalf("Could not start server: %s", err.Error())
log.Error(err, "Could not create server")
os.Exit(1)
}
s.ListenAndServe()
log.Critical("AP exit")
if activeConfig.LDAP.Enabled {
// Don't block if also starting a LDAPS server afterwards
shouldBlock := !activeConfig.LDAPS.Enabled
if shouldBlock {
if err := s.ListenAndServe(); err != nil {
log.Error(err, "Could not start LDAP server")
os.Exit(1)
}
} else {
go func() {
if err := s.ListenAndServe(); err != nil {
log.Error(err, "Could not start LDAP server")
os.Exit(1)
}
}()
}
}
if activeConfig.LDAPS.Enabled {
// Always block here
if err := s.ListenAndServeTLS(); err != nil {
log.Error(err, "Could not start LDAPS server")
os.Exit(1)
}
}
log.V(0).Info("AP exit")
os.Exit(1)
}
func startConfigWatcher() {
configFileLocation := getConfigLocation()
if strings.HasPrefix(configFileLocation, "s3://") {
if !activeConfig.WatchConfig || strings.HasPrefix(configFileLocation, "s3://") {
return
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatalf("Could not start config-watcher: %s", err.Error())
log.Error(err, "Could not start config-watcher")
return
}
ticker := time.NewTicker(1 * time.Second)
go func() {
isChanged, isRemoved := false, false
for {
select {
case event := <-watcher.Events:
if activeConfig.WatchConfig {
if event.Op.String() == "WRITE" {
if err := doConfig(); err != nil {
log.Warningf("Could not reload config: %sHolding on to old config", err.Error())
} else {
log.Notice("Config was reloaded")
}
}
log.V(6).Info("watcher got event", "e", event.Op.String())
if event.Op&fsnotify.Write == fsnotify.Write {
isChanged = true
} else if event.Op&fsnotify.Remove == fsnotify.Remove { // vim edit file with rename/remove
isChanged, isRemoved = true, true
}
case err := <-watcher.Errors:
if activeConfig.WatchConfig {
log.Info("Error!", err)
log.Error(err, "Error!")
case <-ticker.C:
// wakeup, try finding removed config
}
if _, err := os.Stat(configFileLocation); !os.IsNotExist(err) && (isRemoved || isChanged) {
if isRemoved {
log.V(6).Info("rewatching config", "file", configFileLocation)
watcher.Add(configFileLocation) // overwrite
isChanged, isRemoved = true, false
}
if isChanged {
if err := doConfig(); err != nil {
log.V(2).Info("Could not reload config. Holding on to old config", "error", err.Error())
} else {
log.V(3).Info("Config was reloaded")
}
isChanged = false
}
}
}
@@ -186,9 +259,6 @@ func parseConfigFile(configFileLocation string) (*config.Config, error) {
// setup defaults
cfg.LDAP.Enabled = false
cfg.LDAPS.Enabled = true
cfg.Backend.NameFormat = "cn"
cfg.Backend.GroupFormat = "ou"
cfg.Backend.SSHKeyAttr = "sshPublicKey"
// parse the config file
if strings.HasPrefix(configFileLocation, "s3://") {
@@ -212,7 +282,10 @@ func parseConfigFile(configFileLocation string) (*config.Config, error) {
if len(parts) != 2 {
return &cfg, fmt.Errorf("Invalid S3 URL: %s", s3url)
}
b := s3.New(auth, region).Bucket(parts[0])
b, err := s3.New(auth, region).Bucket(parts[0])
if err != nil {
return &cfg, err
}
tomlData, err := b.Get(parts[1])
if err != nil {
return &cfg, err
@@ -221,15 +294,346 @@ func parseConfigFile(configFileLocation string) (*config.Config, error) {
return &cfg, err
}
} else { // local config file
if _, err := toml.DecodeFile(configFileLocation, &cfg); err != nil {
return &cfg, err
fInfo, err := os.Stat(configFileLocation)
if err != nil {
return &cfg, fmt.Errorf("Non-existent config path: %s", configFileLocation)
}
if fInfo.IsDir() { // multiple files in a directory
rawCfgStruct := make(map[string]interface{})
// To keep things simple, we are not going to use the default values built in Cfg
// so far (LDAP.Enabled, LDAPS.Enabled, etc) so do not forget to specify them!
/*
sourcebuf := new(bytes.Buffer)
err = toml.NewEncoder(sourcebuf).Encode(cfg)
if err != nil {
return &cfg, err
}
var initialRawCfgStruct interface{}
if err := toml.Unmarshal(sourcebuf.Bytes(), &initialRawCfgStruct); err != nil {
return &cfg, err
}
if err = mergeConfigs(&rawCfgStruct, initialRawCfgStruct); err != nil {
return &cfg, err
}
*/
files, _ := ioutil.ReadDir(configFileLocation)
for _, f := range files {
canonicalName := filepath.Join(configFileLocation, f.Name())
bs, _ := ioutil.ReadFile(canonicalName)
var curRawCfgStruct interface{}
if err := toml.Unmarshal(bs, &curRawCfgStruct); err != nil {
return &cfg, err
}
if err = mergeConfigs(&rawCfgStruct, curRawCfgStruct); err != nil {
return &cfg, err
}
}
destbuf := new(bytes.Buffer)
err = toml.NewEncoder(destbuf).Encode(rawCfgStruct)
if err != nil {
return &cfg, err
}
fmt.Println(destbuf.String())
merged := config.Config{}
if _, err := toml.Decode(destbuf.String(), &merged); err != nil {
return &cfg, err
}
return &merged, nil
} else {
if _, err := toml.DecodeFile(configFileLocation, &cfg); err != nil {
return &cfg, err
}
}
}
// Backward Compability
if cfg.Backend.Datastore != "" {
if cfg.Backends != nil {
return &cfg, fmt.Errorf("You cannot specify both [Backend] and [[Backends]] directives in the same configuration ")
} else {
cfg.Backends = append(cfg.Backends, cfg.Backend)
}
}
// Patch with default values where not specified
for i := range cfg.Backends {
if cfg.Backends[i].NameFormat == "" {
cfg.Backends[i].NameFormat = "cn"
}
if cfg.Backends[i].GroupFormat == "" {
cfg.Backends[i].GroupFormat = "ou"
}
if cfg.Backends[i].SSHKeyAttr == "" {
cfg.Backends[i].SSHKeyAttr = "sshPublicKey"
}
}
//
return &cfg, nil
}
func handleConfig(cfg config.Config) (*config.Config, error) {
func mergeConfigs(config1 interface{}, config2 interface{}) error {
var merger func(int, string, interface{}, interface{}) error
merger = func(depth int, keyName string, cfg1 interface{}, cfg2 interface{}) error {
//fmt.Println(strings.Repeat(" ", depth), "Handling element: ", keyName, " for: ", cfg2)
switch element2 := cfg2.(type) {
case map[string]interface{}:
//fmt.Println(strings.Repeat(" ", depth), " - A map")
element2, ok := cfg2.(map[string]interface{})
if !ok {
return fmt.Errorf("Config source: %s is not a map", keyName)
}
element1, ok := cfg1.(*map[string]interface{})
if !ok {
return fmt.Errorf("Config dest: %s is not a map", keyName)
}
for k, _ := range element2 {
//fmt.Println(strings.Repeat(" ", depth), " - key: ", k)
_, ok := (*element1)[k]
if !ok {
(*element1)[k] = element2[k]
} else {
//fmt.Println(strings.Repeat(" ", depth), " - merging: ", element2[k])
asanarrayptr, ok := (*element1)[k].([]map[string]interface{})
if ok {
if err := merger(depth+1, k, &asanarrayptr, element2[k]); err != nil {
return err
}
(*element1)[k] = asanarrayptr
} else {
asamapptr, ok := (*element1)[k].(map[string]interface{})
if ok {
if err := merger(depth+1, k, &asamapptr, element2[k]); err != nil {
return err
}
(*element1)[k] = asamapptr
} else {
return fmt.Errorf("Config dest: %s does not make a valid map/array ptr", keyName)
}
}
}
}
case []map[string]interface{}:
//fmt.Println(strings.Repeat(" ", depth), " - An array")
element2, ok := cfg2.([]map[string]interface{})
if !ok {
return fmt.Errorf("Config source: %s is not a map array", keyName)
}
//fmt.Println(strings.Repeat(" ", depth), " - element2: ", element2)
element1, ok := cfg1.(*[]map[string]interface{})
if !ok {
return fmt.Errorf("Config dest: %s is not a map array", keyName)
}
//fmt.Println(strings.Repeat(" ", depth), " - element1: ", element1)
for index, _ := range element2 {
*element1 = append(*element1, element2[index])
}
case string:
//fmt.Println(strings.Repeat(" ", depth), " - A string")
element2, ok := cfg2.(string)
if !ok {
return fmt.Errorf("Config: %s is not a string", keyName)
}
case bool:
//fmt.Println(strings.Repeat(" ", depth), " - A boolean")
element2, ok := cfg2.(bool)
if !ok {
return fmt.Errorf("Config: %s is not a boolean value", keyName)
}
case float64:
//fmt.Println(strings.Repeat(" ", depth), " - A float64")
element2, ok := cfg2.(float64)
if !ok {
return fmt.Errorf("Config: %s is not a float64 value", keyName)
}
case nil:
//fmt.Println(strings.Repeat(" ", depth), " - Nil")
default:
log.V(2).Info("Unknown element type found in configuration file. Ignoring.", "type", reflect.TypeOf(element2))
}
return nil
}
err := merger(0, "TOP", config1, config2)
if err != nil {
return err
}
return nil
}
func mergeConfigsO(config1 interface{}, config2 interface{}) (interface{}, error) {
var merger func(int, string, interface{}, interface{}) (interface{}, error)
merger = func(depth int, keyName string, cfg1 interface{}, cfg2 interface{}) (interface{}, error) {
var returnElement interface{}
fmt.Println(strings.Repeat(" ", depth), "Handling element: ", keyName, " for: ", cfg1)
switch element1 := cfg1.(type) {
case map[string]interface{}:
element2, ok := cfg2.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("Config: %s is not a map", keyName)
}
for k, value2 := range element2 {
value1, ok := element1[k]
if !ok {
return nil, fmt.Errorf("Config: %s not found in at least one config file", k)
}
merged, err := merger(depth+1, k, value1, value2)
if err != nil {
return merged, err
}
element1[k] = merged
}
returnElement = element1
case string:
element2, ok := cfg2.(string)
if !ok {
return nil, fmt.Errorf("Config: %s is not a string", keyName)
}
if element2 != "" {
element1 = element2
}
returnElement = element1
case bool:
element2, ok := cfg2.(bool)
if !ok {
return nil, fmt.Errorf("Config: %s is not a boolean value", keyName)
}
if element2 {
element1 = true
}
returnElement = element1
case float64:
element2, ok := cfg2.(float64)
if !ok {
return nil, fmt.Errorf("Config: %s is not a float64 value", keyName)
}
if element2 != 0 {
element1 = element2
}
returnElement = element1
case nil:
if cfg2 == nil {
returnElement = nil
} else {
element2, ok := cfg2.(map[string]interface{})
if ok {
returnElement = element2
} else {
element3, ok := cfg2.([]interface{})
if ok {
returnElement = element3
} else {
log.V(2).Info("Unexpected interface type for an assignment. Ignoring.")
returnElement = element1
}
}
}
default:
log.V(2).Info("Unknown element type found in configuration file. Ignoring.", "type", reflect.TypeOf(element1))
returnElement = element1
}
fmt.Println(strings.Repeat(" ", depth), "Done with element: ", keyName, " for: ", returnElement)
return returnElement, nil
}
/*
flattenedConfig1, _ := json.Marshal(config1)
var jsonConfig1 interface{}
//json.Unmarshal(flattenedConfig1, &jsonConfig1)
d1 := json.NewDecoder(bytes.NewReader(flattenedConfig1))
d1.UseNumber()
if err := d1.Decode(&jsonConfig1); err != nil {
return config1, err
}
flattenedConfig2, _ := json.Marshal(config2)
var jsonConfig2 interface{}
//json.Unmarshal(flattenedConfig2, &jsonConfig2)
//spew.Dump(jsonConfig2)
d2 := json.NewDecoder(bytes.NewReader(flattenedConfig2))
d2.UseNumber()
if err := d2.Decode(&jsonConfig2); err != nil {
return config1, err
}
*/
blocks, err := merger(0, "TOP", config1, config2)
if err != nil {
return config1, err
}
fmt.Println("--- OUTPUT ---")
spew.Dump(blocks)
fmt.Println("--- NICE EH ---")
//
//
// TODO: return blocks... we will convert back to toml when we are done merging!
//
buf := new(bytes.Buffer)
err = toml.NewEncoder(buf).Encode(blocks)
if err != nil {
return config1, err
}
fmt.Println(buf.String())
merged := config.Config{}
if _, err := toml.Decode(buf.String(), &merged); err != nil {
return config1, err
}
return merged, nil
}
func handleArgs(cfg config.Config) (*config.Config, error) {
// LDAP flags
if ldap, ok := args["--ldap"].(string); ok && ldap != "" {
cfg.LDAP.Enabled = true
cfg.LDAP.Listen = ldap
}
// LDAPS flags
if ldaps, ok := args["--ldaps"].(string); ok && ldaps != "" {
cfg.LDAPS.Enabled = true
cfg.LDAPS.Listen = ldaps
}
if ldapsCert, ok := args["--ldaps-cert"].(string); ok && ldapsCert != "" {
cfg.LDAPS.Cert = ldapsCert
}
if ldapsKey, ok := args["--ldaps-key"].(string); ok && ldapsKey != "" {
cfg.LDAPS.Key = ldapsKey
}
// Backward Compability
if cfg.Backend.Datastore != "" {
if cfg.Backends != nil {
return &cfg, fmt.Errorf("You cannot specify both [Backend] and [[Backends]] directives in the same configuration ")
} else {
cfg.Backends = append(cfg.Backends, cfg.Backend)
}
}
spew.Dump(cfg.Backends)
// Patch with default values where not specified
for i := range cfg.Backends {
if cfg.Backends[i].NameFormat == "" {
cfg.Backends[i].NameFormat = "cn"
}
if cfg.Backends[i].GroupFormat == "" {
cfg.Backends[i].GroupFormat = "ou"
}
if cfg.Backends[i].SSHKeyAttr == "" {
cfg.Backends[i].SSHKeyAttr = "sshPublicKey"
}
}
//
return &cfg, nil
}
func handleLegacyConfig(cfg config.Config) (*config.Config, error) {
if len(cfg.Frontend.Listen) > 0 && (len(cfg.LDAP.Listen) > 0 || len(cfg.LDAPS.Listen) > 0) {
// Both old server-config and new - dont allow
return &cfg, fmt.Errorf("Both old and new server-config in use - please remove old format ([frontend]) and migrate to new format ([ldap], [ldaps])")
@@ -237,7 +641,7 @@ func handleConfig(cfg config.Config) (*config.Config, error) {
if len(cfg.Frontend.Listen) > 0 {
// We're going with old format - parse it into new
log.Warning("Config [frontend] is deprecated - please move to [ldap] and [ldaps] as-per documentation")
log.V(2).Info("Config [frontend] is deprecated - please move to [ldap] and [ldaps] as-per documentation")
cfg.LDAP.Enabled = !cfg.Frontend.TLS
cfg.LDAPS.Enabled = cfg.Frontend.TLS
@@ -255,6 +659,9 @@ func handleConfig(cfg config.Config) (*config.Config, error) {
cfg.LDAPS.Key = cfg.Frontend.Key
}
}
return &cfg, nil
}
func validateConfig(cfg config.Config) (*config.Config, error) {
if !cfg.LDAP.Enabled && !cfg.LDAPS.Enabled {
return &cfg, fmt.Errorf("No server configuration found: please provide either LDAP or LDAPS configuration")
@@ -278,15 +685,34 @@ func handleConfig(cfg config.Config) (*config.Config, error) {
}
}
switch cfg.Backend.Datastore {
case "":
cfg.Backend.Datastore = "config"
case "config":
case "ldap":
case "owncloud":
default:
return &cfg, fmt.Errorf("invalid backend %s - must be 'config', 'ldap' or 'owncloud", cfg.Backend.Datastore)
//spew.Dump(cfg)
for i := range cfg.Backends {
switch cfg.Backends[i].Datastore {
case "":
cfg.Backends[i].Datastore = "config"
case "config":
case "ldap":
case "owncloud":
case "plugin":
default:
return &cfg, fmt.Errorf("invalid backend %s - must be 'config', 'ldap', 'owncloud' or 'plugin'", cfg.Backends[i].Datastore)
}
}
// TODO: remove after deprecating UnixID on User and Group
for _, user := range cfg.Users {
if user.UnixID != 0 {
user.UIDNumber = user.UnixID
log.V(2).Info(fmt.Sprintf("User '%s': 'unixid' is deprecated - please move to 'uidnumber' as per documentation", user.Name))
}
}
for _, group := range cfg.Groups {
if group.UnixID != 0 {
group.GIDNumber = group.UnixID
log.V(2).Info(fmt.Sprintf("Group '%s': 'unixid' is deprecated - please move to 'gidnumber' as per documentation", group.Name))
}
}
return &cfg, nil
}
@@ -298,8 +724,19 @@ func doConfig() error {
return err
}
// Handle validation and parsing of old [frontend] section into [ldap] and/or [ldaps] sections
cfg, err = handleConfig(*cfg)
// Handle parsed flags
cfg, err = handleArgs(*cfg)
if err != nil {
return err
}
// Handle parsing of legacy [frontend] section into [ldap] and/or [ldaps] sections
cfg, err = handleLegacyConfig(*cfg)
if err != nil {
return err
}
cfg, err = validateConfig(*cfg)
if err != nil {
return err
}
@@ -328,7 +765,7 @@ func doConfig() error {
if activeConfig.Debug {
logging.SetLevel(logging.DEBUG, programName)
log.Debug("Debugging enabled")
log.V(6).Info("Debugging enabled")
}
if activeConfig.Syslog {
enableSyslog(stderr)
@@ -339,7 +776,14 @@ func doConfig() error {
// initLogging sets up logging to stderr
func initLogging() *logging.LogBackend {
log = logging.MustGetLogger(programName)
l := logging.MustGetLogger(programName)
l.ExtraCalldepth = 2 // add extra call depth for the logr wrapper
log = gologgingr.New(
gologgingr.Logger(l),
)
gologgingr.SetVerbosity(10) // do not filter by verbosity. glauth uses the go-logging lib to filter the levels
format := "%{color}%{time:15:04:05.000000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}"
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
@@ -356,10 +800,11 @@ func enableSyslog(stderrBackend *logging.LogBackend) {
logging.SetFormatter(logging.MustStringFormatter(format))
syslogBackend, err := logging.NewSyslogBackend("")
if err != nil {
log.Fatal(err)
log.Error(err, "Could not create new syslog backend")
os.Exit(1)
}
logging.SetBackend(stderrBackend, syslogBackend)
log.Debug("Syslog enabled")
log.V(6).Info("Syslog enabled")
}
+210 -19
View File
@@ -1,32 +1,223 @@
package main
// Just the beginning of testing - more to be added
import (
"os/exec"
"strings"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func TestGetVersionString(t *testing.T) {
type testEnv struct {
checkanonymousrootDSE bool
checkTOTP bool
checkbindUPN bool
svcdn string
svcdnnogroup string
otpdn string
expectedinfo string
expectedaccount string
expectedfirstaccount string
expectedgroup string
}
// Setup config and check output
LastGitTag = "7"
BuildTime = "20180616_042045Z"
GitCommit = "a9837f3112fc049fd1e9faca6f56d8be1bec91bd"
GitClean = "1"
GitBranch = "dev"
GitTagIsCommit = "0"
func TestIntegerStuff(t *testing.T) {
versionOutput := getVersionString()
if versionOutput != "7" {
t.Errorf("Finish writing this test, output was: %s", versionOutput)
Convey("Testing sample-simple local file-based LDAP server", t, func() {
svc := startSvc(SD, "bin/glauth", "-c", "sample-simple.cfg")
batteryOfTests(
t,
svc, testEnv{
checkanonymousrootDSE: true,
checkTOTP: true,
checkbindUPN: true,
expectedinfo: "supportedLDAPVersion: 3",
svcdn: "cn=serviceuser,ou=svcaccts,dc=glauth,dc=com",
svcdnnogroup: "cn=serviceuser,dc=glauth,dc=com",
otpdn: "cn=otpuser,ou=superheros,dc=glauth,dc=com",
expectedaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
expectedfirstaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
expectedgroup: "dn: cn=superheros,ou=groups,dc=glauth,dc=com",
})
stopSvc(svc)
})
matchingLibrary := doRunGetFirst(RD, "ls", "bin/sqlite.so")
if matchingLibrary == "bin/sqlite.so" {
Convey("Testing sample-database LDAP server", t, func() {
svc := startSvc(SD, "bin/glauth", "-c", "pkg/plugins/sample-database.cfg")
batteryOfTests(
t,
svc, testEnv{
checkanonymousrootDSE: true,
checkTOTP: false,
checkbindUPN: true,
expectedinfo: "supportedLDAPVersion: 3",
svcdn: "cn=serviceuser,ou=svcaccts,dc=glauth,dc=com",
svcdnnogroup: "cn=serviceuser,dc=glauth,dc=com",
otpdn: "cn=otpuser,ou=superheros,dc=glauth,dc=com",
expectedaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
expectedfirstaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
expectedgroup: "dn: cn=superheros,ou=groups,dc=glauth,dc=com",
})
stopSvc(svc)
})
}
// TODO: write these cases
matchingContainers := doRunGetFirst(RD, "sh", "-c", "docker ps | grep openldap-service | wc -l")
if matchingContainers == "1" {
Convey("Testing sample-simple local LDAP server", t, func() {
svc := startSvc(SD, "bin/glauth", "-c", "sample-ldap-injection.cfg")
batteryOfTests(
t,
svc, testEnv{
checkanonymousrootDSE: false,
checkTOTP: true,
checkbindUPN: false,
expectedinfo: "objectClass: top",
svcdn: "cn=serviceuser,cn=svcaccts,ou=users,dc=glauth,dc=com",
svcdnnogroup: "", // ignore
otpdn: "cn=otpuser,cn=superheros,ou=users,dc=glauth,dc=com",
expectedaccount: "dn: cn=hackers,cn=superheros,ou=users,dc=glauth,dc=com",
expectedfirstaccount: "dn: cn=johndoe,cn=superheros,ou=users,dc=glauth,dc=com",
expectedgroup: "dn: cn=superheros,ou=users,dc=glauth,dc=com",
})
stopSvc(svc)
})
}
}
// Cases to test:
// 1) if GitTagIsCommit=1 - check for release-based strings (this build is release)
// 2) if GitClean=1 - check for "Commit: " as well as value of commit str
// 3) if GitClean=0 - Ensure "Commit: " is NOT there, as well ensure value of commit str is not there
// 4) do additional checks for release behavior, as well as branch name
func batteryOfTests(t *testing.T, svc *exec.Cmd, env testEnv) {
Convey("When searching for the 'hackers' user", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
Convey("We should find them in the 'superheros' group", func() {
So(out, ShouldEqual, env.expectedaccount)
})
})
if env.svcdnnogroup != "" {
Convey("When searching for the 'hackers' user without binding with a group", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdnnogroup, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
Convey("We should find them in the 'superheros' group", func() {
So(out, ShouldEqual, env.expectedaccount)
})
})
}
if env.checkbindUPN {
Convey("When searching for the 'hackers' user after binding using the account's UPN", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", "serviceuser@example.com", "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
Convey("We should find them in the 'superheros' group", func() {
So(out, ShouldEqual, env.expectedaccount)
})
})
}
Convey("When querying the root SDE", func() {
out := doRunGetSecond(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-s", "base", "(objectclass=*)")
Convey("We should get some meta information", func() {
So(out, ShouldEqual, env.expectedinfo)
})
})
if env.checkanonymousrootDSE {
Convey("When querying the root SDE anonymously without authorizing in config file", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-x", "-s", "base", "(objectclass=*)")
Convey("We should get error 50", func() {
So(out, ShouldEqual, "exit status 50")
})
})
}
Convey("When enumerating posix groups", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(objectclass=posixgroup)")
Convey("We should get a list starting with the 'superheros' group", func() {
So(out, ShouldEqual, env.expectedgroup)
})
})
Convey("When searching for members of the 'superheros' group", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(memberOf=cn=superheros,ou=groups,dc=glauth,dc=com)")
Convey("We should get a list starting with the 'hackers' user", func() {
So(out, ShouldEqual, env.expectedfirstaccount)
})
})
Convey("When performing a complex search for members of 'superheros' group", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(&(objectClass=*)(memberOf=cn=superheros,ou=groups,dc=glauth,dc=com))")
Convey("We should get a list starting with the 'hackers' user", func() {
So(out, ShouldEqual, env.expectedfirstaccount)
})
})
if env.checkTOTP {
Convey("When searching for the 'hacker' user using a TOTP-enabled account", func() {
otpvalue := doRunGetFirst(RD, "oathtool", "--totp", "-b", "-d", "6", "3hnvnk4ycv44glzigd6s25j4dougs3rk")
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret"+otpvalue, "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
Convey("We should find them in in the 'superheros' group", func() {
So(out, ShouldEqual, env.expectedaccount)
})
})
Convey("When searching for the 'hacker' user using a TOTP-enabled account and no value", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret", "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
Convey("We should get 'Invalid credentials(49)'", func() {
So(out, ShouldEqual, "exit status 49")
})
})
Convey("When searching for the 'hacker' user using a TOTP-enabled account and the wrong value", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret123456", "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
Convey("We should get 'Invalid credentials(49)'", func() {
So(out, ShouldEqual, "exit status 49")
})
})
}
}
// -----=============================================================================----
const SD = 3 // Start Delay
const RD = 2 // Response Delay
func startSvc(delay time.Duration, name string, arg ...string) *exec.Cmd {
cmd := exec.Command(name, arg...)
cmd.Start()
if delay != 0 {
time.Sleep(time.Second * delay)
}
return cmd
}
func stopSvc(svc *exec.Cmd) {
svc.Process.Kill()
}
func doRunGetFirst(delay time.Duration, name string, arg ...string) string {
out := strings.SplitN(doRun(delay, name, arg...), "\n", 2)
if out == nil || len(out) < 1 {
return "*fail*"
}
return out[0]
}
func doRunGetSecond(delay time.Duration, name string, arg ...string) string {
out := strings.SplitN(doRun(delay, name, arg...), "\n", 3)
if out == nil || len(out) < 2 {
return "*fail*"
}
return out[1]
}
func doRun(delay time.Duration, name string, arg ...string) string {
out, err := exec.Command(name, arg...).Output()
if err != nil {
return err.Error()
}
if delay != 0 {
time.Sleep(time.Second * delay)
}
return strings.TrimSpace(string(out))
}
+18 -11
View File
@@ -1,21 +1,28 @@
module github.com/glauth/glauth
go 1.13
go 1.16
replace github.com/hydronica/toml => ./toml
require (
github.com/BurntSushi/toml v0.3.1
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e
github.com/arl/statsviz v0.4.0
github.com/boombuler/barcode v1.0.1 // indirect
github.com/davecgh/go-spew v1.1.1
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/fsnotify/fsnotify v1.4.7
github.com/fsnotify/fsnotify v1.4.9
github.com/go-logr/logr v0.4.0
github.com/go-sql-driver/mysql v1.5.0
github.com/hydronica/toml v0.4.2
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect
github.com/kr/pretty v0.2.0
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 // indirect
github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3
github.com/lib/pq v1.10.0
github.com/mattn/go-sqlite3 v1.14.6
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pquerna/otp v1.2.0
github.com/pquerna/otp v1.3.0
github.com/smartystreets/goconvey v1.6.4
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c // indirect
gopkg.in/amz.v1 v1.0.0-20150111123259-ad23e96a31d2
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
+55 -18
View File
@@ -1,46 +1,83 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e h1:Bqtt5C+uVk+vH/t5dmB47uDCTwxw16EYHqvJnmY2aQc=
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e/go.mod h1:njRCDrl+1RQ/A/+KVU8Ho2EWAxUSkohOWczdW3dzDG0=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/arl/statsviz v0.4.0 h1:pv8DWfti0TC8M2Baold2xGnCnir9jhqv1BO6UNKQmqY=
github.com/arl/statsviz v0.4.0/go.mod h1:+5inUy/dxy11x/KSmicG3ZrEEy0Yr81AFm3dn4QC04M=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hydronica/toml v0.4.2 h1:BY+iAvyl2u1BlfgkJQzqc1UXDGLWfpMmOWuqJrwAVDU=
github.com/hydronica/toml v0.4.2/go.mod h1:c7QhbYq3Wp9SlOWuG7MAieKUyXP2P/hXhy/YqWfbS/4=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
github.com/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts=
github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 h1:D9EvfGQvlkKaDr2CRKN++7HbSXbefUNDrPq60T+g24s=
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484/go.mod h1:O1EljZ+oHprtxDDPHiMWVo/5dBT6PlvWX5PSwj80aBA=
github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3 h1:NNis9uuNpG5h97Dvxxo53Scg02qBg+3Nfabg6zjFGu8=
github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3/go.mod h1:YtrVB1/v9Td9SyjXpjYVmbdKgj9B0nPTBsdGUxy0i8U=
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba h1:DO8NFYdcRv1dnyAINJIBm6Bw2XibtLvQniNFGzf2W8E=
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba/go.mod h1:4S0XndRL8HNOaQBfdViJ2F/GPCgL524xlXRuXFH12/U=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
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/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=
github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc h1:ejaC8rvIvCWmsaFrvmGOxhBuMxxhBB1xRshuM98XQ7M=
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc/go.mod h1:tso14hwzqX4VbnWTNsxiL0DvMb2OwbGISFA7jDibdWc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c h1:jceGD5YNJGgGMkJz79agzOln1K9TaZUjv5ird16qniQ=
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/amz.v1 v1.0.0-20150111123259-ad23e96a31d2 h1:FMrsB0OTjHsPDA1NM7AhRmmZzkBPu3iGdxK/5MFfBmk=
gopkg.in/amz.v1 v1.0.0-20150111123259-ad23e96a31d2/go.mod h1:F0YaN4yi2XekmElKkPYfybh7pReQE9Ph48lSgeCkzyc=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b h1:tdSyAzD5FHJmZAZ13d5WexAo7YJkiyGphhzFfInMnDw=
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b/go.mod h1:cE0dCGx2UfBTjLFlzEx4EXJUmoX6BXBoX9GjKOvqha4=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+23
View File
@@ -0,0 +1,23 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 e5ffe9fd
dn: cn=config
objectClass: olcGlobal
cn: config
olcArgsFile: /var/run/slapd/slapd.args
olcLogLevel: none
olcPidFile: /var/run/slapd/slapd.pid
olcToolThreads: 1
structuralObjectClass: olcGlobal
entryUUID: 5c21e0e8-7ddc-103b-9ebb-c740e015b81b
creatorsName: cn=config
createTimestamp: 20210720192809Z
olcTLSCipherSuite: SECURE256:+SECURE128:-VERS-TLS-ALL:+VERS-TLS1.2:-RSA:-DHE
-DSS:-CAMELLIA-128-CBC:-CAMELLIA-256-CBC
olcTLSCACertificateFile: /container/service/slapd/assets/certs/ca.crt
olcTLSCertificateFile: /container/service/slapd/assets/certs/ldap.crt
olcTLSCertificateKeyFile: /container/service/slapd/assets/certs/ldap.key
olcTLSDHParamFile: /container/service/slapd/assets/certs/dhparam.pem
olcTLSVerifyClient: demand
entryCSN: 20210812002157.868917Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210812002157Z
@@ -0,0 +1,16 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 2aabc521
dn: cn=module{0}
objectClass: olcModuleList
cn: module{0}
olcModulePath: /usr/lib/ldap
olcModuleLoad: {0}back_mdb
olcModuleLoad: {1}memberof
olcModuleLoad: {2}refint
structuralObjectClass: olcModuleList
entryUUID: 5c2b1fb4-7ddc-103b-9ec3-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.856380Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,12 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 bc37faae
dn: cn=schema
objectClass: olcSchemaConfig
cn: schema
structuralObjectClass: olcSchemaConfig
entryUUID: 5c2570a0-7ddc-103b-9ebe-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.316331Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,249 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 57d21f8c
dn: cn={0}core
objectClass: olcSchemaConfig
cn: {0}core
olcAttributeTypes: {0}( 2.5.4.2 NAME 'knowledgeInformation' DESC 'RFC2256: k
nowledge information' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.15{32768} )
olcAttributeTypes: {1}( 2.5.4.4 NAME ( 'sn' 'surname' ) DESC 'RFC2256: last
(family) name(s) for which the entity is known by' SUP name )
olcAttributeTypes: {2}( 2.5.4.5 NAME 'serialNumber' DESC 'RFC2256: serial nu
mber of the entity' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{64} )
olcAttributeTypes: {3}( 2.5.4.6 NAME ( 'c' 'countryName' ) DESC 'RFC4519: tw
o-letter ISO-3166 country code' SUP name SYNTAX 1.3.6.1.4.1.1466.115.121.1.
11 SINGLE-VALUE )
olcAttributeTypes: {4}( 2.5.4.7 NAME ( 'l' 'localityName' ) DESC 'RFC2256: l
ocality which this object resides in' SUP name )
olcAttributeTypes: {5}( 2.5.4.8 NAME ( 'st' 'stateOrProvinceName' ) DESC 'RF
C2256: state or province which this object resides in' SUP name )
olcAttributeTypes: {6}( 2.5.4.9 NAME ( 'street' 'streetAddress' ) DESC 'RFC2
256: street address of this object' EQUALITY caseIgnoreMatch SUBSTR caseIgn
oreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {7}( 2.5.4.10 NAME ( 'o' 'organizationName' ) DESC 'RFC22
56: organization this object belongs to' SUP name )
olcAttributeTypes: {8}( 2.5.4.11 NAME ( 'ou' 'organizationalUnitName' ) DESC
'RFC2256: organizational unit this object belongs to' SUP name )
olcAttributeTypes: {9}( 2.5.4.12 NAME 'title' DESC 'RFC2256: title associate
d with the entity' SUP name )
olcAttributeTypes: {10}( 2.5.4.14 NAME 'searchGuide' DESC 'RFC2256: search g
uide, deprecated by enhancedSearchGuide' SYNTAX 1.3.6.1.4.1.1466.115.121.1.
25 )
olcAttributeTypes: {11}( 2.5.4.15 NAME 'businessCategory' DESC 'RFC2256: bus
iness category' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch S
YNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {12}( 2.5.4.16 NAME 'postalAddress' DESC 'RFC2256: postal
address' EQUALITY caseIgnoreListMatch SUBSTR caseIgnoreListSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )
olcAttributeTypes: {13}( 2.5.4.17 NAME 'postalCode' DESC 'RFC2256: postal co
de' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.15{40} )
olcAttributeTypes: {14}( 2.5.4.18 NAME 'postOfficeBox' DESC 'RFC2256: Post O
ffice Box' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX
1.3.6.1.4.1.1466.115.121.1.15{40} )
olcAttributeTypes: {15}( 2.5.4.19 NAME 'physicalDeliveryOfficeName' DESC 'RF
C2256: Physical Delivery Office Name' EQUALITY caseIgnoreMatch SUBSTR caseI
gnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {16}( 2.5.4.20 NAME 'telephoneNumber' DESC 'RFC2256: Tele
phone Number' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstring
sMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50{32} )
olcAttributeTypes: {17}( 2.5.4.21 NAME 'telexNumber' DESC 'RFC2256: Telex Nu
mber' SYNTAX 1.3.6.1.4.1.1466.115.121.1.52 )
olcAttributeTypes: {18}( 2.5.4.22 NAME 'teletexTerminalIdentifier' DESC 'RFC
2256: Teletex Terminal Identifier' SYNTAX 1.3.6.1.4.1.1466.115.121.1.51 )
olcAttributeTypes: {19}( 2.5.4.23 NAME ( 'facsimileTelephoneNumber' 'fax' )
DESC 'RFC2256: Facsimile (Fax) Telephone Number' SYNTAX 1.3.6.1.4.1.1466.11
5.121.1.22 )
olcAttributeTypes: {20}( 2.5.4.24 NAME 'x121Address' DESC 'RFC2256: X.121 Ad
dress' EQUALITY numericStringMatch SUBSTR numericStringSubstringsMatch SYNT
AX 1.3.6.1.4.1.1466.115.121.1.36{15} )
olcAttributeTypes: {21}( 2.5.4.25 NAME 'internationaliSDNNumber' DESC 'RFC22
56: international ISDN number' EQUALITY numericStringMatch SUBSTR numericSt
ringSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{16} )
olcAttributeTypes: {22}( 2.5.4.26 NAME 'registeredAddress' DESC 'RFC2256: re
gistered postal address' SUP postalAddress SYNTAX 1.3.6.1.4.1.1466.115.121.
1.41 )
olcAttributeTypes: {23}( 2.5.4.27 NAME 'destinationIndicator' DESC 'RFC2256:
destination indicator' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstring
sMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{128} )
olcAttributeTypes: {24}( 2.5.4.28 NAME 'preferredDeliveryMethod' DESC 'RFC22
56: preferred delivery method' SYNTAX 1.3.6.1.4.1.1466.115.121.1.14 SINGLE-
VALUE )
olcAttributeTypes: {25}( 2.5.4.29 NAME 'presentationAddress' DESC 'RFC2256:
presentation address' EQUALITY presentationAddressMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.43 SINGLE-VALUE )
olcAttributeTypes: {26}( 2.5.4.30 NAME 'supportedApplicationContext' DESC 'R
FC2256: supported application context' EQUALITY objectIdentifierMatch SYNTA
X 1.3.6.1.4.1.1466.115.121.1.38 )
olcAttributeTypes: {27}( 2.5.4.31 NAME 'member' DESC 'RFC2256: member of a g
roup' SUP distinguishedName )
olcAttributeTypes: {28}( 2.5.4.32 NAME 'owner' DESC 'RFC2256: owner (of the
object)' SUP distinguishedName )
olcAttributeTypes: {29}( 2.5.4.33 NAME 'roleOccupant' DESC 'RFC2256: occupan
t of role' SUP distinguishedName )
olcAttributeTypes: {30}( 2.5.4.36 NAME 'userCertificate' DESC 'RFC2256: X.50
9 user certificate, use ;binary' EQUALITY certificateExactMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.8 )
olcAttributeTypes: {31}( 2.5.4.37 NAME 'cACertificate' DESC 'RFC2256: X.509
CA certificate, use ;binary' EQUALITY certificateExactMatch SYNTAX 1.3.6.1.
4.1.1466.115.121.1.8 )
olcAttributeTypes: {32}( 2.5.4.38 NAME 'authorityRevocationList' DESC 'RFC22
56: X.509 authority revocation list, use ;binary' SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.9 )
olcAttributeTypes: {33}( 2.5.4.39 NAME 'certificateRevocationList' DESC 'RFC
2256: X.509 certificate revocation list, use ;binary' SYNTAX 1.3.6.1.4.1.14
66.115.121.1.9 )
olcAttributeTypes: {34}( 2.5.4.40 NAME 'crossCertificatePair' DESC 'RFC2256:
X.509 cross certificate pair, use ;binary' SYNTAX 1.3.6.1.4.1.1466.115.121
.1.10 )
olcAttributeTypes: {35}( 2.5.4.42 NAME ( 'givenName' 'gn' ) DESC 'RFC2256: f
irst name(s) for which the entity is known by' SUP name )
olcAttributeTypes: {36}( 2.5.4.43 NAME 'initials' DESC 'RFC2256: initials of
some or all of names, but not the surname(s).' SUP name )
olcAttributeTypes: {37}( 2.5.4.44 NAME 'generationQualifier' DESC 'RFC2256:
name qualifier indicating a generation' SUP name )
olcAttributeTypes: {38}( 2.5.4.45 NAME 'x500UniqueIdentifier' DESC 'RFC2256:
X.500 unique identifier' EQUALITY bitStringMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.6 )
olcAttributeTypes: {39}( 2.5.4.46 NAME 'dnQualifier' DESC 'RFC2256: DN quali
fier' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR case
IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
olcAttributeTypes: {40}( 2.5.4.47 NAME 'enhancedSearchGuide' DESC 'RFC2256:
enhanced search guide' SYNTAX 1.3.6.1.4.1.1466.115.121.1.21 )
olcAttributeTypes: {41}( 2.5.4.48 NAME 'protocolInformation' DESC 'RFC2256:
protocol information' EQUALITY protocolInformationMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.42 )
olcAttributeTypes: {42}( 2.5.4.50 NAME 'uniqueMember' DESC 'RFC2256: unique
member of a group' EQUALITY uniqueMemberMatch SYNTAX 1.3.6.1.4.1.1466.115.1
21.1.34 )
olcAttributeTypes: {43}( 2.5.4.51 NAME 'houseIdentifier' DESC 'RFC2256: hous
e identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYN
TAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )
olcAttributeTypes: {44}( 2.5.4.52 NAME 'supportedAlgorithms' DESC 'RFC2256:
supported algorithms' SYNTAX 1.3.6.1.4.1.1466.115.121.1.49 )
olcAttributeTypes: {45}( 2.5.4.53 NAME 'deltaRevocationList' DESC 'RFC2256:
delta revocation list; use ;binary' SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 )
olcAttributeTypes: {46}( 2.5.4.54 NAME 'dmdName' DESC 'RFC2256: name of DMD'
SUP name )
olcAttributeTypes: {47}( 2.5.4.65 NAME 'pseudonym' DESC 'X.520(4th): pseudon
ym for the object' SUP name )
olcAttributeTypes: {48}( 0.9.2342.19200300.100.1.3 NAME ( 'mail' 'rfc822Mail
box' ) DESC 'RFC1274: RFC822 Mailbox' EQUALITY caseIgnoreIA5Match SUBST
R caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
)
olcAttributeTypes: {49}( 0.9.2342.19200300.100.1.25 NAME ( 'dc' 'domainCompo
nent' ) DESC 'RFC1274/2247: domain component' EQUALITY caseIgnoreIA5Match S
UBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SIN
GLE-VALUE )
olcAttributeTypes: {50}( 0.9.2342.19200300.100.1.37 NAME 'associatedDomain'
DESC 'RFC1274: domain associated with object' EQUALITY caseIgnoreIA5Match S
UBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {51}( 1.2.840.113549.1.9.1 NAME ( 'email' 'emailAddress'
'pkcs9email' ) DESC 'RFC3280: legacy attribute for email addresses in DNs'
EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.26{128} )
olcObjectClasses: {0}( 2.5.6.2 NAME 'country' DESC 'RFC2256: a country' SUP
top STRUCTURAL MUST c MAY ( searchGuide $ description ) )
olcObjectClasses: {1}( 2.5.6.3 NAME 'locality' DESC 'RFC2256: a locality' SU
P top STRUCTURAL MAY ( street $ seeAlso $ searchGuide $ st $ l $ descriptio
n ) )
olcObjectClasses: {2}( 2.5.6.4 NAME 'organization' DESC 'RFC2256: an organiz
ation' SUP top STRUCTURAL MUST o MAY ( userPassword $ searchGuide $ seeAlso
$ businessCategory $ x121Address $ registeredAddress $ destinationIndicato
r $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ tel
ephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ street
$ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName
$ st $ l $ description ) )
olcObjectClasses: {3}( 2.5.6.5 NAME 'organizationalUnit' DESC 'RFC2256: an o
rganizational unit' SUP top STRUCTURAL MUST ou MAY ( userPassword $ searchG
uide $ seeAlso $ businessCategory $ x121Address $ registeredAddress $ desti
nationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalId
entifier $ telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNu
mber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDelive
ryOfficeName $ st $ l $ description ) )
olcObjectClasses: {4}( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP to
p STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ seeAls
o $ description ) )
olcObjectClasses: {5}( 2.5.6.7 NAME 'organizationalPerson' DESC 'RFC2256: an
organizational person' SUP person STRUCTURAL MAY ( title $ x121Address $ r
egisteredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNu
mber $ teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumbe
r $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ posta
lAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) )
olcObjectClasses: {6}( 2.5.6.8 NAME 'organizationalRole' DESC 'RFC2256: an o
rganizational role' SUP top STRUCTURAL MUST cn MAY ( x121Address $ register
edAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $
teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ fac
simileTelephoneNumber $ seeAlso $ roleOccupant $ preferredDeliveryMethod $
street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOffic
eName $ ou $ st $ l $ description ) )
olcObjectClasses: {7}( 2.5.6.9 NAME 'groupOfNames' DESC 'RFC2256: a group of
names (DNs)' SUP top STRUCTURAL MUST ( member $ cn ) MAY ( businessCategor
y $ seeAlso $ owner $ ou $ o $ description ) )
olcObjectClasses: {8}( 2.5.6.10 NAME 'residentialPerson' DESC 'RFC2256: an r
esidential person' SUP person STRUCTURAL MUST l MAY ( businessCategory $ x1
21Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMet
hod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internati
onaliSDNNumber $ facsimileTelephoneNumber $ preferredDeliveryMethod $ stree
t $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName
$ st $ l ) )
olcObjectClasses: {9}( 2.5.6.11 NAME 'applicationProcess' DESC 'RFC2256: an
application process' SUP top STRUCTURAL MUST cn MAY ( seeAlso $ ou $ l $ de
scription ) )
olcObjectClasses: {10}( 2.5.6.12 NAME 'applicationEntity' DESC 'RFC2256: an
application entity' SUP top STRUCTURAL MUST ( presentationAddress $ cn ) MA
Y ( supportedApplicationContext $ seeAlso $ ou $ o $ l $ description ) )
olcObjectClasses: {11}( 2.5.6.13 NAME 'dSA' DESC 'RFC2256: a directory syste
m agent (a server)' SUP applicationEntity STRUCTURAL MAY knowledgeInformati
on )
olcObjectClasses: {12}( 2.5.6.14 NAME 'device' DESC 'RFC2256: a device' SUP
top STRUCTURAL MUST cn MAY ( serialNumber $ seeAlso $ owner $ ou $ o $ l $
description ) )
olcObjectClasses: {13}( 2.5.6.15 NAME 'strongAuthenticationUser' DESC 'RFC22
56: a strong authentication user' SUP top AUXILIARY MUST userCertificate )
olcObjectClasses: {14}( 2.5.6.16 NAME 'certificationAuthority' DESC 'RFC2256
: a certificate authority' SUP top AUXILIARY MUST ( authorityRevocationList
$ certificateRevocationList $ cACertificate ) MAY crossCertificatePair )
olcObjectClasses: {15}( 2.5.6.17 NAME 'groupOfUniqueNames' DESC 'RFC2256: a
group of unique names (DN and Unique Identifier)' SUP top STRUCTURAL MUST (
uniqueMember $ cn ) MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ de
scription ) )
olcObjectClasses: {16}( 2.5.6.18 NAME 'userSecurityInformation' DESC 'RFC225
6: a user security information' SUP top AUXILIARY MAY ( supportedAlgorithms
) )
olcObjectClasses: {17}( 2.5.6.16.2 NAME 'certificationAuthority-V2' SUP cert
ificationAuthority AUXILIARY MAY ( deltaRevocationList ) )
olcObjectClasses: {18}( 2.5.6.19 NAME 'cRLDistributionPoint' SUP top STRUCTU
RAL MUST ( cn ) MAY ( certificateRevocationList $ authorityRevocationList $
deltaRevocationList ) )
olcObjectClasses: {19}( 2.5.6.20 NAME 'dmd' SUP top STRUCTURAL MUST ( dmdNam
e ) MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $ x121Add
ress $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $
telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationali
SDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode
$ postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) )
olcObjectClasses: {20}( 2.5.6.21 NAME 'pkiUser' DESC 'RFC2587: a PKI user' S
UP top AUXILIARY MAY userCertificate )
olcObjectClasses: {21}( 2.5.6.22 NAME 'pkiCA' DESC 'RFC2587: PKI certificate
authority' SUP top AUXILIARY MAY ( authorityRevocationList $ certificateRe
vocationList $ cACertificate $ crossCertificatePair ) )
olcObjectClasses: {22}( 2.5.6.23 NAME 'deltaCRL' DESC 'RFC2587: PKI user' SU
P top AUXILIARY MAY deltaRevocationList )
olcObjectClasses: {23}( 1.3.6.1.4.1.250.3.15 NAME 'labeledURIObject' DESC 'R
FC2079: object that contains the URI attribute type' MAY ( labeledURI ) SUP
top AUXILIARY )
olcObjectClasses: {24}( 0.9.2342.19200300.100.4.19 NAME 'simpleSecurityObjec
t' DESC 'RFC1274: simple security object' SUP top AUXILIARY MUST userPasswo
rd )
olcObjectClasses: {25}( 1.3.6.1.4.1.1466.344 NAME 'dcObject' DESC 'RFC2247:
domain component object' SUP top AUXILIARY MUST dc )
olcObjectClasses: {26}( 1.3.6.1.1.3.1 NAME 'uidObject' DESC 'RFC2377: uid ob
ject' SUP top AUXILIARY MUST uid )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c26a290-7ddc-103b-9ebf-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.324162Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,178 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 3133677b
dn: cn={1}cosine
objectClass: olcSchemaConfig
cn: {1}cosine
olcAttributeTypes: {0}( 0.9.2342.19200300.100.1.2 NAME 'textEncodedORAddress
' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.
4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {1}( 0.9.2342.19200300.100.1.4 NAME 'info' DESC 'RFC1274:
general information' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2048} )
olcAttributeTypes: {2}( 0.9.2342.19200300.100.1.5 NAME ( 'drink' 'favouriteD
rink' ) DESC 'RFC1274: favorite drink' EQUALITY caseIgnoreMatch SUBSTR case
IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {3}( 0.9.2342.19200300.100.1.6 NAME 'roomNumber' DESC 'RF
C1274: room number' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {4}( 0.9.2342.19200300.100.1.7 NAME 'photo' DESC 'RFC1274
: photo (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.23{25000} )
olcAttributeTypes: {5}( 0.9.2342.19200300.100.1.8 NAME 'userClass' DESC 'RFC
1274: category of user' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstring
sMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {6}( 0.9.2342.19200300.100.1.9 NAME 'host' DESC 'RFC1274:
host computer' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch S
YNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {7}( 0.9.2342.19200300.100.1.10 NAME 'manager' DESC 'RFC1
274: DN of manager' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466
.115.121.1.12 )
olcAttributeTypes: {8}( 0.9.2342.19200300.100.1.11 NAME 'documentIdentifier'
DESC 'RFC1274: unique identifier of document' EQUALITY caseIgnoreMatch SUB
STR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {9}( 0.9.2342.19200300.100.1.12 NAME 'documentTitle' DESC
'RFC1274: title of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSub
stringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {10}( 0.9.2342.19200300.100.1.13 NAME 'documentVersion' D
ESC 'RFC1274: version of document' EQUALITY caseIgnoreMatch SUBSTR caseIgno
reSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {11}( 0.9.2342.19200300.100.1.14 NAME 'documentAuthor' DE
SC 'RFC1274: DN of author of document' EQUALITY distinguishedNameMatch SYNT
AX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {12}( 0.9.2342.19200300.100.1.15 NAME 'documentLocation'
DESC 'RFC1274: location of document original' EQUALITY caseIgnoreMatch SUBS
TR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {13}( 0.9.2342.19200300.100.1.20 NAME ( 'homePhone' 'home
TelephoneNumber' ) DESC 'RFC1274: home telephone number' EQUALITY telephone
NumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.50 )
olcAttributeTypes: {14}( 0.9.2342.19200300.100.1.21 NAME 'secretary' DESC 'R
FC1274: DN of secretary' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1
.1466.115.121.1.12 )
olcAttributeTypes: {15}( 0.9.2342.19200300.100.1.22 NAME 'otherMailbox' SYNT
AX 1.3.6.1.4.1.1466.115.121.1.39 )
olcAttributeTypes: {16}( 0.9.2342.19200300.100.1.26 NAME 'aRecord' EQUALITY
caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {17}( 0.9.2342.19200300.100.1.27 NAME 'mDRecord' EQUALITY
caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {18}( 0.9.2342.19200300.100.1.28 NAME 'mXRecord' EQUALITY
caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {19}( 0.9.2342.19200300.100.1.29 NAME 'nSRecord' EQUALITY
caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {20}( 0.9.2342.19200300.100.1.30 NAME 'sOARecord' EQUALIT
Y caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {21}( 0.9.2342.19200300.100.1.31 NAME 'cNAMERecord' EQUAL
ITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {22}( 0.9.2342.19200300.100.1.38 NAME 'associatedName' DE
SC 'RFC1274: DN of entry associated with domain' EQUALITY distinguishedName
Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {23}( 0.9.2342.19200300.100.1.39 NAME 'homePostalAddress'
DESC 'RFC1274: home postal address' EQUALITY caseIgnoreListMatch SUBSTR ca
seIgnoreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )
olcAttributeTypes: {24}( 0.9.2342.19200300.100.1.40 NAME 'personalTitle' DES
C 'RFC1274: personal title' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubst
ringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {25}( 0.9.2342.19200300.100.1.41 NAME ( 'mobile' 'mobileT
elephoneNumber' ) DESC 'RFC1274: mobile telephone number' EQUALITY telephon
eNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.
115.121.1.50 )
olcAttributeTypes: {26}( 0.9.2342.19200300.100.1.42 NAME ( 'pager' 'pagerTel
ephoneNumber' ) DESC 'RFC1274: pager telephone number' EQUALITY telephoneNu
mberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115
.121.1.50 )
olcAttributeTypes: {27}( 0.9.2342.19200300.100.1.43 NAME ( 'co' 'friendlyCou
ntryName' ) DESC 'RFC1274: friendly country name' EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {28}( 0.9.2342.19200300.100.1.44 NAME 'uniqueIdentifier'
DESC 'RFC1274: unique identifer' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.
1.1466.115.121.1.15{256} )
olcAttributeTypes: {29}( 0.9.2342.19200300.100.1.45 NAME 'organizationalStat
us' DESC 'RFC1274: organizational status' EQUALITY caseIgnoreMatch SUBSTR c
aseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {30}( 0.9.2342.19200300.100.1.46 NAME 'janetMailbox' DESC
'RFC1274: Janet mailbox' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5S
ubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
olcAttributeTypes: {31}( 0.9.2342.19200300.100.1.47 NAME 'mailPreferenceOpti
on' DESC 'RFC1274: mail preference option' SYNTAX 1.3.6.1.4.1.1466.115.121.
1.27 )
olcAttributeTypes: {32}( 0.9.2342.19200300.100.1.48 NAME 'buildingName' DESC
'RFC1274: name of building' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubs
tringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {33}( 0.9.2342.19200300.100.1.49 NAME 'dSAQuality' DESC '
RFC1274: DSA Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.19 SINGLE-VALUE )
olcAttributeTypes: {34}( 0.9.2342.19200300.100.1.50 NAME 'singleLevelQuality
' DESC 'RFC1274: Single Level Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.13
SINGLE-VALUE )
olcAttributeTypes: {35}( 0.9.2342.19200300.100.1.51 NAME 'subtreeMinimumQual
ity' DESC 'RFC1274: Subtree Mininum Quality' SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.13 SINGLE-VALUE )
olcAttributeTypes: {36}( 0.9.2342.19200300.100.1.52 NAME 'subtreeMaximumQual
ity' DESC 'RFC1274: Subtree Maximun Quality' SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.13 SINGLE-VALUE )
olcAttributeTypes: {37}( 0.9.2342.19200300.100.1.53 NAME 'personalSignature'
DESC 'RFC1274: Personal Signature (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.23 )
olcAttributeTypes: {38}( 0.9.2342.19200300.100.1.54 NAME 'dITRedirect' DESC
'RFC1274: DIT Redirect' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.12 )
olcAttributeTypes: {39}( 0.9.2342.19200300.100.1.55 NAME 'audio' DESC 'RFC12
74: audio (u-law)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.4{25000} )
olcAttributeTypes: {40}( 0.9.2342.19200300.100.1.56 NAME 'documentPublisher'
DESC 'RFC1274: publisher of document' EQUALITY caseIgnoreMatch SUBSTR case
IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcObjectClasses: {0}( 0.9.2342.19200300.100.4.4 NAME ( 'pilotPerson' 'newPi
lotPerson' ) SUP person STRUCTURAL MAY ( userid $ textEncodedORAddress $ rf
c822Mailbox $ favouriteDrink $ roomNumber $ userClass $ homeTelephoneNumber
$ homePostalAddress $ secretary $ personalTitle $ preferredDeliveryMethod
$ businessCategory $ janetMailbox $ otherMailbox $ mobileTelephoneNumber $
pagerTelephoneNumber $ organizationalStatus $ mailPreferenceOption $ person
alSignature ) )
olcObjectClasses: {1}( 0.9.2342.19200300.100.4.5 NAME 'account' SUP top STRU
CTURAL MUST userid MAY ( description $ seeAlso $ localityName $ organizatio
nName $ organizationalUnitName $ host ) )
olcObjectClasses: {2}( 0.9.2342.19200300.100.4.6 NAME 'document' SUP top STR
UCTURAL MUST documentIdentifier MAY ( commonName $ description $ seeAlso $
localityName $ organizationName $ organizationalUnitName $ documentTitle $
documentVersion $ documentAuthor $ documentLocation $ documentPublisher ) )
olcObjectClasses: {3}( 0.9.2342.19200300.100.4.7 NAME 'room' SUP top STRUCTU
RAL MUST commonName MAY ( roomNumber $ description $ seeAlso $ telephoneNum
ber ) )
olcObjectClasses: {4}( 0.9.2342.19200300.100.4.9 NAME 'documentSeries' SUP t
op STRUCTURAL MUST commonName MAY ( description $ seeAlso $ telephonenumber
$ localityName $ organizationName $ organizationalUnitName ) )
olcObjectClasses: {5}( 0.9.2342.19200300.100.4.13 NAME 'domain' SUP top STRU
CTURAL MUST domainComponent MAY ( associatedName $ organizationName $ descr
iption $ businessCategory $ seeAlso $ searchGuide $ userPassword $ locality
Name $ stateOrProvinceName $ streetAddress $ physicalDeliveryOfficeName $ p
ostalAddress $ postalCode $ postOfficeBox $ streetAddress $ facsimileTeleph
oneNumber $ internationalISDNNumber $ telephoneNumber $ teletexTerminalIden
tifier $ telexNumber $ preferredDeliveryMethod $ destinationIndicator $ reg
isteredAddress $ x121Address ) )
olcObjectClasses: {6}( 0.9.2342.19200300.100.4.14 NAME 'RFC822localPart' SUP
domain STRUCTURAL MAY ( commonName $ surname $ description $ seeAlso $ tel
ephoneNumber $ physicalDeliveryOfficeName $ postalAddress $ postalCode $ po
stOfficeBox $ streetAddress $ facsimileTelephoneNumber $ internationalISDNN
umber $ telephoneNumber $ teletexTerminalIdentifier $ telexNumber $ preferr
edDeliveryMethod $ destinationIndicator $ registeredAddress $ x121Address )
)
olcObjectClasses: {7}( 0.9.2342.19200300.100.4.15 NAME 'dNSDomain' SUP domai
n STRUCTURAL MAY ( ARecord $ MDRecord $ MXRecord $ NSRecord $ SOARecord $ C
NAMERecord ) )
olcObjectClasses: {8}( 0.9.2342.19200300.100.4.17 NAME 'domainRelatedObject'
DESC 'RFC1274: an object related to an domain' SUP top AUXILIARY MUST asso
ciatedDomain )
olcObjectClasses: {9}( 0.9.2342.19200300.100.4.18 NAME 'friendlyCountry' SUP
country STRUCTURAL MUST friendlyCountryName )
olcObjectClasses: {10}( 0.9.2342.19200300.100.4.20 NAME 'pilotOrganization'
SUP ( organization $ organizationalUnit ) STRUCTURAL MAY buildingName )
olcObjectClasses: {11}( 0.9.2342.19200300.100.4.21 NAME 'pilotDSA' SUP dsa S
TRUCTURAL MAY dSAQuality )
olcObjectClasses: {12}( 0.9.2342.19200300.100.4.22 NAME 'qualityLabelledData
' SUP top AUXILIARY MUST dsaQuality MAY ( subtreeMinimumQuality $ subtreeMa
ximumQuality ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c286ac6-7ddc-103b-9ec0-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.335841Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,108 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 8c7d43da
dn: cn={2}nis
objectClass: olcSchemaConfig
cn: {2}nis
olcAttributeTypes: {0}( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field;
the common name' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5Substrings
Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' DESC 'The absol
ute path to the home directory' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4
.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.1.1.1.4 NAME 'loginShell' DESC 'The path to
the login shell' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121
.1.26 SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange' EQUALITY int
egerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.1.1.1.6 NAME 'shadowMin' EQUALITY integerMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.1.1.1.7 NAME 'shadowMax' EQUALITY integerMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.1.1.1.8 NAME 'shadowWarning' EQUALITY intege
rMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 1.3.6.1.1.1.1.9 NAME 'shadowInactive' EQUALITY integ
erMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.1.1.1.10 NAME 'shadowExpire' EQUALITY intege
rMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.1.1.1.11 NAME 'shadowFlag' EQUALITY integerM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {10}( 1.3.6.1.1.1.1.12 NAME 'memberUid' EQUALITY caseExac
tIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.26 )
olcAttributeTypes: {11}( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup' EQUALITY
caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.146
6.115.121.1.26 )
olcAttributeTypes: {12}( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' DESC 'Net
group triple' SYNTAX 1.3.6.1.1.1.0.0 )
olcAttributeTypes: {13}( 1.3.6.1.1.1.1.15 NAME 'ipServicePort' EQUALITY inte
gerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {14}( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol' SUP name
)
olcAttributeTypes: {15}( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber' EQUALITY i
ntegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber' EQUALITY integ
erMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {17}( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber' DESC 'IP addre
ss' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
olcAttributeTypes: {18}( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber' DESC 'IP ne
twork' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128
} SINGLE-VALUE )
olcAttributeTypes: {19}( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber' DESC 'IP ne
tmask' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128
} SINGLE-VALUE )
olcAttributeTypes: {20}( 1.3.6.1.1.1.1.22 NAME 'macAddress' DESC 'MAC addres
s' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
olcAttributeTypes: {21}( 1.3.6.1.1.1.1.23 NAME 'bootParameter' DESC 'rpc.boo
tparamd parameter' SYNTAX 1.3.6.1.1.1.0.1 )
olcAttributeTypes: {22}( 1.3.6.1.1.1.1.24 NAME 'bootFile' DESC 'Boot image n
ame' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {23}( 1.3.6.1.1.1.1.26 NAME 'nisMapName' SUP name )
olcAttributeTypes: {24}( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry' EQUALITY caseEx
actIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.26{1024} SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction
of an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ u
idNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ ge
cos $ description ) )
olcObjectClasses: {1}( 1.3.6.1.1.1.2.1 NAME 'shadowAccount' DESC 'Additional
attributes for shadow passwords' SUP top AUXILIARY MUST uid MAY ( userPass
word $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowIna
ctive $ shadowExpire $ shadowFlag $ description ) )
olcObjectClasses: {2}( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction o
f a group of accounts' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( use
rPassword $ memberUid $ description ) )
olcObjectClasses: {3}( 1.3.6.1.1.1.2.3 NAME 'ipService' DESC 'Abstraction an
Internet Protocol service' SUP top STRUCTURAL MUST ( cn $ ipServicePort $
ipServiceProtocol ) MAY description )
olcObjectClasses: {4}( 1.3.6.1.1.1.2.4 NAME 'ipProtocol' DESC 'Abstraction o
f an IP protocol' SUP top STRUCTURAL MUST ( cn $ ipProtocolNumber $ descrip
tion ) MAY description )
olcObjectClasses: {5}( 1.3.6.1.1.1.2.5 NAME 'oncRpc' DESC 'Abstraction of an
ONC/RPC binding' SUP top STRUCTURAL MUST ( cn $ oncRpcNumber $ description
) MAY description )
olcObjectClasses: {6}( 1.3.6.1.1.1.2.6 NAME 'ipHost' DESC 'Abstraction of a
host, an IP device' SUP top AUXILIARY MUST ( cn $ ipHostNumber ) MAY ( l $
description $ manager ) )
olcObjectClasses: {7}( 1.3.6.1.1.1.2.7 NAME 'ipNetwork' DESC 'Abstraction of
an IP network' SUP top STRUCTURAL MUST ( cn $ ipNetworkNumber ) MAY ( ipNe
tmaskNumber $ l $ description $ manager ) )
olcObjectClasses: {8}( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup' DESC 'Abstraction
of a netgroup' SUP top STRUCTURAL MUST cn MAY ( nisNetgroupTriple $ memberN
isNetgroup $ description ) )
olcObjectClasses: {9}( 1.3.6.1.1.1.2.9 NAME 'nisMap' DESC 'A generic abstrac
tion of a NIS map' SUP top STRUCTURAL MUST nisMapName MAY description )
olcObjectClasses: {10}( 1.3.6.1.1.1.2.10 NAME 'nisObject' DESC 'An entry in
a NIS map' SUP top STRUCTURAL MUST ( cn $ nisMapEntry $ nisMapName ) MAY de
scription )
olcObjectClasses: {11}( 1.3.6.1.1.1.2.11 NAME 'ieee802Device' DESC 'A device
with a MAC address' SUP top AUXILIARY MAY macAddress )
olcObjectClasses: {12}( 1.3.6.1.1.1.2.12 NAME 'bootableDevice' DESC 'A devic
e with boot parameters' SUP top AUXILIARY MAY ( bootFile $ bootParameter )
)
structuralObjectClass: olcSchemaConfig
entryUUID: 5c2966ce-7ddc-103b-9ec1-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.342294Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,49 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 b409a0c2
dn: cn={3}inetorgperson
objectClass: olcSchemaConfig
cn: {3}inetorgperson
olcAttributeTypes: {0}( 2.16.840.1.113730.3.1.1 NAME 'carLicense' DESC 'RFC2
798: vehicle license or registration plate' EQUALITY caseIgnoreMatch SUBSTR
caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {1}( 2.16.840.1.113730.3.1.2 NAME 'departmentNumber' DESC
'RFC2798: identifies a department within an organization' EQUALITY caseIgn
oreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1
.15 )
olcAttributeTypes: {2}( 2.16.840.1.113730.3.1.241 NAME 'displayName' DESC 'R
FC2798: preferred name to be used when displaying entries' EQUALITY caseIgn
oreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1
.15 SINGLE-VALUE )
olcAttributeTypes: {3}( 2.16.840.1.113730.3.1.3 NAME 'employeeNumber' DESC '
RFC2798: numerically identifies an employee within an organization' EQUALIT
Y caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.
115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {4}( 2.16.840.1.113730.3.1.4 NAME 'employeeType' DESC 'RF
C2798: type of employment for a person' EQUALITY caseIgnoreMatch SUBSTR cas
eIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {5}( 0.9.2342.19200300.100.1.60 NAME 'jpegPhoto' DESC 'RF
C2798: a JPEG image' SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 )
olcAttributeTypes: {6}( 2.16.840.1.113730.3.1.39 NAME 'preferredLanguage' DE
SC 'RFC2798: preferred written or spoken language for a person' EQUALITY ca
seIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.15 SINGLE-VALUE )
olcAttributeTypes: {7}( 2.16.840.1.113730.3.1.40 NAME 'userSMIMECertificate'
DESC 'RFC2798: PKCS#7 SignedData used to support S/MIME' SYNTAX 1.3.6.1.4.
1.1466.115.121.1.5 )
olcAttributeTypes: {8}( 2.16.840.1.113730.3.1.216 NAME 'userPKCS12' DESC 'RF
C2798: personal identity information, a PKCS #12 PFX' SYNTAX 1.3.6.1.4.1.14
66.115.121.1.5 )
olcObjectClasses: {0}( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' DESC 'RF
C2798: Internet Organizational Person' SUP organizationalPerson STRUCTURAL
MAY ( audio $ businessCategory $ carLicense $ departmentNumber $ displayNam
e $ employeeNumber $ employeeType $ givenName $ homePhone $ homePostalAddre
ss $ initials $ jpegPhoto $ labeledURI $ mail $ manager $ mobile $ o $ page
r $ photo $ roomNumber $ secretary $ uid $ userCertificate $ x500uniqueIden
tifier $ preferredLanguage $ userSMIMECertificate $ userPKCS12 ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c2a5016-7ddc-103b-9ec2-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.348266Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,65 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 cded21ef
dn: cn={4}ppolicy
objectClass: olcSchemaConfig
cn: {4}ppolicy
olcAttributeTypes: {0}( 1.3.6.1.4.1.42.2.27.8.1.1 NAME 'pwdAttribute' EQUALI
TY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
olcAttributeTypes: {1}( 1.3.6.1.4.1.42.2.27.8.1.2 NAME 'pwdMinAge' EQUALITY
integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.
1.27 SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.4.1.42.2.27.8.1.3 NAME 'pwdMaxAge' EQUALITY
integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.
1.27 SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.42.2.27.8.1.4 NAME 'pwdInHistory' EQUALI
TY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.1
21.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.4.1.42.2.27.8.1.5 NAME 'pwdCheckQuality' EQU
ALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.11
5.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.42.2.27.8.1.6 NAME 'pwdMinLength' EQUALI
TY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.4.1.42.2.27.8.1.7 NAME 'pwdExpireWarning' EQ
UALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.
115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 1.3.6.1.4.1.42.2.27.8.1.8 NAME 'pwdGraceAuthNLimit'
EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.146
6.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.4.1.42.2.27.8.1.9 NAME 'pwdLockout' EQUALITY
booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.4.1.42.2.27.8.1.10 NAME 'pwdLockoutDuration'
EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.14
66.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {10}( 1.3.6.1.4.1.42.2.27.8.1.11 NAME 'pwdMaxFailure' EQU
ALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {11}( 1.3.6.1.4.1.42.2.27.8.1.12 NAME 'pwdFailureCountInt
erval' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.
4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {12}( 1.3.6.1.4.1.42.2.27.8.1.13 NAME 'pwdMustChange' EQU
ALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {13}( 1.3.6.1.4.1.42.2.27.8.1.14 NAME 'pwdAllowUserChange
' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {14}( 1.3.6.1.4.1.42.2.27.8.1.15 NAME 'pwdSafeModify' EQU
ALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {15}( 1.3.6.1.4.1.4754.1.99.1 NAME 'pwdCheckModule' DESC
'Loadable module that instantiates "check_password() function' EQUALITY cas
eExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.4.1.42.2.27.8.1.30 NAME 'pwdMaxRecordedFail
ure' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.
1.1466.115.121.1.27 SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.4.1.4754.2.99.1 NAME 'pwdPolicyChecker' SUP t
op AUXILIARY MAY pwdCheckModule )
olcObjectClasses: {1}( 1.3.6.1.4.1.42.2.27.8.2.1 NAME 'pwdPolicy' SUP top AU
XILIARY MUST pwdAttribute MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdC
heckQuality $ pwdMinLength $ pwdExpireWarning $ pwdGraceAuthNLimit $ pwdLoc
kout $ pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $ pwdMu
stChange $ pwdAllowUserChange $ pwdSafeModify $ pwdMaxRecordedFailure ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c51364a-7ddc-103b-8d41-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.603187Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,175 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 7d21399c
dn: cn={5}kopano
objectClass: olcSchemaConfig
cn: {5}kopano
olcAttributeTypes: {0}( 1.3.6.1.4.1.47732.1.1.1.1 NAME 'kopanoQuotaOverride'
DESC 'KOPANO: Override child quota' EQUALITY integerMatch SYNTAX 1.3.6.1.4
.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.4.1.47732.1.1.1.2 NAME 'kopanoQuotaWarn' DES
C 'KOPANO: Warning quota size in MB' EQUALITY integerMatch SYNTAX 1.3.6.1.4
.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.4.1.47732.1.1.1.3 NAME 'kopanoQuotaSoft' DES
C 'KOPANO: Soft quota size in MB' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.47732.1.1.1.4 NAME 'kopanoQuotaHard' DES
C 'KOPANO: Hard quota size in MB' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.4.1.47732.1.1.1.5 NAME 'kopanoUserDefaultQuo
taOverride' DESC 'KOPANO: Override User default quota for children' EQUALIT
Y integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.47732.1.1.1.6 NAME 'kopanoUserDefaultQuo
taWarn' DESC 'KOPANO: User default warning quota size in MB' EQUALITY integ
erMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.4.1.47732.1.1.1.7 NAME 'kopanoUserDefaultQuo
taSoft' DESC 'KOPANO: User default soft quota size in MB' EQUALITY integerM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 1.3.6.1.4.1.47732.1.1.1.8 NAME 'kopanoUserDefaultQuo
taHard' DESC 'KOPANO: User default hard quota size in MB' EQUALITY integerM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.4.1.47732.1.1.2.1 NAME 'kopanoAdmin' DESC 'K
OPANO: Administrator of kopano' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.14
66.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.4.1.47732.1.1.2.2 NAME 'kopanoSharedStoreOnl
y' DESC 'KOPANO: is store a shared store' EQUALITY integerMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {10}( 1.3.6.1.4.1.47732.1.1.2.3 NAME 'kopanoAccount' DESC
'KOPANO: entry is a part of kopano' EQUALITY integerMatch SYNTAX 1.3.6.1.4
.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {11}( 1.3.6.1.4.1.47732.1.1.2.4 NAME 'kopanoSendAsPrivile
ge' DESC 'KOPANO: Users may directly send email as this user' EQUALITY case
IgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.15 )
olcAttributeTypes: {12}( 1.3.6.1.4.1.47732.1.1.2.5 NAME 'kopanoMrAccept' DES
C 'KOPANO: user should auto-accept meeting requests' EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {13}( 1.3.6.1.4.1.47732.1.1.2.6 NAME 'kopanoMrDeclineConf
lict' DESC 'KOPANO: user should automatically decline conflicting meeting r
equests' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-
VALUE )
olcAttributeTypes: {14}( 1.3.6.1.4.1.47732.1.1.2.7 NAME 'kopanoMrDeclineRecu
rring' DESC 'KOPANO: user should automatically decline recurring meeting re
quests' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-V
ALUE )
olcAttributeTypes: {15}( 1.3.6.1.4.1.47732.1.1.2.8 NAME 'kopanoId' DESC 'KOP
ANO: Generic unique ID' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.40 SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.4.1.47732.1.1.2.9 NAME 'kopanoResourceType'
DESC 'KOPANO: for shared stores, resource is type Room or Equipment' EQUAL
ITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.146
6.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {17}( 1.3.6.1.4.1.47732.1.1.2.10 NAME 'kopanoResourceCapa
city' DESC 'KOPANO: number of rooms or equipment available' EQUALITY intege
rMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {18}( 1.3.6.1.4.1.47732.1.1.2.11 NAME 'kopanoHidden' DESC
'KOPANO: This object should be hidden from address book' EQUALITY integerM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {19}( 1.3.6.1.4.1.47732.1.1.2.13 NAME 'kopanoEnabledFeatu
res' DESC 'KOPANO: This user has these features explicitly enabled' EQUALIT
Y caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
olcAttributeTypes: {20}( 1.3.6.1.4.1.47732.1.1.2.14 NAME 'kopanoDisabledFeat
ures' DESC 'KOPANO: This user has these features explicitly disabled' EQUAL
ITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
olcAttributeTypes: {21}( 1.3.6.1.4.1.47732.1.1.3.1 NAME 'kopanoAliases' DESC
'KOPANO: All other email addresses for this user' EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {22}( 1.3.6.1.4.1.47732.1.1.4.1 NAME 'kopanoUserServer' D
ESC 'KOPANO: Home server for the user' EQUALITY caseIgnoreMatch SUBSTR case
IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {23}( 1.3.6.1.4.1.47732.1.1.6.1 NAME 'kopanoUserArchiveSe
rvers' DESC 'KOPANO: List of server names that contain an archive store for
the user' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX
1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {24}( 1.3.6.1.4.1.47732.1.1.6.2 NAME 'kopanoUserArchiveCo
uplings' DESC 'KOPANO: List of username:foldername pairs that specify many-
to-one archive locations' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstri
ngsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {25}( 1.3.6.1.4.1.47732.1.2.2.1 NAME 'kopanoSecurityGroup
' DESC 'KOPANO: group has security possibilities' EQUALITY integerMatch SYN
TAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {26}( 1.3.6.1.4.1.47732.1.3.2.4 NAME 'kopanoViewPrivilege
' DESC 'KOPANO: Companies with view privileges over selected company' EQUAL
ITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.146
6.115.121.1.15 )
olcAttributeTypes: {27}( 1.3.6.1.4.1.47732.1.3.2.5 NAME 'kopanoAdminPrivileg
e' DESC 'KOPANO: Users from different companies which are administrator ove
r selected company' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {28}( 1.3.6.1.4.1.47732.1.3.2.6 NAME 'kopanoSystemAdmin'
DESC 'KOPANO: The user who is the system administrator for this company' EQ
UALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {29}( 1.3.6.1.4.1.47732.1.3.1.5 NAME 'kopanoQuotaUserWarn
ingRecipients' DESC 'KOPANO: Users who will receive a notification email wh
en a user exceeds his quota' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubs
tringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {30}( 1.3.6.1.4.1.47732.1.3.1.6 NAME 'kopanoQuotaCompanyW
arningRecipients' DESC 'KOPANO: Users who will receive a notification email
when a company exceeds its quota' EQUALITY caseIgnoreMatch SUBSTR caseIgno
reSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {31}( 1.3.6.1.4.1.47732.1.3.4.1 NAME 'kopanoCompanyServer
' DESC 'KOPANO: Home server for the public folders for a company' EQUALITY
caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.11
5.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {32}( 1.3.6.1.4.1.47732.1.4.4.1 NAME 'kopanoHttpPort' DES
C 'KOPANO: Port for the http connection' EQUALITY integerMatch SYNTAX 1.3.6
.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {33}( 1.3.6.1.4.1.47732.1.4.4.2 NAME 'kopanoSslPort' DESC
'KOPANO: Port for the ssl connection' EQUALITY integerMatch SYNTAX 1.3.6.1
.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {34}( 1.3.6.1.4.1.47732.1.4.4.3 NAME 'kopanoFilePath' DES
C 'KOPANO: The Unix socket or named pipe to the server' EQUALITY caseIgnore
Match SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
olcAttributeTypes: {35}( 1.3.6.1.4.1.47732.1.4.4.4 NAME 'kopanoContainsPubli
c' DESC 'KOPANO: This server contains the public store' EQUALITY integerMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {36}( 1.3.6.1.4.1.47732.1.4.4.6 NAME 'kopanoProxyURL' DES
C 'KOPANO: Full proxy URL for this server' EQUALITY caseIgnoreMatch SUBSTR
caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
)
olcAttributeTypes: {37}( 1.3.6.1.4.1.47732.1.5.5.1 NAME 'kopanoFilter' DESC
'KOPANO: LDAP Filter to apply' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSu
bstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {38}( 1.3.6.1.4.1.47732.1.5.5.2 NAME 'kopanoBase' DESC 'K
OPANO: LDAP Search base to apply' EQUALITY caseIgnoreMatch SUBSTR caseIgnor
eSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.4.1.47732.1.1.0.0 NAME 'kopano-user' DESC 'KO
PANO: an user of Kopano' SUP top AUXILIARY MUST cn MAY ( kopanoQuotaOverrid
e $ kopanoQuotaWarn $ kopanoQuotaSoft $ kopanoSendAsPrivilege $ kopanoQuota
Hard $ kopanoAdmin $ kopanoSharedStoreOnly $ kopanoResourceType $ kopanoRes
ourceCapacity $ kopanoAccount $ kopanoHidden $ kopanoAliases $ kopanoUserSe
rver $ kopanoEnabledFeatures $ kopanoDisabledFeatures $ kopanoUserArchiveSe
rvers $ kopanoUserArchiveCouplings $ uidNumber ) )
olcObjectClasses: {1}( 1.3.6.1.4.1.47732.1.6.0.0 NAME 'kopano-contact' DESC
'KOPANO: a contact of Kopano' SUP top AUXILIARY MUST ( cn $ uidNumber ) MAY
( kopanoSendAsPrivilege $ kopanoHidden $ kopanoAliases $ kopanoAccount ) )
olcObjectClasses: {2}( 1.3.6.1.4.1.47732.1.2.0.0 NAME 'kopano-group' DESC 'K
OPANO: a group of Kopano' SUP top AUXILIARY MUST cn MAY ( kopanoAccount $ k
opanoHidden $ mail $ kopanoAliases $ kopanoSecurityGroup $ kopanoSendAsPriv
ilege $ gidNumber ) )
olcObjectClasses: {3}( 1.3.6.1.4.1.47732.1.3.0.0 NAME 'kopano-company' DESC
'KOPANO: a company of Kopano' SUP top AUXILIARY MUST ou MAY ( kopanoAccount
$ kopanoHidden $ kopanoViewPrivilege $ kopanoAdminPrivilege $ kopanoSystem
Admin $ kopanoQuotaOverride $ kopanoQuotaWarn $ kopanoUserDefaultQuotaOverr
ide $ kopanoUserDefaultQuotaWarn $ kopanoUserDefaultQuotaSoft $ kopanoUserD
efaultQuotaHard $ kopanoQuotaUserWarningRecipients $ kopanoQuotaCompanyWarn
ingRecipients $ kopanoCompanyServer ) )
olcObjectClasses: {4}( 1.3.6.1.4.1.47732.1.4.0.0 NAME 'kopano-server' DESC '
KOPANO: a Kopano server' SUP top AUXILIARY MUST cn MAY ( kopanoAccount $ ko
panoHidden $ kopanoHttpPort $ kopanoSslPort $ kopanoFilePath $ kopanoContai
nsPublic $ kopanoProxyURL ) )
olcObjectClasses: {5}( 1.3.6.1.4.1.47732.1.5.0.0 NAME 'kopano-addresslist' D
ESC 'KOPANO: a Kopano Addresslist' SUP top STRUCTURAL MUST cn MAY ( kopanoA
ccount $ kopanoHidden $ kopanoFilter $ kopanoBase ) )
olcObjectClasses: {6}( 1.3.6.1.4.1.47732.1.7.0.0 NAME 'kopano-dynamicgroup'
DESC 'KOPANO: a Kopano dynamic group' SUP top STRUCTURAL MUST cn MAY ( kopa
noAccount $ kopanoHidden $ mail $ kopanoAliases $ kopanoFilter $ kopanoBase
) )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c5ee420-7ddc-103b-8d42-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.692846Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,18 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 815bdd09
dn: cn={6}openssh-lpk
objectClass: olcSchemaConfig
cn: {6}openssh-lpk
olcAttributeTypes: {0}( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' D
ESC 'MANDATORY: OpenSSH Public key' EQUALITY octetStringMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.40 )
olcObjectClasses: {0}( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' DE
SC 'MANDATORY: OpenSSH LPK objectclass' SUP top AUXILIARY MAY ( sshPublicKe
y $ uid ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c6226ee-7ddc-103b-8d43-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.714216Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,50 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 38be59a6
dn: cn={7}postfix-book
objectClass: olcSchemaConfig
cn: {7}postfix-book
olcAttributeTypes: {0}( 1.3.6.1.4.1.29426.1.10.1 NAME 'mailHomeDirectory' DE
SC 'The absolute path to the mail user home directory' EQUALITY caseExactIA
5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.4.1.29426.1.10.2 NAME 'mailAlias' DESC 'RFC8
22 Mailbox - mail alias' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5Su
bstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
olcAttributeTypes: {2}( 1.3.6.1.4.1.29426.1.10.3 NAME 'mailUidNumber' DESC '
UID required to access the mailbox' EQUALITY integerMatch SYNTAX 1.3.6.1.4.
1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.29426.1.10.4 NAME 'mailGidNumber' DESC '
GID required to access the mailbox' EQUALITY integerMatch SYNTAX 1.3.6.1.4.
1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.4.1.29426.1.10.5 NAME 'mailEnabled' DESC 'TR
UE to enable, FALSE to disable account' EQUALITY booleanMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.29426.1.10.6 NAME 'mailGroupMember' DESC
'Name of a mail distribution list' EQUALITY caseExactIA5Match SYNTAX 1.3.6
.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {6}( 1.3.6.1.4.1.29426.1.10.7 NAME 'mailQuota' DESC 'Mail
quota limit in kilobytes' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.14
66.115.121.1.26 )
olcAttributeTypes: {7}( 1.3.6.1.4.1.29426.1.10.8 NAME 'mailStorageDirectory'
DESC 'The absolute path to the mail users mailbox' EQUALITY caseExactIA5Ma
tch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.4.1.29426.1.10.9 NAME 'mailSieveRuleSource'
DESC 'Sun ONE Messaging Server defined attribute' SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.26 X-ORIGIN 'Sun ONE Messaging Server' )
olcAttributeTypes: {9}( 1.3.6.1.4.1.29426.1.10.10 NAME 'mailForwardingAddres
s' DESC 'Address(es) to forward all incoming messages to.' EQUALITY caseIgn
oreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{320} )
olcObjectClasses: {0}( 1.3.6.1.4.1.29426.1.2.2.1 NAME 'PostfixBookMailAccoun
t' DESC 'Mail account used in Postfix Book' SUP top AUXILIARY MUST mail MAY
( mailHomeDirectory $ mailAlias $ mailGroupMember $ mailUidNumber $ mailGi
dNumber $ mailEnabled $ mailQuota $ mailStorageDirectory $ mailSieveRuleSou
rce ) )
olcObjectClasses: {1}( 1.3.6.1.4.1.29426.1.2.2.2 NAME 'PostfixBookMailForwar
d' DESC 'Mail forward used in Postfix Book' SUP top AUXILIARY MUST ( mail $
mailAlias ) MAY mailForwardingAddress )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c658af0-7ddc-103b-8d44-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.736438Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,235 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 41b81a66
dn: cn={8}samba
objectClass: olcSchemaConfig
cn: {8}samba
olcAttributeTypes: {0}( 1.3.6.1.4.1.7165.2.1.24 NAME 'sambaLMPassword' DESC
'LanManager Password' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.26{32} SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.4.1.7165.2.1.25 NAME 'sambaNTPassword' DESC
'MD4 hash of the unicode password' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6
.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.4.1.7165.2.1.26 NAME 'sambaAcctFlags' DESC '
Account Flags' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.
1.26{16} SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.7165.2.1.27 NAME 'sambaPwdLastSet' DESC
'Timestamp of the last password update' EQUALITY integerMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.4.1.7165.2.1.28 NAME 'sambaPwdCanChange' DES
C 'Timestamp of when the user is allowed to update the password' EQUALITY i
ntegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.7165.2.1.29 NAME 'sambaPwdMustChange' DE
SC 'Timestamp of when the password will expire' EQUALITY integerMatch SYNTA
X 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.4.1.7165.2.1.30 NAME 'sambaLogonTime' DESC '
Timestamp of last logon' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 1.3.6.1.4.1.7165.2.1.31 NAME 'sambaLogoffTime' DESC
'Timestamp of last logoff' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.11
5.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.4.1.7165.2.1.32 NAME 'sambaKickoffTime' DESC
'Timestamp of when the user will be logged off automatically' EQUALITY int
egerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.4.1.7165.2.1.48 NAME 'sambaBadPasswordCount'
DESC 'Bad password attempt count' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1
.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {10}( 1.3.6.1.4.1.7165.2.1.49 NAME 'sambaBadPasswordTime'
DESC 'Time of the last bad password attempt' EQUALITY integerMatch SYNTAX
1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {11}( 1.3.6.1.4.1.7165.2.1.55 NAME 'sambaLogonHours' DESC
'Logon Hours' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.
1.26{42} SINGLE-VALUE )
olcAttributeTypes: {12}( 1.3.6.1.4.1.7165.2.1.33 NAME 'sambaHomeDrive' DESC
'Driver letter of home directory mapping' EQUALITY caseIgnoreIA5Match SYNTA
X 1.3.6.1.4.1.1466.115.121.1.26{4} SINGLE-VALUE )
olcAttributeTypes: {13}( 1.3.6.1.4.1.7165.2.1.34 NAME 'sambaLogonScript' DES
C 'Logon script path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.15{255} SINGLE-VALUE )
olcAttributeTypes: {14}( 1.3.6.1.4.1.7165.2.1.35 NAME 'sambaProfilePath' DES
C 'Roaming profile path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.15{255} SINGLE-VALUE )
olcAttributeTypes: {15}( 1.3.6.1.4.1.7165.2.1.36 NAME 'sambaUserWorkstations
' DESC 'List of user workstations the user is allowed to logon to' EQUALITY
caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.4.1.7165.2.1.37 NAME 'sambaHomePath' DESC '
Home directory UNC path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.15{128} )
olcAttributeTypes: {17}( 1.3.6.1.4.1.7165.2.1.38 NAME 'sambaDomainName' DESC
'Windows NT domain to which the user belongs' EQUALITY caseIgnoreMatch SYN
TAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {18}( 1.3.6.1.4.1.7165.2.1.47 NAME 'sambaMungedDial' DESC
'Base64 encoded user parameter string' EQUALITY caseExactMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.15{1050} )
olcAttributeTypes: {19}( 1.3.6.1.4.1.7165.2.1.54 NAME 'sambaPasswordHistory'
DESC 'Concatenated MD5 hashes of the salted NT passwords used on this acco
unt' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} )
olcAttributeTypes: {20}( 1.3.6.1.4.1.7165.2.1.20 NAME 'sambaSID' DESC 'Secur
ity ID' EQUALITY caseIgnoreIA5Match SUBSTR caseExactIA5SubstringsMatch SYNT
AX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE )
olcAttributeTypes: {21}( 1.3.6.1.4.1.7165.2.1.23 NAME 'sambaPrimaryGroupSID'
DESC 'Primary Group Security ID' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.
1.4.1.1466.115.121.1.26{64} SINGLE-VALUE )
olcAttributeTypes: {22}( 1.3.6.1.4.1.7165.2.1.51 NAME 'sambaSIDList' DESC 'S
ecurity ID List' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.26{64} )
olcAttributeTypes: {23}( 1.3.6.1.4.1.7165.2.1.19 NAME 'sambaGroupType' DESC
'NT Group Type' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE )
olcAttributeTypes: {24}( 1.3.6.1.4.1.7165.2.1.21 NAME 'sambaNextUserRid' DES
C 'Next NT rid to give our for users' EQUALITY integerMatch SYNTAX 1.3.6.1.
4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {25}( 1.3.6.1.4.1.7165.2.1.22 NAME 'sambaNextGroupRid' DE
SC 'Next NT rid to give out for groups' EQUALITY integerMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {26}( 1.3.6.1.4.1.7165.2.1.39 NAME 'sambaNextRid' DESC 'N
ext NT rid to give out for anything' EQUALITY integerMatch SYNTAX 1.3.6.1.4
.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {27}( 1.3.6.1.4.1.7165.2.1.40 NAME 'sambaAlgorithmicRidBa
se' DESC 'Base at which the samba RID generation algorithm should operate'
EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {28}( 1.3.6.1.4.1.7165.2.1.41 NAME 'sambaShareName' DESC
'Share Name' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
olcAttributeTypes: {29}( 1.3.6.1.4.1.7165.2.1.42 NAME 'sambaOptionName' DESC
'Option Name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SY
NTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {30}( 1.3.6.1.4.1.7165.2.1.43 NAME 'sambaBoolOption' DESC
'A boolean option' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1
.7 SINGLE-VALUE )
olcAttributeTypes: {31}( 1.3.6.1.4.1.7165.2.1.44 NAME 'sambaIntegerOption' D
ESC 'An integer option' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.1
21.1.27 SINGLE-VALUE )
olcAttributeTypes: {32}( 1.3.6.1.4.1.7165.2.1.45 NAME 'sambaStringOption' DE
SC 'A string option' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115
.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {33}( 1.3.6.1.4.1.7165.2.1.46 NAME 'sambaStringListOption
' DESC 'A string list option' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1
466.115.121.1.15 )
olcAttributeTypes: {34}( 1.3.6.1.4.1.7165.2.1.53 NAME 'sambaTrustFlags' DESC
'Trust Password Flags' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466
.115.121.1.26 )
olcAttributeTypes: {35}( 1.3.6.1.4.1.7165.2.1.58 NAME 'sambaMinPwdLength' DE
SC 'Minimal password length (default: 5)' EQUALITY integerMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {36}( 1.3.6.1.4.1.7165.2.1.59 NAME 'sambaPwdHistoryLength
' DESC 'Length of Password History Entries (default: 0 => off)' EQUALITY in
tegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {37}( 1.3.6.1.4.1.7165.2.1.60 NAME 'sambaLogonToChgPwd' D
ESC 'Force Users to logon for password change (default: 0 => off, 2 => on)'
EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {38}( 1.3.6.1.4.1.7165.2.1.61 NAME 'sambaMaxPwdAge' DESC
'Maximum password age, in seconds (default: -1 => never expire passwords)'
EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {39}( 1.3.6.1.4.1.7165.2.1.62 NAME 'sambaMinPwdAge' DESC
'Minimum password age, in seconds (default: 0 => allow immediate password c
hange)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-V
ALUE )
olcAttributeTypes: {40}( 1.3.6.1.4.1.7165.2.1.63 NAME 'sambaLockoutDuration'
DESC 'Lockout duration in minutes (default: 30, -1 => forever)' EQUALITY i
ntegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {41}( 1.3.6.1.4.1.7165.2.1.64 NAME 'sambaLockoutObservati
onWindow' DESC 'Reset time after lockout in minutes (default: 30)' EQUALITY
integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {42}( 1.3.6.1.4.1.7165.2.1.65 NAME 'sambaLockoutThreshold
' DESC 'Lockout users after bad logon attempts (default: 0 => off)' EQUALIT
Y integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {43}( 1.3.6.1.4.1.7165.2.1.66 NAME 'sambaForceLogoff' DES
C 'Disconnect Users outside logon hours (default: -1 => off, 0 => on)' EQUA
LITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {44}( 1.3.6.1.4.1.7165.2.1.67 NAME 'sambaRefuseMachinePwd
Change' DESC 'Allow Machine Password changes (default: 0 => off)' EQUALITY
integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {45}( 1.3.6.1.4.1.7165.2.1.68 NAME 'sambaClearTextPasswor
d' DESC 'Clear text password (used for trusted domain passwords)' EQUALITY
octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
olcAttributeTypes: {46}( 1.3.6.1.4.1.7165.2.1.69 NAME 'sambaPreviousClearTex
tPassword' DESC 'Previous clear text password (used for trusted domain pass
words)' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
olcAttributeTypes: {47}( 1.3.6.1.4.1.7165.2.1.70 NAME 'sambaTrustType' DESC
'Type of trust' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE )
olcAttributeTypes: {48}( 1.3.6.1.4.1.7165.2.1.71 NAME 'sambaTrustAttributes'
DESC 'Trust attributes for a trusted domain' EQUALITY integerMatch SYNTAX
1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {49}( 1.3.6.1.4.1.7165.2.1.72 NAME 'sambaTrustDirection'
DESC 'Direction of a trust' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {50}( 1.3.6.1.4.1.7165.2.1.73 NAME 'sambaTrustPartner' DE
SC 'Fully qualified name of the domain with which a trust exists' EQUALITY
caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {51}( 1.3.6.1.4.1.7165.2.1.74 NAME 'sambaFlatName' DESC '
NetBIOS name of a domain' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.
115.121.1.15{128} )
olcAttributeTypes: {52}( 1.3.6.1.4.1.7165.2.1.75 NAME 'sambaTrustAuthOutgoin
g' DESC 'Authentication information for the outgoing portion of a trust' EQ
UALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
olcAttributeTypes: {53}( 1.3.6.1.4.1.7165.2.1.76 NAME 'sambaTrustAuthIncomin
g' DESC 'Authentication information for the incoming portion of a trust' EQ
UALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
olcAttributeTypes: {54}( 1.3.6.1.4.1.7165.2.1.77 NAME 'sambaSecurityIdentifi
er' DESC 'SID of a trusted domain' EQUALITY caseIgnoreIA5Match SUBSTR caseE
xactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALU
E )
olcAttributeTypes: {55}( 1.3.6.1.4.1.7165.2.1.78 NAME 'sambaTrustForestTrust
Info' DESC 'Forest trust information for a trusted domain object' EQUALITY
caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
olcAttributeTypes: {56}( 1.3.6.1.4.1.7165.2.1.79 NAME 'sambaTrustPosixOffset
' DESC 'POSIX offset of a trust' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1
466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {57}( 1.3.6.1.4.1.7165.2.1.80 NAME 'sambaSupportedEncrypt
ionTypes' DESC 'Supported encryption types of a trust' EQUALITY integerMatc
h SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.4.1.7165.2.2.6 NAME 'sambaSamAccount' DESC 'S
amba 3.0 Auxilary SAM Account' SUP top AUXILIARY MUST ( uid $ sambaSID ) MA
Y ( cn $ sambaLMPassword $ sambaNTPassword $ sambaPwdLastSet $ sambaLogonTi
me $ sambaLogoffTime $ sambaKickoffTime $ sambaPwdCanChange $ sambaPwdMustC
hange $ sambaAcctFlags $ displayName $ sambaHomePath $ sambaHomeDrive $ sam
baLogonScript $ sambaProfilePath $ description $ sambaUserWorkstations $ sa
mbaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial $ sambaBadPasswordCo
unt $ sambaBadPasswordTime $ sambaPasswordHistory $ sambaLogonHours ) )
olcObjectClasses: {1}( 1.3.6.1.4.1.7165.2.2.4 NAME 'sambaGroupMapping' DESC
'Samba Group Mapping' SUP top AUXILIARY MUST ( gidNumber $ sambaSID $ samba
GroupType ) MAY ( displayName $ description $ sambaSIDList ) )
olcObjectClasses: {2}( 1.3.6.1.4.1.7165.2.2.14 NAME 'sambaTrustPassword' DES
C 'Samba Trust Password' SUP top STRUCTURAL MUST ( sambaDomainName $ sambaN
TPassword $ sambaTrustFlags ) MAY ( sambaSID $ sambaPwdLastSet ) )
olcObjectClasses: {3}( 1.3.6.1.4.1.7165.2.2.15 NAME 'sambaTrustedDomainPassw
ord' DESC 'Samba Trusted Domain Password' SUP top STRUCTURAL MUST ( sambaDo
mainName $ sambaSID $ sambaClearTextPassword $ sambaPwdLastSet ) MAY sambaP
reviousClearTextPassword )
olcObjectClasses: {4}( 1.3.6.1.4.1.7165.2.2.5 NAME 'sambaDomain' DESC 'Samba
Domain Information' SUP top STRUCTURAL MUST ( sambaDomainName $ sambaSID )
MAY ( sambaNextRid $ sambaNextGroupRid $ sambaNextUserRid $ sambaAlgorithm
icRidBase $ sambaMinPwdLength $ sambaPwdHistoryLength $ sambaLogonToChgPwd
$ sambaMaxPwdAge $ sambaMinPwdAge $ sambaLockoutDuration $ sambaLockoutObse
rvationWindow $ sambaLockoutThreshold $ sambaForceLogoff $ sambaRefuseMachi
nePwdChange ) )
olcObjectClasses: {5}( 1.3.6.1.4.1.7165.2.2.7 NAME 'sambaUnixIdPool' DESC 'P
ool for allocating UNIX uids/gids' SUP top AUXILIARY MUST ( uidNumber $ gid
Number ) )
olcObjectClasses: {6}( 1.3.6.1.4.1.7165.2.2.8 NAME 'sambaIdmapEntry' DESC 'M
apping from a SID to an ID' SUP top AUXILIARY MUST sambaSID MAY ( uidNumber
$ gidNumber ) )
olcObjectClasses: {7}( 1.3.6.1.4.1.7165.2.2.9 NAME 'sambaSidEntry' DESC 'Str
uctural Class for a SID' SUP top STRUCTURAL MUST sambaSID )
olcObjectClasses: {8}( 1.3.6.1.4.1.7165.2.2.10 NAME 'sambaConfig' DESC 'Samb
a Configuration Section' SUP top AUXILIARY MAY description )
olcObjectClasses: {9}( 1.3.6.1.4.1.7165.2.2.11 NAME 'sambaShare' DESC 'Samba
Share Section' SUP top STRUCTURAL MUST sambaShareName MAY description )
olcObjectClasses: {10}( 1.3.6.1.4.1.7165.2.2.12 NAME 'sambaConfigOption' DES
C 'Samba Configuration Option' SUP top STRUCTURAL MUST sambaOptionName MAY
( sambaBoolOption $ sambaIntegerOption $ sambaStringOption $ sambaStringLis
toption $ description ) )
olcObjectClasses: {11}( 1.3.6.1.4.1.7165.2.2.16 NAME 'sambaTrustedDomain' DE
SC 'Samba Trusted Domain Object' SUP top STRUCTURAL MUST cn MAY ( sambaTrus
tType $ sambaTrustAttributes $ sambaTrustDirection $ sambaTrustPartner $ sa
mbaFlatName $ sambaTrustAuthOutgoing $ sambaTrustAuthIncoming $ sambaSecuri
tyIdentifier $ sambaTrustForestTrustInfo $ sambaTrustPosixOffset $ sambaSup
portedEncryptionTypes ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c692e8a-7ddc-103b-8d45-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.760287Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,18 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 6d98bb57
dn: olcDatabase={-1}frontend
objectClass: olcDatabaseConfig
objectClass: olcFrontendConfig
olcDatabase: {-1}frontend
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=extern
al,cn=auth manage by * break
olcAccess: {1}to dn.exact="" by * read
olcAccess: {2}to dn.base="cn=Subschema" by * read
olcSizeLimit: 500
structuralObjectClass: olcDatabaseConfig
entryUUID: 5c22f244-7ddc-103b-9ebc-c740e015b81b
creatorsName: cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.299952Z#000000#000#000000
modifiersName: cn=config
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,16 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 4503eefa
dn: olcDatabase={0}config
objectClass: olcDatabaseConfig
olcDatabase: {0}config
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=extern
al,cn=auth manage by * break
olcRootDN: cn=admin,cn=config
structuralObjectClass: olcDatabaseConfig
entryUUID: 5c248d0c-7ddc-103b-9ebd-c740e015b81b
creatorsName: cn=config
createTimestamp: 20210720192809Z
olcRootPW:: e1NTSEF9eGNpMDV6aExUSXhxL3k0NHYyVHlBR2p0bCsxblV1UHc=
entryCSN: 20210812002157.882687Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210812002157Z
@@ -0,0 +1,31 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 506f4bf6
dn: olcDatabase={1}mdb
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: {1}mdb
olcDbDirectory: /var/lib/ldap
olcSuffix: dc=glauth,dc=com
olcLastMod: TRUE
olcRootDN: cn=admin,dc=glauth,dc=com
olcDbCheckpoint: 512 30
olcDbMaxSize: 1073741824
structuralObjectClass: olcMdbConfig
entryUUID: 5c2cdd40-7ddc-103b-9ec4-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
olcDbIndex: uid eq
olcDbIndex: mail eq
olcDbIndex: memberOf eq
olcDbIndex: entryCSN eq
olcDbIndex: entryUUID eq
olcDbIndex: objectClass eq
olcAccess: {0}to attrs=userPassword,shadowLastChange by self write by dn="cn
=serviceuser,cn=svcaccts,ou=users,dc=glauth,dc=com" write by anonymous auth
by * none
olcAccess: {1}to * by self write by dn="cn=serviceuser,cn=svcaccts,ou=users,
dc=glauth,dc=com" write by users read by * none
olcRootPW:: e1NTSEF9TzNabzVITEhzQXU1dkg5a0txNTlEVFhGclpPVDBPQUg=
entryCSN: 20210812002157.895969Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210812002157Z
@@ -0,0 +1,18 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 5273ae52
dn: olcOverlay={0}memberof
objectClass: olcOverlayConfig
objectClass: olcMemberOf
olcOverlay: {0}memberof
olcMemberOfDangling: ignore
olcMemberOfRefInt: TRUE
olcMemberOfGroupOC: groupOfUniqueNames
olcMemberOfMemberAD: uniqueMember
olcMemberOfMemberOfAD: memberOf
structuralObjectClass: olcMemberOf
entryUUID: 5c74c556-7ddc-103b-8d46-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.836237Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,18 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 a7700674
dn: olcOverlay={1}refint
objectClass: olcOverlayConfig
objectClass: olcRefintConfig
olcOverlay: {1}refint
olcRefintAttribute: owner
olcRefintAttribute: manager
olcRefintAttribute: uniqueMember
olcRefintAttribute: member
olcRefintAttribute: memberOf
structuralObjectClass: olcRefintConfig
entryUUID: 5c78d98e-7ddc-103b-8d47-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.862968Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,4 @@
export PREVIOUS_LDAP_TLS_CA_CRT_PATH=/container/service/slapd/assets/certs/ca.crt
export PREVIOUS_LDAP_TLS_CRT_PATH=/container/service/slapd/assets/certs/ldap.crt
export PREVIOUS_LDAP_TLS_KEY_PATH=/container/service/slapd/assets/certs/ldap.key
export PREVIOUS_LDAP_TLS_DH_PARAM_PATH=/container/service/slapd/assets/certs/dhparam.pem
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+7
View File
@@ -0,0 +1,7 @@
package assets
import "embed"
// Content is the embedded assets.
//go:embed *.tgz js/* css/* *.html
var Content embed.FS
Binary file not shown.
+17 -12
View File
@@ -9,9 +9,9 @@
<title>GLAuth Status</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="assets/glauth.css">
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="assets/css/glauth.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -29,19 +29,18 @@
<div id="statspanel" class="panel panel-default pull-right">
</div>
<h1>GLAuth</h1>
<p>This tool is in development - not quite ready for production!</p>
<div id="headerpanel"></div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6">
<h2>stats</h2>
<h2>frontend stats</h2>
<div id="stats" class="jumbotron">
</div>
</div>
<div class="col-xs-12 col-sm-6">
<h2>backend servers</h2>
<h2>backend (LDAP) servers</h2>
<div id="servers" class="jumbotron">
</div>
</div>
@@ -49,6 +48,12 @@
</div> <!-- /container -->
<script id="header-messages-template" type="text/x-handlebars-template">
{{#each header.messages}}
<span>{{{message}}}</span><br>
{{/each}}
</script>
<script id="statspanel-template" type="text/x-handlebars-template">
<div class="panel-body">
<span class="metric">proxy.version:</span> {{proxy.version}}<br>
@@ -87,10 +92,10 @@
<tbody>
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.3/moment-with-locales.min.js"></script>
<script src="assets/glauth.js"></script>
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/handlebars.min.js"></script>
<script src="assets/js/moment-with-locales.min.js"></script>
<script src="assets/js/glauth.js"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
+20 -2
View File
@@ -1,14 +1,32 @@
var interval = 2000;
var internalsEnabled = false;
$(function() {
updateMetrics();
//updateTail();
$.get("/internals", function() {
internalsEnabled = true;
})
.always(function() {
updateMetrics();
//updateTail();
})
});
var tailId = 0;
function updateMetrics() {
var metrics = $.getJSON( "/debug/vars", function(d) {
var template = Handlebars.compile( $("#header-messages-template").html() );
var messages = [];
if(internalsEnabled) {
messages.push({"message": "Also available: <a href='/internals'>Internals: heap, cache, objects, etc.</a>"});
}
var html = template({
"header": {
"messages": messages
}
});
$("#headerpanel").html(html);
var template = Handlebars.compile( $("#statspanel-template").html() );
var html = template(d);
$("#statspanel").html(html);
+28
View File
File diff suppressed because one or more lines are too long
+4
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
+47 -11
View File
@@ -1,15 +1,29 @@
package config
import "time"
// config file
type Backend struct {
BaseDN string
Datastore string
Insecure bool // For LDAP backend only
Servers []string // For LDAP backend only
NameFormat string
GroupFormat string
SSHKeyAttr string
UseGraphAPI bool // For ownCloud backend only
BaseDN string
Datastore string
Insecure bool // For LDAP and owncloud backend only
Servers []string // For LDAP and owncloud backend only
NameFormat string
GroupFormat string
SSHKeyAttr string
UseGraphAPI bool // For ownCloud backend only
Plugin string // Path to plugin library, for plugin backend only
PluginHandler string // Name of plugin's main handler function
Database string // For Database backends only
AnonymousDSE bool // For Config and Database backends only
}
type Helper struct {
Enabled bool
BaseDN string
Datastore string
Plugin string // Path to plugin library, for plugin backend only
PluginHandler string // Name of plugin's main handler function
Database string // For MySQL backend only TODO REname to match plugin
}
type Frontend struct {
AllowedBaseDNs []string // For LDAP backend only
@@ -31,22 +45,40 @@ type LDAPS struct {
type API struct {
Cert string
Enabled bool
Internals bool
Key string
Listen string
SecretToken string
TLS bool
}
type Behaviors struct {
IgnoreCapabilities bool
LimitFailedBinds bool
NumberOfFailedBinds int
PeriodOfFailedBinds time.Duration
BlockFailedBindsFor time.Duration
PruneSourceTableEvery time.Duration
PruneSourcesOlderThan time.Duration
}
type Capability struct {
Action string
Object string
}
type User struct {
Name string
OtherGroups []int
PassSHA256 string
PassBcrypt string
PassAppSHA256 []string
PassAppBcrypt []string
PrimaryGroup int
Capabilities []Capability
SSHKeys []string
OTPSecret string
Yubikey string
Disabled bool
UnixID int
UnixID int // TODO: remove after deprecating UnixID on User and Group
UIDNumber int
Mail string
LoginShell string
GivenName string
@@ -55,12 +87,16 @@ type User struct {
}
type Group struct {
Name string
UnixID int
UnixID int // TODO: remove after deprecating UnixID on User and Group
GIDNumber int
IncludeGroups []int
}
type Config struct {
API API
Backend Backend
Backend Backend // Deprecated
Backends []Backend
Helper Helper
Behaviors Behaviors
Debug bool
WatchConfig bool
YubikeyClientID string
+50
View File
@@ -0,0 +1,50 @@
package frontend
import (
"context"
"github.com/glauth/glauth/pkg/config"
"github.com/go-logr/logr"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger logr.Logger
Config *config.API
Context context.Context
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val logr.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.API) Option {
return func(o *Options) {
o.Config = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
+26 -43
View File
@@ -1,64 +1,47 @@
package frontend
import (
"bytes"
"fmt"
"io"
"net/http"
"strings"
"github.com/go-logr/logr"
"github.com/glauth/glauth/pkg/assets"
"github.com/glauth/glauth/pkg/config"
logging "github.com/op/go-logging"
)
// RunAPI provides a basic REST API
func RunAPI(log *logging.Logger, cfg *config.Config) {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Debug(fmt.Sprintf("Web: %s", r.URL.Path))
if r.URL.Path != "/" {
log.Debug("Web 404")
http.NotFound(w, r)
func RunAPI(opts ...Option) {
options := newOptions(opts...)
log := options.Logger
cfg := options.Config
register(http.DefaultServeMux, log)
if cfg.TLS {
log.V(3).Info("Starting HTTPS server", "address", cfg.Listen)
err := http.ListenAndServeTLS(cfg.Listen, cfg.Cert, cfg.Key, nil)
if err != nil {
log.Error(err, "Error starting HTTPS server")
return
}
pageTemplate, err := assets.Asset("assets/index.html")
if err != nil {
log.Fatal(fmt.Sprintf("Error with HTTP server template asset: %s", err.Error()))
}
fmt.Fprintf(w, string(pageTemplate))
})
http.HandleFunc("/assets/", webStaticHandler(log))
if cfg.API.TLS {
log.Notice(fmt.Sprintf("Starting HTTPS server on %s", cfg.API.Listen))
err := http.ListenAndServeTLS(cfg.API.Listen, cfg.API.Cert, cfg.API.Key, nil)
if err != nil {
log.Fatal(fmt.Sprintf("Error starting HTTPS server: %s", err.Error()))
}
} else {
log.Notice(fmt.Sprintf("Starting HTTP server on %s", cfg.API.Listen))
err := http.ListenAndServe(cfg.API.Listen, nil)
log.V(3).Info("Starting HTTP server", "address", cfg.Listen)
err := http.ListenAndServe(cfg.Listen, nil)
if err != nil {
log.Fatal(fmt.Sprintf("Error starting HTTP server: %s", err.Error()))
log.Error(err, "Error starting HTTP server")
return
}
}
}
// webStaticHandler serves embedded static web files (js&css)
func webStaticHandler(log *logging.Logger) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
assetPath := r.URL.Path[1:]
staticAsset, err := assets.Asset(assetPath)
if err != nil {
log.Error(err.Error())
func register(mux *http.ServeMux, log logr.Logger) {
fs := http.FileServer(http.FS(assets.Content))
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.V(6).Info("Web", "path", r.URL.Path)
if r.URL.Path != "/" {
log.V(6).Info("Web 404")
http.NotFound(w, r)
return
}
headers := w.Header()
if strings.HasSuffix(assetPath, ".js") {
headers["Content-Type"] = []string{"application/javascript"}
} else if strings.HasSuffix(assetPath, ".css") {
headers["Content-Type"] = []string{"text/css"}
}
io.Copy(w, bytes.NewReader(staticAsset))
}
fs.ServeHTTP(w, r)
})
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
}
+124
View File
@@ -0,0 +1,124 @@
package frontend
import (
"bytes"
"crypto/sha256"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-logr/logr"
"github.com/glauth/glauth/pkg/assets"
)
// TestAPI is a small test to make sure that behavior didn't change for the
// worse when switching over to the go1.16+ "embed" package from go-bindata.
func TestAPI(t *testing.T) {
tt := []struct {
Name string
Path string
Check func(testing.TB, *http.Response)
}{
{
Name: "Root",
Path: "/",
Check: func(t testing.TB, res *http.Response) {
if res.StatusCode != http.StatusOK {
t.Errorf("unexpected response: %v", res.Status)
return
}
h := sha256.New()
if _, err := io.Copy(h, res.Body); err != nil {
t.Error(err)
}
if got, want := h.Sum(nil), mkDigest(t, "index.html"); !bytes.Equal(got, want) {
t.Errorf("bad digest: got: %x, want %x", got, want)
}
},
},
{
Name: "NotFound",
Path: "/glauth.js",
Check: func(t testing.TB, res *http.Response) {
if res.StatusCode == http.StatusNotFound {
return
}
t.Errorf("unexpected response: %v", res.Status)
},
},
{
Name: "Content-Type",
Path: "/assets/glauth.js",
Check: func(t testing.TB, res *http.Response) {
if res.StatusCode != http.StatusOK {
t.Errorf("unexpected response: %v", res.Status)
}
if got, want := res.Header.Get("Content-Type"), `application/javascript`; got != want {
t.Errorf("bad content-type: got: %q, want %q", got, want)
}
h := sha256.New()
if _, err := io.Copy(h, res.Body); err != nil {
t.Error(err)
}
if got, want := h.Sum(nil), mkDigest(t, "glauth.js"); !bytes.Equal(got, want) {
t.Errorf("bad digest: got: %x, want %x", got, want)
}
},
},
{
Name: "Content-Type",
Path: "/assets/glauth.css",
Check: func(t testing.TB, res *http.Response) {
if res.StatusCode != http.StatusOK {
t.Errorf("unexpected response: %v", res.Status)
}
// This isn't exactly the same, it adds a charset argument.
if got, want := res.Header.Get("Content-Type"), `text/css; charset=utf-8`; got != want {
t.Errorf("bad content-type: got: %q, want %q", got, want)
}
h := sha256.New()
if _, err := io.Copy(h, res.Body); err != nil {
t.Error(err)
}
if got, want := h.Sum(nil), mkDigest(t, "glauth.css"); !bytes.Equal(got, want) {
t.Errorf("bad digest: got: %x, want %x", got, want)
}
},
},
}
mux := http.NewServeMux()
register(mux, logr.Discard())
srv := httptest.NewServer(mux)
defer srv.Close()
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, srv.URL+tc.Path, nil)
if err != nil {
t.Fatal(err)
}
res, err := srv.Client().Do(req)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
tc.Check(t, res)
})
}
}
func mkDigest(t testing.TB, path string) []byte {
f, err := assets.Content.Open(path)
if err != nil {
t.Error(err)
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
t.Error(err)
}
return h.Sum(nil)
}
+186
View File
@@ -0,0 +1,186 @@
// Package gologgingr implements github.com/go-logr/logr.Logger in terms of
// github.com/op/go-logging.
package gologgingr
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"github.com/go-logr/logr"
)
// The global verbosity level. See SetVerbosity().
var globalVerbosity int = 0
// SetVerbosity sets the global level against which all info logs will be
// compared. If this is greater than or equal to the "V" of the logger, the
// message will be logged. A higher value here means more logs will be written.
// The previous verbosity value is returned. This is not concurrent-safe -
// callers must be sure to call it from only one goroutine.
func SetVerbosity(v int) int {
old := globalVerbosity
globalVerbosity = v
return old
}
// New returns a logr.Logger which is implemented by Go's standard
// log package, or something like it. See New for details.
func New(opts ...Option) logr.Logger {
options := newOptions(opts...)
l := logger{
llog: options.Logger,
level: 0,
prefix: "",
values: nil,
}
return l
}
// LeveledLogger is the subset of the Go stdlib log.Logger API that is needed for
// this adapter.
type LeveledLogger interface {
Fatal(args ...interface{})
Error(args ...interface{})
Warning(args ...interface{})
Notice(args ...interface{})
Info(args ...interface{})
Debug(args ...interface{})
}
type logger struct {
llog LeveledLogger
level int
prefix string
values []interface{}
depth int
}
func (l logger) clone() logger {
out := l
l.values = copySlice(l.values)
return out
}
func copySlice(in []interface{}) []interface{} {
out := make([]interface{}, len(in))
copy(out, in)
return out
}
// Magic string for intermediate frames that we should ignore.
const autogeneratedFrameName = "<autogenerated>"
type kvPair struct {
key string
val interface{}
}
func flatten(kvList ...interface{}) string {
keys := make([]string, 0, len(kvList))
vals := make(map[string]interface{}, len(kvList))
for i := 0; i < len(kvList); i += 2 {
k, ok := kvList[i].(string)
if !ok {
panic(fmt.Sprintf("key is not a string: %s", pretty(kvList[i])))
}
var v interface{}
if i+1 < len(kvList) {
v = kvList[i+1]
}
keys = append(keys, k)
vals[k] = v
}
sort.Strings(keys)
buf := bytes.Buffer{}
for i, k := range keys {
v := vals[k]
if i > 0 {
buf.WriteRune(' ')
}
buf.WriteString(pretty(k))
buf.WriteString("=")
buf.WriteString(pretty(v))
}
return buf.String()
}
func pretty(value interface{}) string {
jb, _ := json.Marshal(value)
return string(jb)
}
func (l logger) Info(msg string, kvList ...interface{}) {
if l.Enabled() {
lvlStr := flatten("level", l.level)
msgStr := flatten("msg", msg)
fixedStr := flatten(l.values...)
userStr := flatten(kvList...)
l.output(fmt.Sprintln(l.prefix, lvlStr, msgStr, fixedStr, userStr))
}
}
func (l logger) Enabled() bool {
return globalVerbosity >= l.level
}
func (l logger) Error(err error, msg string, kvList ...interface{}) {
msgStr := flatten("msg", msg)
var loggableErr interface{}
if err != nil {
loggableErr = err.Error()
}
errStr := flatten("error", loggableErr)
fixedStr := flatten(l.values...)
userStr := flatten(kvList...)
l.output(fmt.Sprintln(l.prefix, errStr, msgStr, fixedStr, userStr))
}
func (l logger) output(s string) {
switch l.level {
case 0:
l.llog.Error(s)
case 1:
l.llog.Error(s)
case 2:
l.llog.Warning(s)
case 3:
l.llog.Notice(s)
case 4:
l.llog.Info(s)
case 5:
l.llog.Info(s)
default:
l.llog.Debug(s)
}
}
func (l logger) V(level int) logr.InfoLogger {
new := l.clone()
new.level = level
return new
}
// WithName returns a new logr.Logger with the specified name appended. stdr
// uses '/' characters to separate name elements. Callers should not pass '/'
// in the provided name string, but this library does not actually enforce that.
func (l logger) WithName(name string) logr.Logger {
new := l.clone()
if len(l.prefix) > 0 {
new.prefix = l.prefix + "/"
}
new.prefix += name
return new
}
func (l logger) WithValues(kvList ...interface{}) logr.Logger {
new := l.clone()
new.values = append(new.values, kvList...)
return new
}
var _ logr.Logger = logger{}
var _ logr.InfoLogger = logger{}
+27
View File
@@ -0,0 +1,27 @@
package gologgingr
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger LeveledLogger
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val LeveledLogger) Option {
return func(o *Options) {
o.Logger = val
}
}
+165 -241
View File
@@ -1,292 +1,218 @@
package handler
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"net"
"regexp"
"sort"
"strings"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/stats"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
"github.com/op/go-logging"
"github.com/pquerna/otp/totp"
)
type configHandler struct {
log *logging.Logger
backend config.Backend
log logr.Logger
cfg *config.Config
yubikeyAuth *yubigo.YubiAuth
ldohelper LDAPOpsHelper
attmatcher *regexp.Regexp
}
func NewConfigHandler(log *logging.Logger, cfg *config.Config, yubikeyAuth *yubigo.YubiAuth) Handler {
// NewConfigHandler creates a new config backed handler
func NewConfigHandler(opts ...Option) Handler {
options := newOptions(opts...)
handler := configHandler{
log: log,
cfg: cfg,
yubikeyAuth: yubikeyAuth}
backend: options.Backend,
log: options.Logger,
cfg: options.Config, // TODO only used to access Users and Groups, move that to dedicated options
yubikeyAuth: options.YubiAuth,
ldohelper: options.LDAPHelper,
attmatcher: configattributematcher,
}
return handler
}
//
func (h configHandler) GetBackend() config.Backend {
return h.backend
}
func (h configHandler) GetLog() logr.Logger {
return h.log
}
func (h configHandler) GetCfg() *config.Config {
return h.cfg
}
func (h configHandler) GetYubikeyAuth() *yubigo.YubiAuth {
return h.yubikeyAuth
}
// Bind implements a bind request against the config file
func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
return h.ldohelper.Bind(h, bindDN, bindSimplePw, conn)
}
h.log.Debug(fmt.Sprintf("Bind request: bindDN: %s, BaseDN: %s, source: %s", bindDN, h.cfg.Backend.BaseDN, conn.RemoteAddr().String()))
// Search implements a search request against the config file
func (h configHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
return h.ldohelper.Search(h, bindDN, searchReq, conn)
}
stats.Frontend.Add("bind_reqs", 1)
// Add is not supported for a static config file
func (h configHandler) Add(boundDN string, req ldap.AddRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// parse the bindDN - ensure that the bindDN ends with the BaseDN
if !strings.HasSuffix(bindDN, baseDN) {
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, h.cfg.Backend.BaseDN))
// h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, baseDN))
return ldap.LDAPResultInvalidCredentials, nil
}
parts := strings.Split(strings.TrimSuffix(bindDN, baseDN), ",")
groupName := ""
userName := ""
if len(parts) == 1 {
userName = strings.TrimPrefix(parts[0], h.cfg.Backend.NameFormat+"=")
} else if len(parts) == 2 {
userName = strings.TrimPrefix(parts[0], h.cfg.Backend.NameFormat+"=")
groupName = strings.TrimPrefix(parts[1], h.cfg.Backend.GroupFormat+"=")
} else {
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s should have only one or two parts (has %d)", bindDN, len(parts)))
return ldap.LDAPResultInvalidCredentials, nil
}
// Modify is not supported for a static config file
func (h configHandler) Modify(boundDN string, req ldap.ModifyRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// find the user
// Delete is not supported for a static config file
func (h configHandler) Delete(boundDN string, deleteDN string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
func (h configHandler) FindUser(userName string, searchByUPN bool) (f bool, u config.User, err error) {
user := config.User{}
found := false
for _, u := range h.cfg.Users {
if u.Name == userName {
found = true
user = u
if searchByUPN {
if strings.EqualFold(u.Mail, userName) {
found = true
user = u
}
} else {
if strings.EqualFold(u.Name, userName) {
found = true
user = u
}
}
}
if !found {
h.log.Warning(fmt.Sprintf("Bind Error: User %s not found.", userName))
return ldap.LDAPResultInvalidCredentials, nil
}
// find the group
return found, user, nil
}
func (h configHandler) FindGroup(groupName string) (f bool, g config.Group, err error) {
// TODO Does g get erased, and above does u get erased?
// TODO and what about f?
group := config.Group{}
found = false
found := false
for _, g := range h.cfg.Groups {
if g.Name == groupName {
if strings.EqualFold(g.Name, groupName) {
found = true
group = g
}
}
if !found {
h.log.Warning(fmt.Sprintf("Bind Error: Group %s not found.", groupName))
return ldap.LDAPResultInvalidCredentials, nil
}
// validate group membership
if user.PrimaryGroup != group.UnixID {
h.log.Warning(fmt.Sprintf("Bind Error: User %s primary group is not %s.", userName, groupName))
return ldap.LDAPResultInvalidCredentials, nil
}
validotp := false
if len(user.Yubikey) == 0 && len(user.OTPSecret) == 0 {
validotp = true
}
if len(user.Yubikey) > 0 && h.yubikeyAuth != nil {
if len(bindSimplePw) > 44 {
otp := bindSimplePw[len(bindSimplePw)-44:]
yubikeyid := otp[0:12]
bindSimplePw = bindSimplePw[:len(bindSimplePw)-44]
if user.Yubikey == yubikeyid {
_, ok, _ := h.yubikeyAuth.Verify(otp)
if ok {
validotp = true
}
}
}
}
// Store the full bind password provided before possibly modifying
// in the otp check
// Generate a hash of the provided password
hashFull := sha256.New()
hashFull.Write([]byte(bindSimplePw))
// Test OTP if exists
if len(user.OTPSecret) > 0 && !validotp {
if len(bindSimplePw) > 6 {
otp := bindSimplePw[len(bindSimplePw)-6:]
bindSimplePw = bindSimplePw[:len(bindSimplePw)-6]
validotp = totp.Validate(otp, user.OTPSecret)
}
}
// finally, validate user's pw
// check app passwords first
for index, appPw := range user.PassAppSHA256 {
if appPw != hex.EncodeToString(hashFull.Sum(nil)) {
h.log.Debug(fmt.Sprintf("Attempted to bind app pw #%d - failure as %s from %s", index, bindDN, conn.RemoteAddr().String()))
} else {
stats.Frontend.Add("bind_successes", 1)
h.log.Debug("Bind success using app pw #%d as %s from %s", index, bindDN, conn.RemoteAddr().String())
return ldap.LDAPResultSuccess, nil
}
}
// then check main password with the hash
hash := sha256.New()
hash.Write([]byte(bindSimplePw))
// Then ensure the OTP is valid before checking
if !validotp {
h.log.Warning(fmt.Sprintf("Bind Error: invalid OTP token as %s from %s", bindDN, conn.RemoteAddr().String()))
return ldap.LDAPResultInvalidCredentials, nil
}
// Now, check the hash
if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
h.log.Warning(fmt.Sprintf("Bind Error: invalid credentials as %s from %s", bindDN, conn.RemoteAddr().String()))
return ldap.LDAPResultInvalidCredentials, nil
}
stats.Frontend.Add("bind_successes", 1)
h.log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
return ldap.LDAPResultSuccess, nil
return found, group, nil
}
//
func (h configHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
searchBaseDN := strings.ToLower(searchReq.BaseDN)
h.log.Debug(fmt.Sprintf("Search request as %s from %s for %s", bindDN, conn.RemoteAddr().String(), searchReq.Filter))
stats.Frontend.Add("search_reqs", 1)
// validate the user is authenticated and has appropriate access
if len(bindDN) < 1 {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", bindDN)
}
if !strings.HasSuffix(bindDN, baseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: BindDN %s not in our BaseDN %s", bindDN, h.cfg.Backend.BaseDN)
}
if !strings.HasSuffix(searchBaseDN, h.cfg.Backend.BaseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: search BaseDN %s is not in our BaseDN %s", searchBaseDN, h.cfg.Backend.BaseDN)
}
// return all users in the config file - the LDAP library will filter results for us
func (h configHandler) FindPosixAccounts() (entrylist []*ldap.Entry, err error) {
entries := []*ldap.Entry{}
filterEntity, err := ldap.GetFilterObjectClass(searchReq.Filter)
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error parsing filter: %s", searchReq.Filter)
}
switch filterEntity {
default:
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, searchReq.Filter)
case "posixgroup":
for _, g := range h.cfg.Groups {
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{g.Name}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{g.Name}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s", g.Name)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "gidNumber", Values: []string{fmt.Sprintf("%d", g.UnixID)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixGroup"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "uniqueMember", Values: h.getGroupMembers(g.UnixID)})
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: h.getGroupMemberIDs(g.UnixID)})
dn := fmt.Sprintf("cn=%s,%s=groups,%s", g.Name, h.cfg.Backend.GroupFormat, h.cfg.Backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
for _, u := range h.cfg.Users {
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{u.Name}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{u.Name}})
if len(u.GivenName) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{Name: "givenName", Values: []string{u.GivenName}})
}
case "posixaccount", "":
for _, u := range h.cfg.Users {
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{u.Name}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{u.Name}})
if len(u.GivenName) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{Name: "givenName", Values: []string{u.GivenName}})
}
if len(u.SN) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{Name: "sn", Values: []string{u.SN}})
}
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{h.getGroupName(u.PrimaryGroup)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "uidNumber", Values: []string{fmt.Sprintf("%d", u.UnixID)}})
if u.Disabled {
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}})
}
if len(u.Mail) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{Name: "mail", Values: []string{u.Mail}})
}
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixAccount", "shadowAccount"}})
if len(u.LoginShell) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{Name: "loginShell", Values: []string{u.LoginShell}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "loginShell", Values: []string{"/bin/bash"}})
}
if len(u.Homedir) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{Name: "homeDirectory", Values: []string{u.Homedir}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "homeDirectory", Values: []string{"/home/" + u.Name}})
}
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s", u.Name)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "gecos", Values: []string{fmt.Sprintf("%s", u.Name)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "gidNumber", Values: []string{fmt.Sprintf("%d", u.PrimaryGroup)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberOf", Values: h.getGroupDNs(append(u.OtherGroups, u.PrimaryGroup))})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowExpire", Values: []string{"-1"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowFlag", Values: []string{"134538308"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowInactive", Values: []string{"-1"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowLastChange", Values: []string{"11000"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowMax", Values: []string{"99999"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowMin", Values: []string{"-1"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowWarning", Values: []string{"7"}})
if len(u.SSHKeys) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{Name: h.cfg.Backend.SSHKeyAttr, Values: u.SSHKeys})
}
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.cfg.Backend.NameFormat, u.Name, h.cfg.Backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.cfg.Backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
if len(u.SN) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{Name: "sn", Values: []string{u.SN}})
}
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{h.getGroupName(u.PrimaryGroup)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "uidNumber", Values: []string{fmt.Sprintf("%d", u.UIDNumber)}})
if u.Disabled {
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}})
}
if len(u.Mail) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{Name: "mail", Values: []string{u.Mail}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "userPrincipalName", Values: []string{u.Mail}})
}
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixAccount", "shadowAccount"}})
if len(u.LoginShell) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{Name: "loginShell", Values: []string{u.LoginShell}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "loginShell", Values: []string{"/bin/bash"}})
}
if len(u.Homedir) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{Name: "homeDirectory", Values: []string{u.Homedir}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "homeDirectory", Values: []string{"/home/" + u.Name}})
}
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s", u.Name)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "gecos", Values: []string{fmt.Sprintf("%s", u.Name)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "gidNumber", Values: []string{fmt.Sprintf("%d", u.PrimaryGroup)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberOf", Values: h.getGroupDNs(append(u.OtherGroups, u.PrimaryGroup))})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowExpire", Values: []string{"-1"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowFlag", Values: []string{"134538308"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowInactive", Values: []string{"-1"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowLastChange", Values: []string{"11000"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowMax", Values: []string{"99999"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowMin", Values: []string{"-1"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowWarning", Values: []string{"7"}})
if len(u.SSHKeys) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{Name: h.backend.SSHKeyAttr, Values: u.SSHKeys})
}
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
stats.Frontend.Add("search_successes", 1)
h.log.Debug(fmt.Sprintf("AP: Search OK: %s", searchReq.Filter))
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
return entries, nil
}
//
func (h configHandler) FindPosixGroups() (entrylist []*ldap.Entry, err error) {
entries := []*ldap.Entry{}
for _, g := range h.cfg.Groups {
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{g.Name}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{g.Name}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s", g.Name)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "gidNumber", Values: []string{fmt.Sprintf("%d", g.GIDNumber)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixGroup"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "uniqueMember", Values: h.getGroupMembers(g.GIDNumber)})
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: h.getGroupMemberIDs(g.GIDNumber)})
dn := fmt.Sprintf("cn=%s,%s=groups,%s", g.Name, h.backend.GroupFormat, h.backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
return entries, nil
}
// Close does not actually close anything, because the config data is kept in memory
func (h configHandler) Close(boundDn string, conn net.Conn) error {
stats.Frontend.Add("closes", 1)
return nil
}
//
func (h configHandler) getGroupMembers(gid int) []string {
members := make(map[string]bool)
for _, u := range h.cfg.Users {
if u.PrimaryGroup == gid {
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.cfg.Backend.NameFormat, u.Name, h.cfg.Backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.cfg.Backend.BaseDN)
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
members[dn] = true
} else {
for _, othergid := range u.OtherGroups {
if othergid == gid {
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.cfg.Backend.NameFormat, u.Name, h.cfg.Backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.cfg.Backend.BaseDN)
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
members[dn] = true
}
}
@@ -294,7 +220,7 @@ func (h configHandler) getGroupMembers(gid int) []string {
}
for _, g := range h.cfg.Groups {
if gid == g.UnixID {
if gid == g.GIDNumber {
for _, includegroupid := range g.IncludeGroups {
if includegroupid != gid {
includegroupmembers := h.getGroupMembers(includegroupid)
@@ -308,7 +234,7 @@ func (h configHandler) getGroupMembers(gid int) []string {
}
m := []string{}
for k, _ := range members {
for k := range members {
m = append(m, k)
}
@@ -317,7 +243,6 @@ func (h configHandler) getGroupMembers(gid int) []string {
return m
}
//
func (h configHandler) getGroupMemberIDs(gid int) []string {
members := make(map[string]bool)
for _, u := range h.cfg.Users {
@@ -333,10 +258,10 @@ func (h configHandler) getGroupMemberIDs(gid int) []string {
}
for _, g := range h.cfg.Groups {
if gid == g.UnixID {
if gid == g.GIDNumber {
for _, includegroupid := range g.IncludeGroups {
if includegroupid == gid {
h.log.Warning(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
h.log.V(2).Info("Ignoring myself as included group", "groupid", includegroupid)
} else {
includegroupmemberids := h.getGroupMemberIDs(includegroupid)
@@ -349,7 +274,7 @@ func (h configHandler) getGroupMemberIDs(gid int) []string {
}
m := []string{}
for k, _ := range members {
for k := range members {
m = append(m, k)
}
@@ -363,14 +288,14 @@ func (h configHandler) getGroupDNs(gids []int) []string {
groups := make(map[string]bool)
for _, gid := range gids {
for _, g := range h.cfg.Groups {
if g.UnixID == gid {
dn := fmt.Sprintf("cn=%s,%s=groups,%s", g.Name, h.cfg.Backend.GroupFormat, h.cfg.Backend.BaseDN)
if g.GIDNumber == gid {
dn := fmt.Sprintf("cn=%s,%s=groups,%s", g.Name, h.backend.GroupFormat, h.backend.BaseDN)
groups[dn] = true
}
for _, includegroupid := range g.IncludeGroups {
if includegroupid == gid && g.UnixID != gid {
includegroupdns := h.getGroupDNs([]int{g.UnixID})
if includegroupid == gid && g.GIDNumber != gid {
includegroupdns := h.getGroupDNs([]int{g.GIDNumber})
for _, includegroupdn := range includegroupdns {
groups[includegroupdn] = true
@@ -381,7 +306,7 @@ func (h configHandler) getGroupDNs(gids []int) []string {
}
g := []string{}
for k, _ := range groups {
for k := range groups {
g = append(g, k)
}
@@ -390,10 +315,9 @@ func (h configHandler) getGroupDNs(gids []int) []string {
return g
}
//
func (h configHandler) getGroupName(gid int) string {
for _, g := range h.cfg.Groups {
if g.UnixID == gid {
if g.GIDNumber == gid {
return g.Name
}
}
+27 -2
View File
@@ -1,10 +1,35 @@
package handler
import "github.com/nmcclain/ldap"
import (
"github.com/glauth/glauth/pkg/config"
"github.com/nmcclain/ldap"
)
// interface for backend handler
type HelperMaker interface {
FindUser(userName string, searchByUPN bool) (bool, config.User, error)
FindGroup(groupName string) (bool, config.Group, error)
}
// Handler is the common interface for all datastores
type Handler interface {
// read support
ldap.Binder
ldap.Searcher
ldap.Closer
// write support
ldap.Adder
ldap.Modifier // Note: modifying eg the uid or cn might change the dn because the hierarchy is determined by the backend
ldap.Deleter
// helper
HelperMaker
}
// TODO When I grow up, I want to handle pointers same as I would in C
// and not need a counter because I would not allocate statically
// but use idiomatic slicing instead
type HandlerWrapper struct {
Handlers []Handler
Count *int
}
+221 -30
View File
@@ -7,6 +7,8 @@ import (
"fmt"
"net"
"net/url"
"os"
"regexp"
"strconv"
"strings"
"sync"
@@ -14,18 +16,24 @@ import (
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/stats"
"github.com/kr/pretty"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
"github.com/op/go-logging"
"github.com/pquerna/otp/totp"
)
// global matcher
var ldapattributematcher = regexp.MustCompile(`(?i)(?P<attribute>[a-zA-Z0-9]+)\s*=\s*(?P<value>.*)`)
type ldapHandler struct {
backend config.Backend
handlers HandlerWrapper
doPing chan bool
log *logging.Logger
cfg *config.Config
log logr.Logger
lock *sync.Mutex // for sessions and servers
sessions map[string]ldapSession
servers []ldapBackend
helper Handler
attm *regexp.Regexp
}
// global lock for ldapHandler sessions & servers manipulation
@@ -51,19 +59,25 @@ type ldapBackend struct {
Ping time.Duration
}
func NewLdapHandler(log *logging.Logger, cfg *config.Config) Handler {
func NewLdapHandler(opts ...Option) Handler {
options := newOptions(opts...)
handler := ldapHandler{ // set non-zero-value defaults here
backend: options.Backend,
handlers: options.Handlers,
sessions: make(map[string]ldapSession),
doPing: make(chan bool),
log: log,
cfg: cfg,
log: options.Logger,
helper: options.Helper,
lock: &ldaplock,
attm: ldapattributematcher,
}
// parse LDAP URLs
for _, ldapurl := range cfg.Backend.Servers {
for _, ldapurl := range handler.backend.Servers {
l, err := parseURL(ldapurl)
if err != nil {
log.Fatal(err)
handler.log.Error(err, "could not parse url")
os.Exit(1)
}
handler.servers = append(handler.servers, l)
}
@@ -76,27 +90,91 @@ func NewLdapHandler(log *logging.Logger, cfg *config.Config) Handler {
//
func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
h.log.Debug(fmt.Sprintf("Bind request as %s from %s", bindDN, conn.RemoteAddr().String()))
h.log.V(6).Info("Bind request", "binddn", bindDN, "src", conn.RemoteAddr())
// if h.helper != nil {
if true {
lowerBindDN := strings.ToLower(bindDN)
baseDN := strings.ToLower("," + h.backend.BaseDN)
parts := strings.Split(strings.TrimSuffix(lowerBindDN, baseDN), ",")
userName := strings.TrimPrefix(parts[0], h.backend.NameFormat+"=")
validotp := false
// Find the user
// We are going to go through all backends and ask
// until we find our user or die of boredom.
user := config.User{}
found := false
for i, handler := range h.handlers.Handlers {
found, user, _ = handler.FindUser(userName, false)
if found {
break
}
if i >= *h.handlers.Count {
break
}
}
if !found {
validotp = true
} else {
if len(user.OTPSecret) == 0 {
validotp = true
} else {
if len(bindSimplePw) > 6 {
otp := bindSimplePw[len(bindSimplePw)-6:]
bindSimplePw = bindSimplePw[:len(bindSimplePw)-6]
validotp = totp.Validate(otp, user.OTPSecret)
}
}
}
if !validotp {
h.log.V(6).Info(fmt.Sprintf("Bind Error: invalid OTP token as %s from %s", bindDN, conn.RemoteAddr().String()))
return ldap.LDAPResultInvalidCredentials, nil
}
}
stats.Frontend.Add("bind_reqs", 1)
s, err := h.getSession(conn)
if err != nil {
stats.Frontend.Add("bind_ldapSession_errors", 1)
h.log.Debug(fmt.Sprintf("Bind ops error as %s from %s == %s", bindDN, conn.RemoteAddr().String(), err.Error()))
h.log.V(6).Info("could not get session", "binddn", bindDN, "src", conn.RemoteAddr(), "error", err)
return ldap.LDAPResultOperationsError, err
}
if err := s.ldap.Bind(bindDN, bindSimplePw); err != nil {
stats.Frontend.Add("bind_errors", 1)
h.log.Debug(fmt.Sprintf("Bind invalid creds as %s from %s", bindDN, conn.RemoteAddr().String()))
h.log.V(6).Info("invalid creds", "binddn", bindDN, "src", conn.RemoteAddr())
return ldap.LDAPResultInvalidCredentials, nil
}
stats.Frontend.Add("bind_successes", 1)
h.log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
h.log.V(6).Info("bind success", "binddn", bindDN, "src", conn.RemoteAddr())
return ldap.LDAPResultSuccess, nil
}
//
func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
h.log.Debug(fmt.Sprintf("Search request as %s from %s for %s", boundDN, conn.RemoteAddr().String(), searchReq.Filter))
wantAttributes := true
wantTypesOnly := false
h.log.V(6).Info("Search request", "binddn", boundDN, "src", conn.RemoteAddr(), "filter", searchReq.Filter)
// "1.1" has special meaning: it does what an empty attribute list would do
// if it didn't already mean "return all attributes"
if len(searchReq.Attributes) == 1 && searchReq.Attributes[0] == "1.1" {
wantAttributes = false
searchReq.Attributes = searchReq.Attributes[:0]
}
// TypesOnly cannot be true: if it were, glauth would not be able to
// match the returned valuea against the query
if searchReq.TypesOnly == true {
wantTypesOnly = true
searchReq.TypesOnly = false
}
stats.Frontend.Add("search_reqs", 1)
s, err := h.getSession(conn)
if err != nil {
@@ -115,26 +193,133 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
searchReq.Controls,
)
h.log.Debug(fmt.Sprintf("Search req to backend: %# v", pretty.Formatter(search)))
h.log.V(6).Info("Search request to backend", "request", search)
sr, err := s.ldap.Search(search)
h.log.Debug(fmt.Sprintf("Backend Search result: %# v", pretty.Formatter(sr)))
h.log.V(6).Info("Backend Search result", "result", sr)
if !wantAttributes {
h.log.V(6).Info("AP: Search Info", "type", "No attributes")
for _, entry := range sr.Entries {
entry.Attributes = entry.Attributes[:0]
}
}
if wantTypesOnly {
h.log.V(6).Info("AP: Search Info", "type", "Types only")
for _, entry := range sr.Entries {
for _, attribute := range entry.Attributes {
attribute.Values = attribute.Values[:0]
}
}
}
// WART used to debug when testing special cases against
// glauth acting as a backend, where it may have
// the same workaround thus hiding the issue
/*
for _, entry := range sr.Entries {
for _, attribute := range entry.Attributes {
if attribute.Name == "objectclass" {
attribute.Name = "bogus"
}
}
}
*/
// If our original attribute is not present, either because:
// 1-This is a root query
// 2-We were asked not to return attributes
// 3-We were asked not to return values
// then we re-insert the correct values in there.
if searchReq.Scope == 0 && searchReq.BaseDN == "" {
h.log.V(6).Info("AP: Search Info", "type", "Root search detected")
}
filters := h.buildReqAttributesList(searchReq.Filter, []string{})
for _, filter := range filters {
attbits := h.attm.FindStringSubmatch(filter)
for _, entry := range sr.Entries {
foundattname := false
for _, attribute := range entry.Attributes {
if strings.ToLower(attribute.Name) == strings.ToLower(attbits[1]) {
foundattname = true
if len(attbits[2]) == 0 {
attribute.Values = []string{attbits[2]}
}
break
}
}
if !foundattname {
entry.Attributes = append(entry.Attributes, &ldap.EntryAttribute{Name: attbits[1], Values: []string{attbits[2]}})
}
}
}
ssr := ldap.ServerSearchResult{
Entries: sr.Entries,
Referrals: sr.Referrals,
Controls: sr.Controls,
}
h.log.Debug(fmt.Sprintf("Frontend Search result: %# v", pretty.Formatter(ssr)))
h.log.V(6).Info("Frontend Search result", "result", ssr)
if err != nil {
e := err.(*ldap.Error)
h.log.Debug(fmt.Sprintf("Search Err: %# v", pretty.Formatter(err)))
h.log.V(6).Info("Search Err", "error", err)
stats.Frontend.Add("search_errors", 1)
ssr.ResultCode = ldap.LDAPResultCode(e.ResultCode)
return ssr, err
}
stats.Frontend.Add("search_successes", 1)
h.log.Debug(fmt.Sprintf("AP: Search OK: %s -> num of entries = %d\n", search.Filter, len(ssr.Entries)))
h.log.V(6).Info("AP: Search OK", "filter", search.Filter, "numentries", len(ssr.Entries))
return ssr, nil
}
func (h ldapHandler) buildReqAttributesList(filter string, filters []string) []string {
maxp := len(filter)
start := -1
descended := false
for p, c := range filter {
if c == '(' {
if p+1 < maxp {
start = p + 1
}
} else if c == ')' {
if start > -1 {
descended = true
filters = h.buildReqAttributesList(filter[start:p], filters)
}
start = -1
}
}
if !descended {
filters = append(filters, filter)
}
return filters
}
// Add is not yet supported for the ldap backend
func (h ldapHandler) Add(boundDN string, req ldap.AddRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Modify is not yet supported for the ldap backend
func (h ldapHandler) Modify(boundDN string, req ldap.ModifyRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Delete is not yet supported for the ldap backend
func (h ldapHandler) Delete(boundDN string, deleteDN string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
func (h ldapHandler) FindUser(userName string, searchByUPN bool) (found bool, user config.User, err error) {
return false, config.User{}, nil
}
func (h ldapHandler) FindGroup(groupName string) (found bool, group config.Group, err error) {
return false, config.Group{}, nil
}
func (h ldapHandler) Close(boundDn string, conn net.Conn) error {
conn.Close() // close connection to the server when then client is closed
h.lock.Lock()
@@ -149,22 +334,28 @@ func (h ldapHandler) Close(boundDn string, conn net.Conn) error {
func (h *ldapHandler) monitorServers() {
err := h.ping()
if err != nil {
h.log.Fatal(err)
h.log.Error(err, "could not ping server")
os.Exit(1)
// TODO return error
}
go func() {
for {
select {
case <-h.doPing:
h.log.Notice("doPing requested due to server failure")
h.log.V(3).Info("doPing requested due to server failure")
err = h.ping()
if err != nil {
h.log.Fatal(err)
h.log.Error(err, "could not ping server")
os.Exit(1)
// TODO return error
}
case <-time.NewTimer(60 * time.Second).C:
h.log.Debug("doPing after timeout")
h.log.V(6).Info("doPing after timeout")
err = h.ping()
if err != nil {
h.log.Fatal(err)
h.log.Error(err, "could not ping server")
os.Exit(1)
// TODO return error
}
}
}
@@ -186,7 +377,7 @@ func (h ldapHandler) getSession(conn net.Conn) (ldapSession, error) {
dest := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
if server.Scheme == "ldaps" {
tlsCfg := &tls.Config{}
if h.cfg.Backend.Insecure {
if h.backend.Insecure {
tlsCfg.InsecureSkipVerify = true
}
l, err = ldap.DialTLS("tcp", dest, tlsCfg)
@@ -218,7 +409,7 @@ func (h ldapHandler) ping() error {
start := time.Now()
if h.servers[0].Scheme == "ldaps" {
tlsCfg := &tls.Config{}
if h.cfg.Backend.Insecure {
if h.backend.Insecure {
tlsCfg.InsecureSkipVerify = true
}
l, err = ldap.DialTLS("tcp", dest, tlsCfg)
@@ -228,7 +419,7 @@ func (h ldapHandler) ping() error {
elapsed := time.Since(start)
h.lock.Lock()
if err != nil || l == nil {
h.log.Error(fmt.Sprintf("Server %s:%d ping failed: %s", s.Hostname, s.Port, err.Error()))
h.log.V(1).Info("Server ping failed", "hostname", s.Hostname, "port", s.Port, "error", err)
h.servers[k].Ping = 0
h.servers[k].Status = Down
} else {
@@ -239,10 +430,10 @@ func (h ldapHandler) ping() error {
}
h.lock.Unlock()
}
h.log.Debug(fmt.Sprintf("Server health: %# v", pretty.Formatter(h.servers)))
h.log.V(6).Info("Server health", "servers", h.servers)
b, err := json.Marshal(h.servers)
if err != nil {
h.log.Error(fmt.Sprintf("Error encoding tail data: %s", err.Error()))
h.log.V(1).Info("Error encoding tail data", "error", err)
}
stats.Backend.Set("servers", stats.Stringer(string(b)))
if healthy == false {
@@ -268,7 +459,7 @@ func (h ldapHandler) getBestServer() (ldapBackend, error) {
if bestping == forever {
return ldapBackend{}, fmt.Errorf("No healthy servers found")
}
h.log.Debug(fmt.Sprintf("Best server: %# v", pretty.Formatter(favorite)))
h.log.V(6).Info("Best server", "favorite", favorite)
return favorite, nil
}
+491
View File
@@ -0,0 +1,491 @@
package handler
import (
"bufio"
"crypto/sha256"
"encoding/hex"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/stats"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
"github.com/pquerna/otp/totp"
"golang.org/x/crypto/bcrypt"
)
var configattributematcher = regexp.MustCompile(`(?i)\((?P<attribute>[a-zA-Z0-9]+)\s*=\s*(?P<value>.*)\)`)
var emailmatcher = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
type LDAPOpsHandler interface {
GetBackend() config.Backend
GetLog() logr.Logger
GetCfg() *config.Config
GetYubikeyAuth() *yubigo.YubiAuth
FindUser(userName string, searchByUPN bool) (f bool, u config.User, err error)
FindGroup(groupName string) (f bool, g config.Group, err error)
FindPosixAccounts() (entrylist []*ldap.Entry, err error)
FindPosixGroups() (entrylist []*ldap.Entry, err error)
}
type failedBind struct {
ts time.Time
}
type sourceInfo struct {
lastSeen time.Time
failures chan failedBind
waitUntil time.Time
}
type LDAPOpsHelper struct {
sources map[string]*sourceInfo
nextPruning time.Time
}
func NewLDAPOpsHelper() LDAPOpsHelper {
helper := LDAPOpsHelper{
sources: make(map[string]*sourceInfo),
nextPruning: time.Now(),
}
return helper
}
func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
if l.isInTimeout(h, conn) {
return ldap.LDAPResultUnwillingToPerform, nil
}
bindDN = strings.ToLower(bindDN)
h.GetLog().V(6).Info("Bind request", "binddn", bindDN, "basedn", h.GetBackend().BaseDN, "src", conn.RemoteAddr())
stats.Frontend.Add("bind_reqs", 1)
// Special Case: bind as anonymous
if bindDN == "" && bindSimplePw == "" {
stats.Frontend.Add("bind_successes", 1)
h.GetLog().V(6).Info("Anonymous Bind success", "src", conn.RemoteAddr())
return ldap.LDAPResultSuccess, nil
}
user, ldapcode := l.findUser(h, bindDN, true /* checkGroup */)
if ldapcode != ldap.LDAPResultSuccess {
return ldapcode, nil
}
validotp := false
if len(user.Yubikey) == 0 && len(user.OTPSecret) == 0 {
validotp = true
}
if len(user.Yubikey) > 0 && h.GetYubikeyAuth() != nil {
if len(bindSimplePw) > 44 {
otp := bindSimplePw[len(bindSimplePw)-44:]
yubikeyid := otp[0:12]
bindSimplePw = bindSimplePw[:len(bindSimplePw)-44]
if user.Yubikey == yubikeyid {
_, ok, _ := h.GetYubikeyAuth().Verify(otp)
if ok {
validotp = true
}
}
}
}
// Store the full bind password provided before possibly modifying
// in the otp check
untouchedBindSimplePw := bindSimplePw
// Test OTP if is exists
if len(user.OTPSecret) > 0 && !validotp {
if len(bindSimplePw) > 6 {
otp := bindSimplePw[len(bindSimplePw)-6:]
bindSimplePw = bindSimplePw[:len(bindSimplePw)-6]
validotp = totp.Validate(otp, user.OTPSecret)
}
}
// finally, validate user's pw
// check app passwords first
if user.PassAppBcrypt != nil {
for index, appPw := range user.PassAppBcrypt {
decoded, err := hex.DecodeString(appPw)
if err != nil {
h.GetLog().V(6).Info("invalid app credentials", "incorrect stored hash", "(omitted)")
} else {
if bcrypt.CompareHashAndPassword(decoded, []byte(untouchedBindSimplePw)) == nil {
stats.Frontend.Add("bind_successes", 1)
h.GetLog().V(6).Info("Bind success using app pw", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
return ldap.LDAPResultSuccess, nil
}
}
}
}
if user.PassAppSHA256 != nil {
hashFull := sha256.New()
hashFull.Write([]byte(untouchedBindSimplePw))
for index, appPw := range user.PassAppSHA256 {
if appPw != hex.EncodeToString(hashFull.Sum(nil)) {
h.GetLog().V(6).Info("Attempt to bind app pw failed", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
} else {
stats.Frontend.Add("bind_successes", 1)
h.GetLog().V(6).Info("Bind success using app pw", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
return ldap.LDAPResultSuccess, nil
}
}
}
// Then ensure the OTP is valid before checking
if !validotp {
h.GetLog().V(2).Info("invalid OTP token", "binddn", bindDN, "src", conn.RemoteAddr())
return ldap.LDAPResultInvalidCredentials, nil
}
// Now, check the pasword hash
if user.PassBcrypt != "" {
decoded, err := hex.DecodeString(user.PassBcrypt)
if err != nil {
h.GetLog().V(2).Info("invalid credentials", "incorrect stored hash", "(omitted)")
return ldap.LDAPResultInvalidCredentials, nil
}
if bcrypt.CompareHashAndPassword(decoded, []byte(bindSimplePw)) != nil {
h.GetLog().V(2).Info("invalid credentials", "binddn", bindDN, "src", conn.RemoteAddr())
l.maybePutInTimeout(h, conn, true)
return ldap.LDAPResultInvalidCredentials, nil
}
}
if user.PassSHA256 != "" {
hash := sha256.New()
hash.Write([]byte(bindSimplePw))
if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
h.GetLog().V(2).Info("invalid credentials", "binddn", bindDN, "src", conn.RemoteAddr())
l.maybePutInTimeout(h, conn, true)
return ldap.LDAPResultInvalidCredentials, nil
}
}
stats.Frontend.Add("bind_successes", 1)
h.GetLog().V(6).Info("Bind success", "binddn", bindDN, "src", conn.RemoteAddr())
return ldap.LDAPResultSuccess, nil
}
func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
if l.isInTimeout(h, conn) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultUnwillingToPerform}, fmt.Errorf("Source is in a timeout")
}
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower(h.GetBackend().BaseDN)
delimitedBaseDN := "," + baseDN
searchBaseDN := strings.ToLower(searchReq.BaseDN)
anonymous := len(bindDN) < 1
var boundUser *config.User
var ldapcode ldap.LDAPResultCode
if !anonymous {
boundUser, ldapcode = l.findUser(h, bindDN, false /* checkGroup */)
if ldapcode != ldap.LDAPResultSuccess {
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Search Error: Potential bypass of BindDN %s", bindDN)
}
// What if this user was bound using their UPN? We still want to enforce baseDN etc so we
// have to rewire them to their original DN which is of course a waste of cycles.
// TODO Down the road we would want to perform lightweight memoization of DNs to UPNs
if emailmatcher.MatchString(bindDN) {
// cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
bindDN = fmt.Sprintf("cn=%s,%s", boundUser.Name, baseDN)
}
}
h.GetLog().V(6).Info("Search request", "binddn", bindDN, "basedn", baseDN, "searchbasedn", searchBaseDN, "src", conn.RemoteAddr(), "scope", searchReq.Scope, "filter", searchReq.Filter)
stats.Frontend.Add("search_reqs", 1)
// special case: querying the root DSE
if searchReq.Scope == 0 && (searchBaseDN == "" || searchBaseDN == baseDN) {
if anonymous && !h.GetBackend().AnonymousDSE {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Root DSE Search Error: Anonymous BindDN not allowed %s", bindDN)
}
h.GetLog().V(6).Info("Search request", "special case", "root DSE")
entries := []*ldap.Entry{}
attrs := []*ldap.EntryAttribute{}
// unfortunately, objectClass is not to be included so we will respect that
// attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"*"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedSASLMechanisms", Values: []string{}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedLDAPVersion", Values: []string{"3"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedControl", Values: []string{}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedCapabilities", Values: []string{}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "subschemaSubentry", Values: []string{"cn=schema"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "serverName", Values: []string{"unknown"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "namingContexts", Values: []string{baseDN}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "defaultNamingContext", Values: []string{baseDN}})
attrs = l.collectRequestedAttributesBack(attrs, searchReq)
entries = append(entries, &ldap.Entry{DN: searchBaseDN, Attributes: attrs})
stats.Frontend.Add("search_successes", 1)
h.GetLog().V(6).Info("AP: Root Search OK", "filter", searchReq.Filter)
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
// validate the user is authenticated and has appropriate access
if anonymous {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", bindDN)
}
if searchBaseDN == "cn=schema" {
h.GetLog().V(6).Info("Search request", "special case", "schema discovery")
entries := []*ldap.Entry{}
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{"schema"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "hasSubordinates", Values: []string{"false"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "modifiersName", Values: []string{"cn=Directory Manager"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "modifyTimeStamp", Values: []string{"Mar 8, 2021, 12:46:29 PM PST (20210308204629Z)"}})
// Iterate through schema attributes provided in schema/ directory
filenames, _ := ioutil.ReadDir("schema")
for _, filename := range filenames {
file, err := os.Open(filepath.Join("schema", filename.Name()))
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Schema Error: attribute %s cannot be read", filename)
}
defer file.Close()
values := []string{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
values = append(values, line)
}
attrs = append(attrs, &ldap.EntryAttribute{Name: filename.Name(), Values: values})
}
attrs = l.collectRequestedAttributesBack(attrs, searchReq)
entries = append(entries, &ldap.Entry{DN: searchBaseDN, Attributes: attrs})
stats.Frontend.Add("search_successes", 1)
h.GetLog().V(6).Info("AP: Schema Discovery OK", "filter", searchReq.Filter)
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
if !strings.HasSuffix(bindDN, delimitedBaseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: BindDN %s not in our BaseDN %s", bindDN, h.GetBackend().BaseDN)
}
if !strings.HasSuffix(searchBaseDN, h.GetBackend().BaseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: search BaseDN %s is not in our BaseDN %s", searchBaseDN, h.GetBackend().BaseDN)
}
// Unless globally ignored, we will check that a user has capabilities allowing them to perform a search in the requested BaseDN
if !h.GetCfg().Behaviors.IgnoreCapabilities && !l.checkCapability(*boundUser, "search", []string{"*", searchBaseDN}) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: no capability allowing BindDN %s to perform search in %s", bindDN, searchBaseDN)
}
// return all users in the config file - the LDAP library will filter results for us
entries := []*ldap.Entry{}
filterEntity, err := ldap.GetFilterObjectClass(searchReq.Filter)
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error parsing filter: %s", searchReq.Filter)
}
switch filterEntity {
default:
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, searchReq.Filter)
case "posixgroup":
posixentries, err := h.FindPosixGroups()
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: Unable to retrieve data [%s]", err.Error())
}
entries = append(entries, posixentries...)
case "posixaccount", "shadowaccount", "":
posixentries, err := h.FindPosixAccounts()
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: Unable to retrieve data [%s]", err.Error())
}
entries = append(entries, posixentries...)
}
stats.Frontend.Add("search_successes", 1)
h.GetLog().V(6).Info("AP: Search OK", "filter", searchReq.Filter)
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
func (l LDAPOpsHelper) findUser(h LDAPOpsHandler, bindDN string, checkGroup bool) (userWhenFound *config.User, resultCode ldap.LDAPResultCode) {
var user config.User
baseDN := strings.ToLower("," + h.GetBackend().BaseDN)
// Special Case: bind using UPN
// Not using mail.ParseAddress/1 because we would allow incorrectly formatted UPNs
if emailmatcher.MatchString(bindDN) {
var foundUser bool // = false
foundUser, user, _ = h.FindUser(bindDN, true)
if !foundUser {
h.GetLog().V(2).Info("User not found", "userprincipalname", bindDN)
return nil, ldap.LDAPResultInvalidCredentials
}
} else {
// parse the bindDN - ensure that the bindDN ends with the BaseDN
if !strings.HasSuffix(bindDN, baseDN) {
h.GetLog().V(2).Info("BindDN not part of our BaseDN", "binddn", bindDN, "basedn", h.GetBackend().BaseDN)
// h.GetLog().Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, baseDN))
return nil, ldap.LDAPResultInvalidCredentials
}
parts := strings.Split(strings.TrimSuffix(bindDN, baseDN), ",")
groupName := ""
userName := ""
if len(parts) == 1 {
userName = strings.TrimPrefix(parts[0], h.GetBackend().NameFormat+"=")
} else if len(parts) == 2 {
userName = strings.TrimPrefix(parts[0], h.GetBackend().NameFormat+"=")
groupName = strings.TrimPrefix(parts[1], h.GetBackend().GroupFormat+"=")
} else {
h.GetLog().V(2).Info("BindDN should have only one or two parts", "binddn", bindDN, "numparts", len(parts))
return nil, ldap.LDAPResultInvalidCredentials
}
// find the user
var foundUser bool // = false
foundUser, user, _ = h.FindUser(userName, false)
if !foundUser {
h.GetLog().V(2).Info("User not found", "username", userName)
return nil, ldap.LDAPResultInvalidCredentials
}
if checkGroup {
// find the group
var group config.Group // = nil
var foundGroup bool // = false
if groupName != "" {
foundGroup, group, _ = h.FindGroup(groupName)
if !foundGroup {
h.GetLog().V(2).Info("Group not found", "groupname", groupName)
return nil, ldap.LDAPResultInvalidCredentials
}
}
// validate group membership
if foundGroup {
if user.PrimaryGroup != group.GIDNumber {
h.GetLog().V(2).Info("primary group mismatch", "username", userName, "primarygroup", user.PrimaryGroup, "groupid", group.GIDNumber)
return nil, ldap.LDAPResultInvalidCredentials
}
}
}
}
return &user, ldap.LDAPResultSuccess
}
func (l LDAPOpsHelper) checkCapability(user config.User, action string, objects []string) bool {
for _, capability := range user.Capabilities {
if capability.Action == action {
for _, object := range objects {
if capability.Object == object {
return true
}
}
}
}
return false
}
// If your query is for, say 'objectClass', then our LDAP
// library will weed out this entry since it does *not* contain an objectclass attribute
// so we are going to re-inject it to keep the LDAP library happy
func (l LDAPOpsHelper) collectRequestedAttributesBack(attrs []*ldap.EntryAttribute, searchReq ldap.SearchRequest) []*ldap.EntryAttribute {
attbits := configattributematcher.FindStringSubmatch(searchReq.Filter)
if len(attbits) == 3 {
foundattname := false
for _, attr := range attrs {
if strings.ToLower(attr.Name) == strings.ToLower(attbits[1]) {
foundattname = true
break
}
}
// the ugly hack: we are going to pretend that the requested attribute is in there
if !foundattname {
attrs = append(attrs, &ldap.EntryAttribute{Name: attbits[1], Values: []string{attbits[2]}})
}
}
return attrs
}
// return true if we should not process the current operation
func (l LDAPOpsHelper) isInTimeout(handler LDAPOpsHandler, conn net.Conn) bool {
cfg := handler.GetCfg()
if !cfg.Behaviors.LimitFailedBinds {
return false
}
remoteAddr := l.getAddr(conn)
now := time.Now()
info, ok := l.sources[remoteAddr]
if !ok {
l.sources[remoteAddr] = &sourceInfo{
lastSeen: now,
failures: make(chan failedBind, cfg.Behaviors.NumberOfFailedBinds),
waitUntil: now,
}
return false
}
// update so that this source does not get pruned
info.lastSeen = now
// if we are in a time out...
if cfg.Behaviors.LimitFailedBinds && info.waitUntil.After(now) {
return true
}
return false
}
func (l LDAPOpsHelper) maybePutInTimeout(handler LDAPOpsHandler, conn net.Conn, noteFailure bool) bool {
cfg := handler.GetCfg()
if !cfg.Behaviors.LimitFailedBinds {
return false
}
remoteAddr := l.getAddr(conn)
now := time.Now()
info, _ := l.sources[remoteAddr]
// if we have a failed bind...
if noteFailure {
info.failures <- failedBind{ts: time.Now()}
// if we now have 3 failed binds in a row
if len(info.failures) == cfg.Behaviors.NumberOfFailedBinds {
// we cannot have more than 3 failed binds in our channel so pop the oldest one
pruned := <-info.failures
// if we have 3 failed bind in a row in less than 3 seconds
if pruned.ts.Add(cfg.Behaviors.PeriodOfFailedBinds * time.Second).After(now) {
// we will wait for 'n' seconds no matter what happens next
info.waitUntil = time.Now().Add(cfg.Behaviors.BlockFailedBindsFor * time.Second)
// purge our failure queue until we resume accepting operations
for len(info.failures) > 0 {
<-info.failures
}
}
}
}
// Prune old IPs
// TODO We should ensure that the time between prunings is bigger than the time to determine rapid failed binds
if l.nextPruning.Before(now) {
for sourceIP, sourceInfo := range l.sources {
if sourceInfo.lastSeen.Add(cfg.Behaviors.PruneSourcesOlderThan * time.Second).Before(now) {
delete(l.sources, sourceIP)
}
}
l.nextPruning = time.Now().Add(cfg.Behaviors.PruneSourceTableEvery * time.Second)
}
return false
}
func (l LDAPOpsHelper) getAddr(conn net.Conn) string {
fullAddr := conn.RemoteAddr().String()
sep := strings.LastIndex(fullAddr, ":")
if sep == -1 {
return fullAddr
}
return fullAddr[0:sep]
}
+102
View File
@@ -0,0 +1,102 @@
package handler
import (
"context"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/go-logr/logr"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Backend config.Backend
Handlers HandlerWrapper
Logger logr.Logger
Config *config.Config
Context *context.Context
YubiAuth *yubigo.YubiAuth
Helper Handler
LDAPHelper LDAPOpsHelper
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// newOptions initializes the available default options.
func NewOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Backend is our current backend
func Backend(val config.Backend) Option {
return func(o *Options) {
o.Backend = val
}
}
// Our friendly handlers for all backends
func Handlers(val HandlerWrapper) Option {
return func(o *Options) {
o.Handlers = val
}
}
// Logger provides a function to set the logger option.
func Logger(val logr.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Context provides a function to set the context option.
func Context(val *context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// YubiAuth provides a function to set the yubiauth option.
func YubiAuth(val *yubigo.YubiAuth) Option {
return func(o *Options) {
o.YubiAuth = val
}
}
// If we specified a helper, for instance for OTP injection
func Helper(val Handler) Option {
return func(o *Options) {
o.Helper = val
}
}
// Global LDAP Handler
func LDAPHelper(val LDAPOpsHelper) Option {
return func(o *Options) {
o.LDAPHelper = val
}
}
+112 -56
View File
@@ -2,6 +2,7 @@ package handler
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
@@ -13,73 +14,80 @@ import (
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/stats"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
"github.com/op/go-logging"
msgraph "github.com/yaegashi/msgraph.go/v1.0"
)
type ownCloudSession struct {
log *logging.Logger
log logr.Logger
client *http.Client
user string
password string
baseUrl string
endpoint string
useGraphAPI bool
}
type ownCloudHandler struct {
log *logging.Logger
cfg *config.Config
meUrl string
backend config.Backend
log logr.Logger
client *http.Client
sessions map[string]ownCloudSession
lock sync.Mutex
lock *sync.Mutex
}
// global lock for ownCloudHandler sessions & servers manipulation
var ownCloudLock sync.Mutex
func (h ownCloudHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.LDAPResultCode, error) {
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
baseDN := strings.ToLower("," + h.backend.BaseDN)
h.log.Debug(fmt.Sprintf("Bind request: bindDN: %s, BaseDN: %s, source: %s", bindDN, h.cfg.Backend.BaseDN, conn.RemoteAddr().String()))
h.log.V(6).Info("Bind request", "binddn", bindDN, "basedn", h.backend.BaseDN, "src", conn.RemoteAddr())
stats.Frontend.Add("bind_reqs", 1)
// parse the bindDN - ensure that the bindDN ends with the BaseDN
if !strings.HasSuffix(bindDN, baseDN) {
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, h.cfg.Backend.BaseDN))
h.log.V(2).Info("BindDN not part of our BaseDN", "binddn", bindDN, "basedn", h.backend.BaseDN)
return ldap.LDAPResultInvalidCredentials, nil
}
parts := strings.Split(strings.TrimSuffix(bindDN, baseDN), ",")
if len(parts) > 2 {
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s should have only one or two parts (has %d)", bindDN, len(parts)))
h.log.V(2).Info("BindDN should have only one or two parts", "binddn", bindDN, "numparts", len(parts))
return ldap.LDAPResultInvalidCredentials, nil
}
userName := strings.TrimPrefix(parts[0], "cn=")
// try to login
if !h.login(userName, bindSimplePw) {
h.log.Warning(fmt.Sprintf("Bind Error: User %s login failed", userName))
h.log.V(2).Info("Login failed", "username", userName, "basedn", h.backend.BaseDN)
return ldap.LDAPResultInvalidCredentials, nil
}
// TODO reuse HTTP connection
id := connID(conn)
h.lock.Lock()
h.sessions[id] = ownCloudSession{
s := ownCloudSession{
log: h.log,
user: userName,
password: bindSimplePw,
baseUrl: h.cfg.Backend.Servers[0],
useGraphAPI: h.cfg.Backend.UseGraphAPI,
endpoint: h.backend.Servers[0],
useGraphAPI: h.backend.UseGraphAPI,
client: h.client,
}
h.lock.Lock()
h.sessions[id] = s
h.lock.Unlock()
stats.Frontend.Add("bind_successes", 1)
h.log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
h.log.V(6).Info("Bind success", "binddn", bindDN, "basedn", h.backend.BaseDN, "src", conn.RemoteAddr())
return ldap.LDAPResultSuccess, nil
}
func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) {
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
baseDN := strings.ToLower("," + h.backend.BaseDN)
searchBaseDN := strings.ToLower(searchReq.BaseDN)
h.log.Debug(fmt.Sprintf("Search request as %s from %s for %s on %s", bindDN, conn.RemoteAddr().String(), searchReq.Filter, searchBaseDN))
h.log.V(6).Info("Search request", "binddn", bindDN, "basedn", baseDN, "src", conn.RemoteAddr(), "filter", searchReq.Filter)
stats.Frontend.Add("search_reqs", 1)
// validate the user is authenticated and has appropriate access
@@ -87,10 +95,10 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("search error: Anonymous BindDN not allowed %s", bindDN)
}
if !strings.HasSuffix(bindDN, baseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("search error: BindDN %s not in our BaseDN %s", bindDN, h.cfg.Backend.BaseDN)
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("search error: BindDN %s not in our BaseDN %s", bindDN, h.backend.BaseDN)
}
if !strings.HasSuffix(searchBaseDN, h.cfg.Backend.BaseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("search error: search BaseDN %s is not in our BaseDN %s", searchBaseDN, h.cfg.Backend.BaseDN)
if !strings.HasSuffix(searchBaseDN, h.backend.BaseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("search error: search BaseDN %s is not in our BaseDN %s", searchBaseDN, h.backend.BaseDN)
}
// return all users in the config file - the LDAP library will filter results for us
entries := []*ldap.Entry{}
@@ -115,7 +123,7 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{*g.ID}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s from ownCloud", *g.ID)}})
// attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{fmt.Sprintf("%d", g.UnixID)}})
// attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{fmt.Sprintf("%d", g.GIDNumber)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixGroup"}})
if g.Members != nil {
members := make([]string, len(g.Members))
@@ -125,20 +133,20 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: members})
}
dn := fmt.Sprintf("cn=%s,%s=groups,%s", *g.ID, h.cfg.Backend.GroupFormat, h.cfg.Backend.BaseDN)
dn := fmt.Sprintf("cn=%s,%s=groups,%s", *g.ID, h.backend.GroupFormat, h.backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
case "posixaccount", "":
userName := ""
if searchBaseDN != strings.ToLower(h.cfg.Backend.BaseDN) {
if searchBaseDN != strings.ToLower(h.backend.BaseDN) {
parts := strings.Split(strings.TrimSuffix(searchBaseDN, baseDN), ",")
if len(parts) == 1 {
if len(parts) >= 1 {
userName = strings.TrimPrefix(parts[0], "cn=")
}
}
users, err := session.getUsers(userName)
if err != nil {
h.log.Debug(err)
h.log.V(6).Info("Could not get user", "username", userName, "err", err)
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, errors.New("search error: error getting users")
}
for _, u := range users {
@@ -155,15 +163,39 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixAccount"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s from ownCloud", *u.ID)}})
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.cfg.Backend.NameFormat, *u.ID, h.cfg.Backend.GroupFormat, "users", h.cfg.Backend.BaseDN)
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, *u.ID, h.backend.GroupFormat, "users", h.backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
}
stats.Frontend.Add("search_successes", 1)
h.log.Debug(fmt.Sprintf("AP: Search OK: %s", searchReq.Filter))
h.log.V(6).Info("AP: Search OK", "filter", searchReq.Filter)
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
// Add is not yet supported for the owncloud backend
func (h ownCloudHandler) Add(boundDN string, req ldap.AddRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Modify is not yet supported for the owncloud backend
func (h ownCloudHandler) Modify(boundDN string, req ldap.ModifyRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Delete is not yet supported for the owncloud backend
func (h ownCloudHandler) Delete(boundDN string, deleteDN string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// FindUser with the given username. Called by the ldap backend to authenticate the bind. Optional
func (h ownCloudHandler) FindUser(userName string, searchByUPN bool) (found bool, user config.User, err error) {
return false, config.User{}, nil
}
func (h ownCloudHandler) FindGroup(groupName string) (found bool, group config.Group, err error) {
return false, config.Group{}, nil
}
func (h ownCloudHandler) Close(boundDN string, conn net.Conn) error {
conn.Close() // close connection to the server when then client is closed
h.lock.Lock()
@@ -175,10 +207,19 @@ func (h ownCloudHandler) Close(boundDN string, conn net.Conn) error {
}
func (h ownCloudHandler) login(name, pw string) bool {
req, _ := http.NewRequest("GET", h.meUrl, nil)
var req *http.Request
if h.backend.UseGraphAPI {
// TODO oc10 graphapi app should implement /me
req, _ = http.NewRequest("GET", h.backend.Servers[0]+"/users/"+name, nil)
} else {
// use provisioning api
meURL := fmt.Sprintf("%s/ocs/v2.php/cloud/user?format=json", h.backend.Servers[0])
req, _ = http.NewRequest("GET", meURL, nil)
}
req.SetBasicAuth(name, pw)
resp, err := http.DefaultClient.Do(req)
if err != nil {
resp, err := h.client.Do(req)
if err != nil || resp.StatusCode != http.StatusOK {
h.log.Error(err, "failed login", "status", resp.StatusCode)
return false
}
defer resp.Body.Close()
@@ -198,11 +239,6 @@ type OCSGroupsResponse struct {
} `json:"ocs"`
}
func (s ownCloudSession) RoundTrip(req *http.Request) (*http.Response, error) {
req.SetBasicAuth(s.user, s.password)
return http.DefaultTransport.RoundTrip(req)
}
func (s ownCloudSession) getGroups() ([]msgraph.Group, error) {
if s.useGraphAPI {
ctx := context.Background()
@@ -210,11 +246,11 @@ func (s ownCloudSession) getGroups() ([]msgraph.Group, error) {
req.Expand("members")
return req.Get(ctx)
}
groupsUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/groups?format=json", s.baseUrl)
groupsUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/groups?format=json", s.endpoint)
req, _ := http.NewRequest("GET", groupsUrl, nil)
req.SetBasicAuth(s.user, s.password)
resp, err := http.DefaultClient.Do(req)
resp, err := s.client.Do(req)
if err != nil {
return nil, err
}
@@ -229,8 +265,8 @@ func (s ownCloudSession) getGroups() ([]msgraph.Group, error) {
}
ret := make([]msgraph.Group, len(f.Ocs.Data.Groups))
for i, v := range f.Ocs.Data.Groups {
ret[i] = msgraph.Group{DirectoryObject: msgraph.DirectoryObject{Entity: msgraph.Entity{ID: &v}}}
for i := range f.Ocs.Data.Groups {
ret[i] = msgraph.Group{DirectoryObject: msgraph.DirectoryObject{Entity: msgraph.Entity{ID: &f.Ocs.Data.Groups[i]}}}
}
return ret, nil
@@ -251,34 +287,41 @@ type OCSUsersResponse struct {
// NewClient returns GraphService request builder with default base URL
func (s ownCloudSession) NewClient() *msgraph.GraphServiceRequestBuilder {
graphAPIBaseUrl := fmt.Sprintf("%s/index.php/apps/graphapi/v1.0/", s.baseUrl)
httpClient := &http.Client{
Transport: s,
}
g := msgraph.NewClient(httpClient)
g.SetURL(graphAPIBaseUrl)
g.SetURL(s.endpoint)
return g
}
func (s ownCloudSession) RoundTrip(req *http.Request) (*http.Response, error) {
req.SetBasicAuth(s.user, s.password)
return s.client.Transport.RoundTrip(req)
}
func (s ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
if s.useGraphAPI {
s.log.V(6).Info("using graph api")
ctx := context.Background()
req := s.NewClient().Users()
if len(userName) > 0 {
s.log.V(6).Info("fetching single user")
u, err := req.ID(userName).Request().Get(ctx)
if err != nil {
return nil, err
}
return []msgraph.User{*u}, nil
}
s.log.V(6).Info("fetching all users")
return req.Request().Get(ctx)
}
usersUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/users?format=json", s.baseUrl)
s.log.V(6).Info("using provisioning api")
usersUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/users?format=json", s.endpoint)
req, _ := http.NewRequest("GET", usersUrl, nil)
req.SetBasicAuth(s.user, s.password)
resp, err := http.DefaultClient.Do(req)
resp, err := s.client.Do(req)
if err != nil {
return nil, err
}
@@ -293,27 +336,40 @@ func (s ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
}
ret := make([]msgraph.User, len(f.Ocs.Data.Users))
for i, v := range f.Ocs.Data.Users {
ret[i] = msgraph.User{DirectoryObject: msgraph.DirectoryObject{Entity: msgraph.Entity{ID: &v}}}
for i := range f.Ocs.Data.Users {
ret[i] = msgraph.User{
DirectoryObject: msgraph.DirectoryObject{
Entity: msgraph.Entity{ID: &f.Ocs.Data.Users[i]},
},
}
}
return ret, nil
}
func (s ownCloudSession) redirectPolicyFunc(req *http.Request, via []*http.Request) error {
s.log.Debug("Setting user and password")
s.log.V(6).Info("Setting user and password", "username", s.user)
req.SetBasicAuth(s.user, s.password)
return nil
}
func NewOwnCloudHandler(log *logging.Logger, cfg *config.Config) Handler {
meUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/user?format=json", cfg.Backend.Servers[0])
func NewOwnCloudHandler(opts ...Option) Handler {
options := newOptions(opts...)
handler := ownCloudHandler{
log: log,
cfg: cfg,
meUrl: meUrl,
return ownCloudHandler{
backend: options.Backend,
log: options.Logger,
sessions: make(map[string]ownCloudSession),
lock: &ownCloudLock,
client: &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: options.Backend.Insecure,
},
},
},
}
return handler
}
+25
View File
@@ -0,0 +1,25 @@
#####################
# Plugins Targets
#####################
# Note: to make a plugin compatible with a binary built in debug mode, add `-gcflags='all=-N -l'`
PLUGIN_OS ?= linux
PLUGIN_ARCH ?= amd64
plugins: plugin_mysql plugin_postgres plugin_sqlite
plugin_mysql: bin/mysql.so
bin/mysql.so: pkg/plugins/mysql.go pkg/plugins/basesqlhandler.go
GOOS=$(PLUGIN_OS) GOARCH=$(PLUGIN_ARCH) go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -buildmode=plugin -o $@ $^
plugin_postgres: bin/postgres.so
bin/postgres.so: pkg/plugins/postgres.go pkg/plugins/basesqlhandler.go
GOOS=$(PLUGIN_OS) GOARCH=$(PLUGIN_ARCH) go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -buildmode=plugin -o $@ $^
plugin_sqlite: bin/sqlite.so
bin/sqlite.so: pkg/plugins/sqlite.go pkg/plugins/basesqlhandler.go
GOOS=$(PLUGIN_OS) GOARCH=$(PLUGIN_ARCH) go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -buildmode=plugin -o $@ $^
+140
View File
@@ -0,0 +1,140 @@
# GLAuth Plugins
This folder contains plugins; that is, backends that are not compiled in GLAuth by default.
To quote 'Butonic' (Jörn Friedrich Dreyer):
> Just keep the 'lightweight' in mind.
To build either back-end, type
```
make plugin_name
```
where 'name' is the plugin's name; so, for instance: `make plugin_sqlite`
To build back-ends for specific architectures, specify `PLUGIN_OS` and `PLUGIN_ARCH` --
For instance, to build the sqlite plugin for the new Mac M1s:
```
make plugin_sqlite PLUGIN_OS=darwin PLUGIN_ARCH=arm64
```
## Database Plugins
To use a database plugin, edit the configuration file (see pkg/plugins/sample-database.cfg) so that:
```
...
[backend]
datastore = "plugin"
plugin = "dynamic library you created using the previous 'make' command"
database = "database connection string"
...
```
so, let's say you built the 'sqlite' plugin, you would now specify its library: `database = sqlite.so`
### SQLite, MySQL, Postgres
Tables:
- users, groups are self-explanatory
- includegroups store the 'includegroups' relationships
- othergroups, on the other hand, are a comma-separated list found in the users table (performance)
Here is how to insert example data using your database's REPL (more detailed information can be found in pkg/plugins/sample-database.cfg)
```sql
INSERT INTO groups(name, gidnumber) VALUES('superheros', 5501);
INSERT INTO groups(name, gidnumber) VALUES('svcaccts', 5502);
INSERT INTO groups(name, gidnumber) VALUES('civilians', 5503);
INSERT INTO groups(name, gidnumber) VALUES('caped', 5504);
INSERT INTO groups(name, gidnumber) VALUES('lovesailing', 5505);
INSERT INTO groups(name, gidnumber) VALUES('smoker', 5506);
INSERT INTO includegroups(parentgroupid, includegroupid) VALUES(5503, 5501);
INSERT INTO includegroups(parentgroupid, includegroupid) VALUES(5504, 5502);
INSERT INTO includegroups(parentgroupid, includegroupid) VALUES(5504, 5501);
INSERT INTO users(name, uidnumber, primarygroup, passsha256) VALUES('hackers', 5001, 5501, '6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a');
INSERT INTO users(name, uidnumber, primarygroup, passsha256) VALUES('johndoe', 5002, 5502, '6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a');
INSERT INTO users(name, mail, uidnumber, primarygroup, passsha256) VALUES('serviceuser', "serviceuser@example.com", 5003, 5502, '652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0');
INSERT INTO users(name, uidnumber, primarygroup, passsha256, othergroups) VALUES('user4', 5004, 5504, '652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0', '5505,5506');
INSERT INTO capabilities(userid, action, object) VALUES(5001, "search", "ou=superheros,dc=glauth,dc=com");
INSERT INTO capabilities(userid, action, object) VALUES(5003, "search", "*");
```
This should be equivalent to this configuration:
```text
[[users]]
name = "hackers"
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
[[users]]
name = "johndoe"
uidnumber = 5002
primarygroup = 5502
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
[[users]]
name = "serviceuser"
mail = "serviceuser@example.com"
uidnumber = 5003
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
primarygroup = 5502
[[users.capabilities]]
action = "search"
object = "*"
[[users]]
name = "user4"
uidnumber = 5003
primarygroup = 5504
othergroups = [5505, 5506]
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
[[groups]]
name = "superheros"
gidnumber = 5501
[[groups]]
name = "svcaccts"
gidnumber = 5502
[[groups]]
name = "civilians"
gidnumber = 5503
includegroups = [ 5501 ]
[[groups]]
name = "caped"
gidnumber = 5504
includegroups = [ 5502, 5501 ]
```
and LDAP should return these `memberOf` values:
```text
uid: hackers
ou: superheros
memberOf: cn=caped,ou=groups,dc=militate,dc=com
memberOf: cn=civilians,ou=groups,dc=militate,dc=com
memberOf: cn=superheros,ou=groups,dc=militate,dc=com
uid: johndoe
ou: svcaccts
memberOf: cn=caped,ou=groups,dc=militate,dc=com
memberOf: cn=svcaccts,ou=groups,dc=militate,dc=com
uid: serviceuser
ou: caped
memberOf: cn=caped,ou=groups,dc=militate,dc=com
uid: user4
ou: caped
memberOf: cn=caped,ou=groups,dc=militate,dc=com
memberOf: cn=lovesailing,ou=groups,dc=militate,dc=com
memberOf: cn=smoker,ou=groups,dc=militate,dc=com
```
If you have the ldap client package installed, this can be easily confirmed by running
```
ldapsearch -H ldap://localhost:3893 -D cn=hackers,ou=superheros,dc=glauth,dc=com -w dogood -x -bdc=glauth,dc=com cn=hackers
```
and so on.
+517
View File
@@ -0,0 +1,517 @@
package main
import (
"database/sql"
"errors"
"fmt"
"net"
"os"
"regexp"
"sort"
"strconv"
"strings"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/handler"
"github.com/glauth/glauth/pkg/stats"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
)
var configattributematcher = regexp.MustCompile(`(?i)\((?P<attribute>[a-zA-Z0-9]+)\s*=\s*(?P<value>.*)\)`)
type SqlBackend interface {
// Name used by database/sql when loading the driver
GetDriverName() string
// Create db/schema if necessary
CreateSchema(db *sql.DB)
//
GetPrepareSymbol() string
}
type database struct {
path string
cnx *sql.DB
}
type databaseHandler struct {
backend config.Backend
log logr.Logger
cfg *config.Config
yubikeyAuth *yubigo.YubiAuth
sqlBackend SqlBackend
database database
MemGroups []config.Group
ldohelper handler.LDAPOpsHelper
attmatcher *regexp.Regexp
}
// func NewDatabaseHandler_deprecated(log *logging.Logger, cfg *config.Config, yubikeyAuth *yubigo.YubiAuth, sqlBackend SqlBackend) handler.Handler {
func NewDatabaseHandler(sqlBackend SqlBackend, opts ...handler.Option) handler.Handler {
options := handler.NewOptions(opts...)
// Note: we will never terminate this connection pool.
db, err := sql.Open(sqlBackend.GetDriverName(), options.Backend.Database)
if err != nil {
options.Logger.Error(err, "Unable to open SQL database named '%s' error: %s", options.Backend.Database)
os.Exit(1)
}
err = db.Ping()
if err != nil {
options.Logger.Error(err, "Unable to communicate with SQL database error: %s", options.Backend.Database)
os.Exit(1)
}
dbInfo := database{
path: options.Backend.Database,
cnx: db,
}
handler := databaseHandler{
backend: options.Backend,
log: options.Logger,
cfg: options.Config,
yubikeyAuth: options.YubiAuth,
sqlBackend: sqlBackend,
database: dbInfo,
ldohelper: options.LDAPHelper,
attmatcher: configattributematcher}
sqlBackend.CreateSchema(db)
options.Logger.V(3).Info("Database (" + sqlBackend.GetDriverName() + "::" + options.Backend.Database + ") Plugin: Ready")
return handler
}
func (h databaseHandler) GetBackend() config.Backend {
return h.backend
}
func (h databaseHandler) GetLog() logr.Logger {
return h.log
}
func (h databaseHandler) GetCfg() *config.Config {
return h.cfg
}
func (h databaseHandler) GetYubikeyAuth() *yubigo.YubiAuth {
return h.yubikeyAuth
}
func (h databaseHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
return h.ldohelper.Bind(h, bindDN, bindSimplePw, conn)
}
func (h databaseHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
return h.ldohelper.Search(h, bindDN, searchReq, conn)
}
// Add is not yet supported for the sql backend
func (h databaseHandler) Add(boundDN string, req ldap.AddRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Modify is not yet supported for the sql backend
func (h databaseHandler) Modify(boundDN string, req ldap.ModifyRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Delete is not yet supported for the sql backend
func (h databaseHandler) Delete(boundDN string, deleteDN string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u config.User, err error) {
var criterion string
if searchByUPN {
criterion = "lower(u.mail)"
} else {
criterion = "lower(u.name)"
}
user := config.User{}
found := false
err = h.database.cnx.QueryRow(fmt.Sprintf(`
SELECT u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey
FROM users u WHERE %s=%s`,
criterion,
h.sqlBackend.GetPrepareSymbol()), userName).Scan(
&user.UIDNumber, &user.PrimaryGroup, &user.PassBcrypt, &user.PassSHA256, &user.OTPSecret, &user.Yubikey)
if err == nil {
found = true
if !h.cfg.Behaviors.IgnoreCapabilities {
capability := config.Capability{}
rows, err := h.database.cnx.Query(fmt.Sprintf(`
SELECT c.action,c.object
FROM capabilities c WHERE userid=%s`,
h.sqlBackend.GetPrepareSymbol()), user.UIDNumber)
if err == nil {
for rows.Next() {
err := rows.Scan(&capability.Action, &capability.Object)
if err == nil {
user.Capabilities = append(user.Capabilities, capability)
}
}
}
defer rows.Close()
}
}
return found, user, err
}
func (h databaseHandler) FindGroup(groupName string) (f bool, g config.Group, err error) {
group := config.Group{}
found := false
err = h.database.cnx.QueryRow(fmt.Sprintf(`
SELECT g.gidnumber FROM groups g WHERE lower(name)=%s`, h.sqlBackend.GetPrepareSymbol()), groupName).Scan(
&group.GIDNumber)
if err == nil {
found = true
}
return found, group, err
}
func (h databaseHandler) FindPosixAccounts() (entrylist []*ldap.Entry, err error) {
entries := []*ldap.Entry{}
h.MemGroups, err = h.memoizeGroups()
if err != nil {
return entries, err
}
rows, err := h.database.cnx.Query(`
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups,u.givenname,u.sn,u.mail,u.loginshell,u.homedirectory,u.disabled
FROM users u`)
if err != nil {
return entries, err
}
defer rows.Close()
var otherGroups string
var disabled int
u := config.User{}
for rows.Next() {
err := rows.Scan(&u.Name, &u.UIDNumber, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups, &u.GivenName, &u.SN, &u.Mail, &u.LoginShell, &u.Homedir, &disabled)
if err != nil {
return entries, err
}
u.OtherGroups = h.commaListToTable(otherGroups)
u.Disabled = h.intToBool(disabled)
entries = append(entries, h.getAccount(u))
}
return entries, nil
}
func (h databaseHandler) FindPosixGroups() (entrylist []*ldap.Entry, err error) {
entries := []*ldap.Entry{}
h.MemGroups, err = h.memoizeGroups()
if err != nil {
return entries, err
}
for _, g := range h.MemGroups {
entries = append(entries, h.getGroup(g))
}
return entries, nil
}
func (h databaseHandler) Close(boundDn string, conn net.Conn) error {
stats.Frontend.Add("closes", 1)
return nil
}
func (h databaseHandler) intToBool(value int) bool {
if value == 0 {
return false
}
return true
}
func (h databaseHandler) commaListToTable(commaList string) []int {
if len(commaList) == 0 {
return make([]int, 0)
}
rowsAsStrings := strings.Split(commaList, ",")
rowsAsInts := make([]int, len(rowsAsStrings))
for i, v := range rowsAsStrings {
iv, err := strconv.Atoi(v)
if err != nil {
return rowsAsInts
}
rowsAsInts[i] = iv
}
return rowsAsInts
}
func (h databaseHandler) memoizeGroups() ([]config.Group, error) {
workMemGroups := make([]*config.Group, 0)
rows, err := h.database.cnx.Query(`
SELECT g1.name,g1.gidnumber,ig.includegroupid
FROM groups g1
LEFT JOIN includegroups ig ON g1.gidnumber=ig.parentgroupid
LEFT JOIN groups g2 ON ig.includegroupid=g2.gidnumber`)
if err != nil {
return nil, errors.New("Unable to memoize groups list")
}
defer rows.Close()
var groupName string
var groupId int
var includeId sql.NullInt64 // Store includeid from left join
var pg *config.Group
recentId := -1 // id of recently updated group
for rows.Next() {
err := rows.Scan(&groupName, &groupId, &includeId)
if err != nil {
return nil, errors.New("Unable to memoize groups list")
}
if recentId != groupId {
recentId = groupId
g := config.Group{Name: groupName, GIDNumber: groupId}
pg = &g // To manipulate end of slice
workMemGroups = append(workMemGroups, &g)
}
if includeId.Valid {
pg.IncludeGroups = append(pg.IncludeGroups, int(includeId.Int64))
}
}
memGroups := make([]config.Group, len(workMemGroups))
for i, v := range workMemGroups {
memGroups[i] = config.Group{Name: v.Name, GIDNumber: v.GIDNumber, IncludeGroups: v.IncludeGroups}
}
return memGroups, nil
}
// Used when looking up Posix Groups
func (h databaseHandler) getGroupMembers(gid int) []string {
members := make(map[string]bool)
rows, err := h.database.cnx.Query(`
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
FROM users u WHERE lower(u.name)=?`,
)
if err != nil {
// Silent fail... for now
return []string{}
}
defer rows.Close()
var otherGroups string
u := config.User{}
for rows.Next() {
err := rows.Scan(&u.Name, &u.UIDNumber, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups)
if err != nil {
return []string{}
}
if u.PrimaryGroup == gid {
dn := fmt.Sprintf("cn=%s,ou=%s,%s", u.Name, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
members[dn] = true
} else {
u.OtherGroups = h.commaListToTable(otherGroups)
for _, othergid := range u.OtherGroups {
if othergid == gid {
dn := fmt.Sprintf("cn=%s,ou=%s,%s", u.Name, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
members[dn] = true
}
}
}
}
for _, g := range h.MemGroups {
if gid == g.GIDNumber {
for _, includegroupid := range g.IncludeGroups {
if includegroupid != gid {
includegroupmembers := h.getGroupMembers(includegroupid)
for _, includegroupmember := range includegroupmembers {
members[includegroupmember] = true
}
}
}
}
}
m := []string{}
for k := range members {
m = append(m, k)
}
sort.Strings(m)
return m
}
// Used exclusively when looking up Posix Groups
func (h databaseHandler) getGroupMemberIDs(gid int) []string {
members := make(map[string]bool)
rows, err := h.database.cnx.Query(`
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
FROM users u`)
if err != nil {
// Silent fail... for now
return []string{}
}
defer rows.Close()
var otherGroups string
u := config.User{}
for rows.Next() {
err := rows.Scan(&u.Name, &u.UIDNumber, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups)
if err != nil {
return []string{}
}
if u.PrimaryGroup == gid {
members[u.Name] = true
} else {
u.OtherGroups = h.commaListToTable(otherGroups)
for _, othergid := range u.OtherGroups {
if othergid == gid {
members[u.Name] = true
}
}
}
}
for _, g := range h.MemGroups {
if gid == g.GIDNumber {
for _, includegroupid := range g.IncludeGroups {
if includegroupid == gid {
h.log.V(3).Info(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
} else {
includegroupmemberids := h.getGroupMemberIDs(includegroupid)
for _, includegroupmemberid := range includegroupmemberids {
members[includegroupmemberid] = true
}
}
}
}
}
m := []string{}
for k := range members {
m = append(m, k)
}
sort.Strings(m)
return m
}
// Invoked for every user being returned from our database
func (h databaseHandler) getGroupDNs(gids []int) []string {
groups := make(map[string]bool)
for _, gid := range gids {
for _, g := range h.MemGroups {
if g.GIDNumber == gid {
dn := fmt.Sprintf("cn=%s,ou=groups,%s", g.Name, h.backend.BaseDN)
groups[dn] = true
}
for _, includegroupid := range g.IncludeGroups {
if includegroupid == gid && g.GIDNumber != gid {
includegroupdns := h.getGroupDNs([]int{g.GIDNumber})
for _, includegroupdn := range includegroupdns {
groups[includegroupdn] = true
}
}
}
}
}
g := []string{}
for k := range groups {
g = append(g, k)
}
sort.Strings(g)
return g
}
// Invoked for every user being returned from our database
func (h databaseHandler) getGroupName(gid int) string {
for _, g := range h.MemGroups {
if g.GIDNumber == gid {
return g.Name
}
}
return ""
}
// Toolbox
func (h databaseHandler) getGroup(g config.Group) *ldap.Entry {
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{"cn", []string{g.Name}})
attrs = append(attrs, &ldap.EntryAttribute{"description", []string{fmt.Sprintf("%s via LDAP", g.Name)}})
attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{fmt.Sprintf("%d", g.GIDNumber)}})
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixGroup"}})
attrs = append(attrs, &ldap.EntryAttribute{"uniqueMember", h.getGroupMembers(g.GIDNumber)})
attrs = append(attrs, &ldap.EntryAttribute{"memberUid", h.getGroupMemberIDs(g.GIDNumber)})
dn := fmt.Sprintf("cn=%s,ou=groups,%s", g.Name, h.backend.BaseDN)
return &ldap.Entry{dn, attrs}
}
func (h databaseHandler) getAccount(u config.User) *ldap.Entry {
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{"cn", []string{u.Name}})
attrs = append(attrs, &ldap.EntryAttribute{"uid", []string{u.Name}})
if len(u.GivenName) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{"givenName", []string{u.GivenName}})
}
if len(u.SN) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{"sn", []string{u.SN}})
}
attrs = append(attrs, &ldap.EntryAttribute{"ou", []string{h.getGroupName(u.PrimaryGroup)}})
attrs = append(attrs, &ldap.EntryAttribute{"uidNumber", []string{fmt.Sprintf("%d", u.UIDNumber)}})
if u.Disabled {
attrs = append(attrs, &ldap.EntryAttribute{"accountStatus", []string{"inactive"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{"accountStatus", []string{"active"}})
}
if len(u.Mail) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{"mail", []string{u.Mail}})
attrs = append(attrs, &ldap.EntryAttribute{"userPrincipalName", []string{u.Mail}})
}
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixAccount"}})
if len(u.LoginShell) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{"loginShell", []string{u.LoginShell}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{"loginShell", []string{"/bin/bash"}})
}
if len(u.Homedir) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{"homeDirectory", []string{u.Homedir}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{"homeDirectory", []string{"/home/" + u.Name}})
}
attrs = append(attrs, &ldap.EntryAttribute{"description", []string{fmt.Sprintf("%s via LDAP", u.Name)}})
attrs = append(attrs, &ldap.EntryAttribute{"gecos", []string{fmt.Sprintf("%s via LDAP", u.Name)}})
attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{fmt.Sprintf("%d", u.PrimaryGroup)}})
attrs = append(attrs, &ldap.EntryAttribute{"memberOf", h.getGroupDNs(append(u.OtherGroups, u.PrimaryGroup))})
if len(u.SSHKeys) > 0 {
attrs = append(attrs, &ldap.EntryAttribute{"sshPublicKey", u.SSHKeys})
}
dn := fmt.Sprintf("cn=%s,ou=%s,%s", u.Name, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
return &ldap.Entry{dn, attrs}
}
func main() {}
+58
View File
@@ -0,0 +1,58 @@
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"github.com/glauth/glauth/pkg/handler"
)
type MysqlBackend struct {
}
func NewMySQLHandler(opts ...handler.Option) handler.Handler {
backend := MysqlBackend{}
return NewDatabaseHandler(backend, opts...)
}
func (b MysqlBackend) GetDriverName() string {
return "mysql"
}
func (b MysqlBackend) GetPrepareSymbol() string {
return "?"
}
// Create db/schema if necessary
func (b MysqlBackend) CreateSchema(db *sql.DB) {
statement, _ := db.Prepare(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(64) NOT NULL,
uidnumber INTEGER NOT NULL,
primarygroup INTEGER NOT NULL,
othergroups VARCHAR(1024) DEFAULT '',
givenname VARCHAR(64) DEFAULT '',
sn VARCHAR(64) DEFAULT '',
mail VARCHAR(254) DEFAULT '',
loginshell VARCHAR(64) DEFAULT '',
homedirectory VARCHAR(64) DEFAULT '',
disabled SMALLINT DEFAULT 0,
passsha256 VARCHAR(64) DEFAULT '',
passbcrypt VARCHAR(64) DEFAULT '',
otpsecret VARCHAR(64) DEFAULT '',
yubikey VARCHAR(128) DEFAULT '')
`)
statement.Exec()
statement, _ = db.Prepare("CREATE UNIQUE INDEX idx_user_name on users(name)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS groups (id INTEGER AUTO_INCREMENT PRIMARY KEY, name VARCHAR(64) NOT NULL, gidnumber INTEGER NOT NULL)")
statement.Exec()
statement, _ = db.Prepare("CREATE UNIQUE INDEX idx_group_name on groups(name)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS includegroups (id INTEGER AUTO_INCREMENT PRIMARY KEY, parentgroupid INTEGER NOT NULL, includegroupid INTEGER NOT NULL)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS capabilities (id INTEGER AUTO_INCREMENT PRIMARY KEY, userid INTEGER NOT NULL, action VARCHAR(128) NOT NULL, object VARCHAR(128) NOT NULL)")
statement.Exec()
}
+58
View File
@@ -0,0 +1,58 @@
package main
import (
"database/sql"
_ "github.com/lib/pq"
"github.com/glauth/glauth/pkg/handler"
)
type PostgresBackend struct {
}
func NewPostgresHandler(opts ...handler.Option) handler.Handler {
backend := PostgresBackend{}
return NewDatabaseHandler(backend, opts...)
}
func (b PostgresBackend) GetDriverName() string {
return "postgres"
}
func (b PostgresBackend) GetPrepareSymbol() string {
return "$1"
}
// Create db/schema if necessary
func (b PostgresBackend) CreateSchema(db *sql.DB) {
statement, _ := db.Prepare(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
uidnumber INTEGER NOT NULL,
primarygroup INTEGER NOT NULL,
othergroups TEXT DEFAULT '',
givenname TEXT DEFAULT '',
sn TEXT DEFAULT '',
mail TEXT DEFAULT '',
loginshell TEXT DEFAULT '',
homedirectory TEXT DEFAULT '',
disabled SMALLINT DEFAULT 0,
passsha256 TEXT DEFAULT '',
passbcrypt TEXT DEFAULT '',
otpsecret TEXT DEFAULT '',
yubikey TEXT DEFAULT '')
`)
statement.Exec()
statement, _ = db.Prepare("CREATE UNIQUE INDEX IF NOT EXISTS idx_user_name on users(name)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS groups (id SERIAL PRIMARY KEY, name TEXT NOT NULL, gidnumber INTEGER NOT NULL)")
statement.Exec()
statement, _ = db.Prepare("CREATE UNIQUE INDEX IF NOT EXISTS idx_group_name on groups(name)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS includegroups (id SERIAL PRIMARY KEY, parentgroupid INTEGER NOT NULL, includegroupid INTEGER NOT NULL)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS capabilities (id SERIAL PRIMARY KEY, userid INTEGER NOT NULL, action TEXT NOT NULL, object TEXT NOT NULL)")
statement.Exec()
}
+82
View File
@@ -0,0 +1,82 @@
#################
# glauth.conf
#################
# General configuration.
debug = true
# syslog = true
#
# Enable hot-reload of configuration on changes
# - does NOT work [ldap], [ldaps], [backend] or [api] sections
# watchconfig = true
#################
# yubikeyclientid = "yubi-api-clientid"
# yubikeysecret = "yubi-api-secret"
#################
# Server configuration.
#
# This is the old server-config format
# - will still work, but please use new format, shown below
#
# [frontend]
# listen = "localhost:8484"
# tls = false
# cert ="certs/server.crt"
# key = "certs/server.key"
# This ([ldap] and [ldaps]) is the new server-config format
[ldap]
enabled = true
listen = "0.0.0.0:3893"
[ldaps]
enabled = false
listen = "0.0.0.0:3894"
cert = "certs/server.crt"
key = "certs/server.key"
#################
# The backend section controls the data store.
[backend]
datastore = "plugin"
# If "plugin," uncomment the line below
plugin = "bin/sqlite.so"
pluginhandler = "NewSQLiteHandler"
baseDN = "dc=glauth,dc=com"
# // MySQL //
# Uncomment this line to configure your MySQL database connection string:
# database = "glauth:glauth@tcp(127.0.0.1:3306)/glauth"
# Creating database:
# create database glauth identified;
# grant all on glauth.* to glauth@localhost identified by 'glauth';
# // Postgres //
# Uncomment this line to configure your Postgres database connection string:
# database = "host=127.0.0.1 port=5432 dbname=glauth user=glauth password=glauth sslmode=disable"
# Creating database:
# CREATE DATABASE <dbname>;
# CREATE USER <username> WITH PASSWORD '<userpassword>'
# GRANT ALL PRIVILEGES ON DATABASE <dbname> to <username>;
# // SQLite //
# Uncomment this line to configure your SQLite database connection string:
database = "gl.db"
## Configure dn format to use structures like
## "uid=serviceuser,cn=svcaccts,$BASEDN" instead of "cn=serviceuser,ou=svcaccts,$BASEDN"
## to help ease migrations from other LDAP systems
# nameformat = "uid"
# groupformat = "cn"
## Configure ssh-key attribute name, default is 'sshPublicKey'
# sshkeyattr = "ipaSshPubKey"
#################
# Enable and configure the optional REST API here.
[api]
enabled = true
tls = false # enable TLS for production!!
listen = "0.0.0.0:5555"
cert = "cert.pem"
key = "key.pem"
+45
View File
@@ -0,0 +1,45 @@
#################
# glauth.conf
#################
# General configuration.
#debug = true
syslog = true
#################
# Server configuration.
[ldap]
enabled = true
listen = "0.0.0.0:389"
[ldaps]
enabled = true
listen = "0.0.0.0:636"
cert = "glauth.crt"
key = "glauth.key"
#################
# The backend section controls the data store.
[backend]
datastore = "ldap"
servers = [ "ldaps://server1:636", "ldaps://server2:636" ]
# Ignore SSL certificate errors when connecting to backend LDAP servers.
#insecure = true
[helper]
enabled = true
datastore = "plugin"
plugin = "bin/sqlite.so"
pluginhandler = "NewSQLiteHandler"
database = "glh.db"
#################
# Enable and configure the optional REST API here.
[api]
enabled = true
tls = true
listen = "localhost:5555"
cert = "glauth.crt"
key = "glauth.key"
+58
View File
@@ -0,0 +1,58 @@
package main
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
"github.com/glauth/glauth/pkg/handler"
)
type SqliteBackend struct {
}
func NewSQLiteHandler(opts ...handler.Option) handler.Handler {
backend := SqliteBackend{}
return NewDatabaseHandler(backend, opts...)
}
func (b SqliteBackend) GetDriverName() string {
return "sqlite3"
}
func (b SqliteBackend) GetPrepareSymbol() string {
return "?"
}
// Create db/schema if necessary
func (b SqliteBackend) CreateSchema(db *sql.DB) {
statement, _ := db.Prepare(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
uidnumber INTEGER NOT NULL,
primarygroup INTEGER NOT NULL,
othergroups TEXT DEFAULT '',
givenname TEXT DEFAULT '',
sn TEXT DEFAULT '',
mail TEXT DEFAULT '',
loginshell TYEXT DEFAULT '',
homedirectory TEXT DEFAULT '',
disabled SMALLINT DEFAULT 0,
passsha256 TEXT DEFAULT '',
passbcrypt TEXT DEFAULT '',
otpsecret TEXT DEFAULT '',
yubikey TEXT DEFAULT '')
`)
statement.Exec()
statement, _ = db.Prepare("CREATE UNIQUE INDEX IF NOT EXISTS idx_user_name on users(name)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS groups (id INTEGER PRIMARY KEY, name TEXT NOT NULL, gidnumber INTEGER NOT NULL)")
statement.Exec()
statement, _ = db.Prepare("CREATE UNIQUE INDEX IF NOT EXISTS idx_group_name on groups(name)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS includegroups (id INTEGER PRIMARY KEY, parentgroupid INTEGER NOT NULL, includegroupid INTEGER NOT NULL)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS capabilities (id INTEGER PRIMARY KEY, userid INTEGER NOT NULL, action TEXT NOT NULL, object TEXT NOT NULL)")
statement.Exec()
}
+50
View File
@@ -0,0 +1,50 @@
package server
import (
"context"
"github.com/glauth/glauth/pkg/config"
"github.com/go-logr/logr"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger logr.Logger
Config *config.Config
Context context.Context
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val logr.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
+131 -49
View File
@@ -3,89 +3,171 @@ package server
import (
"errors"
"fmt"
"plugin"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/handler"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
"github.com/op/go-logging"
)
type LdapSvc struct {
log *logging.Logger
log logr.Logger
c *config.Config
yubiAuth *yubigo.YubiAuth
l *ldap.Server
}
func NewServer(log *logging.Logger, cfg *config.Config) (*LdapSvc, error) {
func NewServer(opts ...Option) (*LdapSvc, error) {
options := newOptions(opts...)
s := LdapSvc{
log: log,
c: cfg,
log: options.Logger,
c: options.Config,
}
var err error
if len(cfg.YubikeyClientID) > 0 && len(cfg.YubikeySecret) > 0 {
s.yubiAuth, err = yubigo.NewYubiAuth(cfg.YubikeyClientID, cfg.YubikeySecret)
if len(s.c.YubikeyClientID) > 0 && len(s.c.YubikeySecret) > 0 {
s.yubiAuth, err = yubigo.NewYubiAuth(s.c.YubikeyClientID, s.c.YubikeySecret)
if err != nil {
return nil, errors.New("Yubikey Auth failed")
}
}
// configure the backend
var helper handler.Handler
loh := handler.NewLDAPOpsHelper()
// instantiate the helper, if any
if s.c.Helper.Enabled {
switch s.c.Helper.Datastore {
case "config":
helper = handler.NewConfigHandler(
handler.Logger(s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
handler.LDAPHelper(loh),
)
case "plugin":
plug, err := plugin.Open(s.c.Helper.Plugin)
if err != nil {
return nil, errors.New(fmt.Sprintf("Unable to load specified helper plugin: %s", err))
}
nph, err := plug.Lookup(s.c.Helper.PluginHandler)
if err != nil {
return nil, errors.New("Unable to find 'NewPluginHandler' in loaded helper plugin")
}
initFunc, ok := nph.(func(...handler.Option) handler.Handler)
if !ok {
return nil, errors.New("Loaded helper plugin lacks a proper NewPluginHandler function")
}
// Normally, here, we would somehow have imported our plugin into our
// handler namespace. Oops?
helper = initFunc(
handler.Logger(s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
handler.LDAPHelper(loh),
)
default:
return nil, fmt.Errorf("unsupported helper %s - must be one of 'config', 'plugin'", s.c.Helper.Datastore)
}
s.log.V(3).Info("Using helper", "datastore", s.c.Helper.Datastore)
}
backendCounter := -1
allHandlers := handler.HandlerWrapper{Handlers: make([]handler.Handler, 10), Count: &backendCounter}
// configure the backends
s.l = ldap.NewServer()
s.l.EnforceLDAP = true
var h handler.Handler
switch cfg.Backend.Datastore {
case "ldap":
h = handler.NewLdapHandler(log, cfg)
case "owncloud":
h = handler.NewOwnCloudHandler(log, cfg)
case "config":
h = handler.NewConfigHandler(log, cfg, s.yubiAuth)
default:
return nil, fmt.Errorf("unsupported backend %s - must be 'config' or 'ldap'", cfg.Backend.Datastore)
for i, backend := range s.c.Backends {
var h handler.Handler
switch backend.Datastore {
case "ldap":
h = handler.NewLdapHandler(
handler.Backend(backend),
handler.Handlers(allHandlers),
handler.Logger(s.log),
handler.Helper(helper),
)
case "owncloud":
h = handler.NewOwnCloudHandler(
handler.Backend(backend),
handler.Logger(s.log),
)
case "config":
h = handler.NewConfigHandler(
handler.Backend(backend),
handler.Logger(s.log),
handler.Config(s.c), // TODO only used to access Users and Groups, move that to dedicated options
handler.YubiAuth(s.yubiAuth),
handler.LDAPHelper(loh),
)
case "plugin":
plug, err := plugin.Open(backend.Plugin)
if err != nil {
return nil, errors.New(fmt.Sprintf("Unable to load specified backend plugin: %s", err))
}
nph, err := plug.Lookup(backend.PluginHandler)
if err != nil {
return nil, errors.New("Unable to find 'NewPluginHandler' in loaded backend plugin")
}
initFunc, ok := nph.(func(...handler.Option) handler.Handler)
if !ok {
return nil, errors.New("Loaded backend plugin lacks a proper NewPluginHandler function")
}
// Normally, here, we would somehow have imported our plugin into our
// handler namespace. Oops?
h = initFunc(
handler.Backend(backend),
handler.Logger(s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
handler.LDAPHelper(loh),
)
default:
return nil, fmt.Errorf("unsupported backend %s - must be one of 'config', 'ldap','owncloud' or 'plugin'", backend.Datastore)
}
s.log.V(3).Info("Loading backend", "datastore", backend.Datastore, "position", i)
// Only our first backend will answer proper LDAP queries.
// Note that this could evolve towars something nicer where we would maintain
// multiple binders in addition to the existing multiple LDAP backends
if i == 0 {
s.l.BindFunc("", h)
s.l.SearchFunc("", h)
s.l.CloseFunc("", h)
}
allHandlers.Handlers[i] = h
backendCounter++
}
log.Notice(fmt.Sprintf("Using %s backend", cfg.Backend.Datastore))
s.l.BindFunc("", h)
s.l.SearchFunc("", h)
s.l.CloseFunc("", h)
return &s, nil
}
func (s *LdapSvc) ListenAndServe() {
if s.c.LDAP.Enabled {
// Dont block if also starting a LDAPS server afterwards
shouldBlock := !s.c.LDAPS.Enabled
if shouldBlock {
s.startLDAP()
} else {
go s.startLDAP()
}
}
if s.c.LDAPS.Enabled {
// Always block here
s.startLDAPS()
}
// ListenAndServe listens on the TCP network address s.c.LDAP.Listen
func (s *LdapSvc) ListenAndServe() error {
s.log.V(3).Info("LDAP server listening", "address", s.c.LDAP.Listen)
return s.l.ListenAndServe(s.c.LDAP.Listen)
}
func (s *LdapSvc) startLDAP() {
s.log.Notice(fmt.Sprintf("LDAP server listening on %s", s.c.LDAP.Listen))
if err := s.l.ListenAndServe(s.c.LDAP.Listen); err != nil {
s.log.Fatalf("LDAP Server Failed: %s", err.Error())
}
// ListenAndServeTLS listens on the TCP network address s.c.LDAPS.Listen
func (s *LdapSvc) ListenAndServeTLS() error {
s.log.V(3).Info("LDAPS server listening", "address", s.c.LDAPS.Listen)
return s.l.ListenAndServeTLS(
s.c.LDAPS.Listen,
s.c.LDAPS.Cert,
s.c.LDAPS.Key,
)
}
func (s *LdapSvc) startLDAPS() {
s.log.Notice(fmt.Sprintf("LDAPS server listening on %s", s.c.LDAPS.Listen))
if err := s.l.ListenAndServeTLS(s.c.LDAPS.Listen, s.c.LDAPS.Cert, s.c.LDAPS.Key); err != nil {
s.log.Fatalf("LDAP Server Failed: %s", err.Error())
}
// Shutdown ends listeners by sending true to the ldap serves quit channel
func (s *LdapSvc) Shutdown() {
s.l.Quit <- true
}
+1 -1
View File
@@ -2,4 +2,4 @@ package stats
type Stringer string
func (s Stringer) String() string { return string(s) }
func (s Stringer) String() string { return "\"" + string(s) + "\"" }
+42
View File
@@ -0,0 +1,42 @@
#################
# glauth.conf
#################
# General configuration.
#debug = true
syslog = true
#################
# Server configuration.
[ldap]
enabled = true
listen = "0.0.0.0:3893"
[ldaps]
enabled = false
listen = "0.0.0.0:636"
cert = "glauth.crt"
key = "glauth.key"
#################
# The backend section controls the data store.
[[backends]]
datastore = "ldap"
servers = [ "ldap://localhost:389" ]
[[backends]]
datastore = "config"
[[users]]
name = "otpuser"
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
#################
# Enable and configure the optional REST API here.
[api]
enabled = false
tls = true
listen = "localhost:5555"
cert = "glauth.crt"
key = "glauth.key"
+11 -7
View File
@@ -7,12 +7,16 @@
syslog = true
#################
# The frontend section controls how clients connect to the proxy.
[frontend]
tls = true
# Server configuration.
[ldap]
enabled = true
listen = "0.0.0.0:389"
[ldaps]
enabled = true
listen = "0.0.0.0:636"
cert = "cert.pem"
key = "key.pem"
cert = "glauth.crt"
key = "glauth.key"
#################
# The backend section controls the data store.
@@ -29,6 +33,6 @@ syslog = true
enabled = true
tls = true
listen = "localhost:5555"
cert = "cert.pem"
key = "key.pem"
cert = "glauth.crt"
key = "glauth.key"
+5 -2
View File
@@ -18,5 +18,8 @@ debug = true
[backend]
datastore = "owncloud"
baseDN = "dc=owncloud,dc=com"
servers = ["https://cloud.example.com"]
UseGraphAPI = true
UseGraphAPI = true
servers = ["https://cloud.example.com/index.php/apps/graphapi/v1.0/"]
# if not using the graph API only provide the url to the owncloud instance
#UseGraphAPI = false
#servers = ["https://other.example.com/owncloud/"]
+53 -21
View File
@@ -16,26 +16,18 @@ debug = true
#################
# Server configuration.
#
# This is the old server-config format
# - will still work, but please use new format, shown below
#
# [frontend]
# listen = "localhost:8484"
# tls = false
# cert ="certs/server.crt"
# key = "certs/server.key"
# This ([ldap] and [ldaps]) is the new server-config format
[ldap]
enabled = true
# run on a non privileged port
listen = "0.0.0.0:3893"
[ldaps]
enabled = true
# to enable ldaps genrerate a certificate, eg. with:
# openssl req -x509 -newkey rsa:4096 -keyout glauth.key -out glauth.crt -days 365 -nodes -subj '/CN=`hostname`'
enabled = false
listen = "0.0.0.0:3894"
cert = "certs/server.crt"
key = "certs/server.key"
cert = "glauth.crt"
key = "glauth.key"
#################
# The backend section controls the data store.
@@ -52,14 +44,33 @@ debug = true
## Configure ssh-key attribute name, default is 'sshPublicKey'
# sshkeyattr = "ipaSshPubKey"
[behaviors]
# Ignore all capabilities restrictions, for instance allowing every user to perform a search
IgnoreCapabilities = false
# Enable a "fail2ban" type backoff mechanism temporarily banning repeated failed login attempts
LimitFailedBinds = true
# How many failed login attempts are allowed before a ban is imposed
NumberOfFailedBinds = 3
# How long (in seconds) is the window for failed login attempts
PeriodOfFailedBinds = 10
# How long (in seconds) is the ban duration
BlockFailedBindsFor = 60
# Clean learnt IP addresses every N seconds
PruneSourceTableEvery = 600
# Clean learnt IP addresses not seen in N seconds
PruneSourcesOlderThan = 600
#################
# The users section contains a hardcoded list of valid users.
# to create a passSHA256: echo -n "mysecret" | openssl dgst -sha256
[[users]]
name = "hackers"
unixid = 5001
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
# This user record shows all of the possible fields available
[[users]]
@@ -67,7 +78,7 @@ debug = true
givenname="John"
sn="Doe"
mail = "jdoe@example.com"
unixid = 5002
uidnumber = 5002
primarygroup = 5501
loginShell = "/bin/sh"
homeDir = "/root"
@@ -81,38 +92,59 @@ debug = true
[[users]]
name = "serviceuser"
unixid = 5003
mail = "serviceuser@example.com"
uidnumber = 5003
primarygroup = 5502
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
[[users.capabilities]]
action = "search"
object = "*"
# Test user showing 2 factor auth authentication
[[users]]
name = "otpuser"
unixid = 5004
uidnumber = 5004
primarygroup = 5501
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
yubikey = "vvjrcfalhlaa"
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
[[users]]
name = "uberhackers"
uidnumber = 5005
primarygroup = 5501
# bcrypt format: hex($2y$2^<number of rounds>$<salt>$<hash>)
passappbcrypt = [
"243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
]
# uncomment and comment out above array to test password with otp code
# passappbcrypt = "243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
#################
# The groups section contains a hardcoded list of valid users.
[[groups]]
name = "superheros"
unixid = 5501
gidnumber = 5501
[[groups]]
name = "svcaccts"
unixid = 5502
gidnumber = 5502
[[groups]]
name = "vpn"
unixid = 5503
gidnumber = 5503
includegroups = [ 5501 ]
#################
# Enable and configure the optional REST API here.
[api]
enabled = true
internals = true # debug application performance
tls = false # enable TLS for production!!
listen = "0.0.0.0:5555"
cert = "cert.pem"
@@ -31,6 +31,7 @@ ou: superheros
uidNumber: 5002
accountStatus: active
mail: jdoe@example.com
userPrincipalName: jdoe@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
@@ -64,6 +65,7 @@ ou: superheros
uidNumber: 5003
accountStatus: active
mail: jdoe2@example.com
userPrincipalName: jdoe2@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
@@ -99,6 +101,7 @@ ou: superheros
uidNumber: 5004
accountStatus: active
mail: adoe@example.com
userPrincipalName: adoe@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
@@ -128,6 +131,7 @@ ou: superheros
uidNumber: 5005
accountStatus: active
mail: jdoe3@example.com
userPrincipalName: jdoe3@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
@@ -157,6 +161,7 @@ ou: superheros
uidNumber: 5006
accountStatus: active
mail: sdoe@example.com
userPrincipalName: sdoe@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
@@ -7,6 +7,7 @@ ou: superheros
uidNumber: 5002
accountStatus: active
mail: jdoe@example.com
userPrincipalName: jdoe@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
@@ -7,6 +7,7 @@ ou: superheros
uidNumber: 5003
accountStatus: active
mail: jdoe2@example.com
userPrincipalName: jdoe2@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
@@ -7,6 +7,7 @@ ou: superheros
uidNumber: 5004
accountStatus: active
mail: adoe@example.com
userPrincipalName: adoe@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
@@ -13,14 +13,14 @@ if [[ "$?" = "1" ]] ; then
exit 1;
fi
# Get the git working directory base if travis build dir isn't set
if [[ "$TRAVIS_BUILD_DIR" == "" ]] ; then
export TRAVIS_BUILD_DIR="$(git rev-parse --show-toplevel)"
# Get the git working directory base if CI build dir isn't set
if [[ "$CI_BUILD_DIR" == "" ]] ; then
export CI_BUILD_DIR="$(git rev-parse --show-toplevel)/v2"
fi
# This script requires that "$TRAVIS_BUILD_DIR" is set to the repo root
# This script requires that "$CI_BUILD_DIR" is set to the repo root
# Ensure ldap utils are installed (for example - when running this outside of travis)
# Ensure ldap utils are installed (for example - when running this outside of CI)
if [[ ! `which ldapsearch` ]]; then
sudo apt-get -qq update && sudo apt-get -qq install -y ldap-utils || exit 1;
fi
@@ -29,14 +29,14 @@ fi
echo "";
echo ""
echo "Version string of tested binary:"
"$TRAVIS_BUILD_DIR/bin/glauth64" --version
"$CI_BUILD_DIR/bin/glauth64" --version
echo ""
# Start in background, capture PID
"$TRAVIS_BUILD_DIR/bin/glauth64" -c "$TRAVIS_BUILD_DIR/scripts/travis/test-config.cfg" &> /dev/null &
"$CI_BUILD_DIR/bin/glauth64" -c "$CI_BUILD_DIR/scripts/ci/test-config.cfg" &> /dev/null &
# Use this instead to see glauth logs while running
# "$TRAVIS_BUILD_DIR/bin/glauth64" -c "$TRAVIS_BUILD_DIR/scripts/travis/test-config.cfg" &
# "$CI_BUILD_DIR/bin/glauth64" -c "$CI_BUILD_DIR/scripts/ci/test-config.cfg" &
glauthPid="$!"
echo "Running glauth at PID=$glauthPid"
@@ -86,8 +86,8 @@ function bindTest() {
# $2 - name of snapshot
function snapshotTest() {
goodResults="$TRAVIS_BUILD_DIR/scripts/travis/good-results"
testResults="$TRAVIS_BUILD_DIR/scripts/travis/test-results"
goodResults="$CI_BUILD_DIR/scripts/ci/good-results"
testResults="$CI_BUILD_DIR/scripts/ci/test-results"
mkdir "$testResults" &> /dev/null
@@ -28,9 +28,12 @@ debug = true
# to create a passSHA256: echo -n "mysecret" | openssl dgst -sha256
[[users]]
name = "hackers"
unixid = 5001
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
# This user record shows all of the possible fields available
[[users]]
@@ -38,12 +41,15 @@ debug = true
givenname="John"
sn="Doe"
mail = "jdoe@example.com"
unixid = 5002
uidnumber = 5002
primarygroup = 5501
loginShell = "/bin/sh"
homeDir = "/root"
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
sshkeys = ["ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwVw3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4hC/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLVIfppJU35t0Ho9doHAEfEvcQA6tcm7FLJUvklAxc8WUbdziczbRV40KzDroIkXAZRjX7vXXhh/p7XBYnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+txSuhw== rsa-key-20160209"]
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
# This user record tests lots of groups, including group inheritence
[[users]]
@@ -51,7 +57,7 @@ debug = true
givenname="James"
sn="Doe"
mail = "jdoe2@example.com"
unixid = 5003
uidnumber = 5003
primarygroup = 5501
loginShell = "/bin/sh"
otherGroups = [5505,5507]
@@ -65,12 +71,15 @@ debug = true
givenname="Alex"
sn="Doe"
mail = "adoe@example.com"
unixid = 5004
uidnumber = 5004
primarygroup = 5501
loginShell = "/bin/sh"
otherGroups = [5505,5507]
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
passsha256 = "b1cae08a1fd8b0bd0855e48f74ba2f8548904dd5de72311e6234617a3f661e16" # ThisAloneWontWork!
[[users.capabilities]]
action = "search"
object = "dc=glauth,dc=com"
@@ -80,7 +89,7 @@ debug = true
givenname="Jack"
sn="Doe"
mail = "jdoe3@example.com"
unixid = 5005
uidnumber = 5005
primarygroup = 5501
loginShell = "/bin/sh"
otherGroups = [5505,5507]
@@ -90,6 +99,9 @@ debug = true
"c9853d5f2599e90497e9f8cc671bd2022b0fb5d1bd7cfff92f079e8f8f02b8d3", # TestAppPw2
"4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88", # TestAppPw3
]
[[users.capabilities]]
action = "search"
object = "dc=glauth,dc=com"
# App PW test user with OTP
[[users]]
@@ -97,7 +109,7 @@ debug = true
givenname="Sarah"
sn="Doe"
mail = "sdoe@example.com"
unixid = 5006
uidnumber = 5006
primarygroup = 5501
loginShell = "/bin/sh"
otherGroups = [5505,5507]
@@ -108,47 +120,53 @@ debug = true
"c9853d5f2599e90497e9f8cc671bd2022b0fb5d1bd7cfff92f079e8f8f02b8d3", # TestAppPw2
"4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88", # TestAppPw3
]
[[users.capabilities]]
action = "search"
object = "dc=glauth,dc=com"
[[users]]
name = "serviceuser"
unixid = 5003
uidnumber = 5003
primarygroup = 5502
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
[[users.capabilities]]
action = "search"
object = "*"
#################
# The groups section contains a hardcoded list of valid users.
[[groups]]
name = "superheros"
unixid = 5501
gidnumber = 5501
[[groups]]
name = "svcaccts"
unixid = 5502
gidnumber = 5502
[[groups]]
name = "vpnaccess"
unixid = 5503
gidnumber = 5503
includegroups = [ 5501 ]
[[groups]]
name = "allaccs"
unixid = 5504
gidnumber = 5504
includegroups = [ 5501, 5502 ]
[[groups]]
name = "mailadmin"
unixid = 5505
gidnumber = 5505
[[groups]]
name = "webmail"
unixid = 5506
gidnumber = 5506
[[groups]]
name = "fulltime"
unixid = 5507
gidnumber = 5507
#################
# Enable and configure the optional REST API here.
+38
View File
@@ -0,0 +1,38 @@
#################
# glauth.conf
#################
# General configuration.
debug = true
#syslog = true
#################
[ldap]
enabled = true
listen = "0.0.0.0:3893"
[ldaps]
enabled = false
listen = "0.0.0.0:3894"
cert = "cert.pem"
key = "key.pem"
#################
# The backend section controls the data store.
[backend]
datastore = "plugin"
# If "plugin," uncomment the line below
plugin = "/app/sqlite.so"
pluginhandler = "NewSQLiteHandler"
baseDN = "dc=glauth,dc=com"
database = "/app/config/gl.db"
#################
# Enable and configure the optional REST API here.
[api]
enabled = true
tls = false # enable TLS for production!!
listen = "localhost:5555"
cert = "cert.pem"
key = "key.pem"
@@ -0,0 +1,150 @@
#################
# glauth.conf
#################
# General configuration.
debug = true
# syslog = true
#
# Enable hot-reload of configuration on changes
# - does NOT work [ldap], [ldaps], [backend] or [api] sections
# watchconfig = true
#################
# yubikeyclientid = "yubi-api-clientid"
# yubikeysecret = "yubi-api-secret"
#################
# Server configuration.
[ldap]
enabled = true
# run on a non privileged port
listen = "0.0.0.0:3893"
[ldaps]
# to enable ldaps genrerate a certificate, eg. with:
# openssl req -x509 -newkey rsa:4096 -keyout glauth.key -out glauth.crt -days 365 -nodes -subj '/CN=`hostname`'
enabled = false
listen = "0.0.0.0:3894"
cert = "glauth.crt"
key = "glauth.key"
#################
# The backend section controls the data store.
[backend]
datastore = "config"
baseDN = "dc=glauth,dc=com"
## Configure dn format to use structures like
## "uid=serviceuser,cn=svcaccts,$BASEDN" instead of "cn=serviceuser,ou=svcaccts,$BASEDN"
## to help ease migrations from other LDAP systems
# nameformat = "uid"
# groupformat = "cn"
## Configure ssh-key attribute name, default is 'sshPublicKey'
# sshkeyattr = "ipaSshPubKey"
[behaviors]
# Ignore all capabilities restrictions, for instance allowing every user to perform a search
IgnoreCapabilities = false
# Enable a "fail2ban" type backoff mechanism temporarily banning repeated failed login attempts
LimitFailedBinds = true
# How many failed login attempts are allowed before a ban is imposed
NumberOfFailedBinds = 3
# How long (in seconds) is the window for failed login attempts
PeriodOfFailedBinds = 10
# How long (in seconds) is the ban duration
BlockFailedBindsFor = 60
# Clean learnt IP addresses every N seconds
PruneSourceTableEvery = 600
# Clean learnt IP addresses not seen in N seconds
PruneSourcesOlderThan = 600
#################
# The users section contains a hardcoded list of valid users.
# to create a passSHA256: echo -n "mysecret" | openssl dgst -sha256
[[users]]
name = "hackers"
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
# This user record shows all of the possible fields available
[[users]]
name = "johndoe"
givenname="John"
sn="Doe"
mail = "jdoe@example.com"
uidnumber = 5002
primarygroup = 5501
loginShell = "/bin/sh"
homeDir = "/root"
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
sshkeys = ["ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwVw3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4hC/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLVIfppJU35t0Ho9doHAEfEvcQA6tcm7FLJUvklAxc8WUbdziczbRV40KzDroIkXAZRjX7vXXhh/p7XBYnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+txSuhw== rsa-key-20160209"]
passappsha256 = [
"c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa", # TestAppPw1
"c9853d5f2599e90497e9f8cc671bd2022b0fb5d1bd7cfff92f079e8f8f02b8d3", # TestAppPw2
"4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88", # TestAppPw3
]
[[users]]
name = "serviceuser"
mail = "serviceuser@example.com"
uidnumber = 5003
primarygroup = 5502
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
[[users.capabilities]]
action = "search"
object = "*"
# Test user showing 2 factor auth authentication
[[users]]
name = "otpuser"
uidnumber = 5004
primarygroup = 5501
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
yubikey = "vvjrcfalhlaa"
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
[[users]]
name = "uberhackers"
uidnumber = 5005
primarygroup = 5501
# bcrypt format: hex($2y$2^<number of rounds>$<salt>$<hash>)
passappbcrypt = [
"243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
]
# uncomment and comment out above array to test password with otp code
# passappbcrypt = "243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
#################
# The groups section contains a hardcoded list of valid users.
[[groups]]
name = "superheros"
gidnumber = 5501
[[groups]]
name = "svcaccts"
gidnumber = 5502
[[groups]]
name = "vpn"
gidnumber = 5503
includegroups = [ 5501 ]
#################
# Enable and configure the optional REST API here.
[api]
enabled = true
tls = false # enable TLS for production!!
listen = "0.0.0.0:5555"
cert = "cert.pem"
key = "key.pem"
-70
View File
@@ -1,70 +0,0 @@
#################
# glauth.conf
#################
# General configuration.
debug = true
#syslog = true
#################
# The frontend section controls how clients connect to the proxy.
[frontend]
tls = false # enable TLS for production!!
listen = "localhost:389"
cert = "cert.pem"
key = "key.pem"
#################
# The backend section controls the data store.
[backend]
datastore = "config"
baseDN = "dc=glauth,dc=com"
#################
# The users section contains a hardcoded list of valid users.
# to create a passSHA256: echo -n "mysecret" | openssl dgst -sha256
[[users]]
name = "hackers"
unixid = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
# This user record shows all of the possible fields available
[[users]]
name = "johndoe"
givenname="John"
sn="Doe"
mail = "jdoe@example.com"
unixid = 5002
primarygroup = 5501
loginShell = "/bin/sh"
homeDir = "/root"
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
sshkeys = ["ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwVw3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4hC/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLVIfppJU35t0Ho9doHAEfEvcQA6tcm7FLJUvklAxc8WUbdziczbRV40KzDroIkXAZRjX7vXXhh/p7XBYnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+txSuhw== rsa-key-20160209"]
[[users]]
name = "serviceuser"
unixid = 5003
primarygroup = 5502
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
#################
# The groups section contains a hardcoded list of valid users.
[[groups]]
name = "superheros"
unixid = 5501
[[groups]]
name = "svcaccts"
unixid = 5502
#################
# Enable and configure the optional REST API here.
[api]
enabled = true
tls = false # enable TLS for production!!
listen = "localhost:5555"
cert = "cert.pem"
key = "key.pem"
+767
View File
@@ -0,0 +1,767 @@
{
"defaultAction": "SCMP_ACT_TRACE",
"defaultErrnoRet": 1,
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
]
},
{
"architecture": "SCMP_ARCH_AARCH64",
"subArchitectures": [
"SCMP_ARCH_ARM"
]
},
{
"architecture": "SCMP_ARCH_MIPS64",
"subArchitectures": [
"SCMP_ARCH_MIPS",
"SCMP_ARCH_MIPS64N32"
]
},
{
"architecture": "SCMP_ARCH_MIPS64N32",
"subArchitectures": [
"SCMP_ARCH_MIPS",
"SCMP_ARCH_MIPS64"
]
},
{
"architecture": "SCMP_ARCH_MIPSEL64",
"subArchitectures": [
"SCMP_ARCH_MIPSEL",
"SCMP_ARCH_MIPSEL64N32"
]
},
{
"architecture": "SCMP_ARCH_MIPSEL64N32",
"subArchitectures": [
"SCMP_ARCH_MIPSEL",
"SCMP_ARCH_MIPSEL64"
]
},
{
"architecture": "SCMP_ARCH_S390X",
"subArchitectures": [
"SCMP_ARCH_S390"
]
}
],
"syscalls": [
{
"names": [
"accept",
"accept4",
"access",
"adjtimex",
"alarm",
"bind",
"brk",
"capget",
"capset",
"chdir",
"chmod",
"chown",
"chown32",
"clock_adjtime",
"clock_adjtime64",
"clock_getres",
"clock_getres_time64",
"clock_gettime",
"clock_gettime64",
"clock_nanosleep",
"clock_nanosleep_time64",
"close",
"close_range",
"connect",
"copy_file_range",
"creat",
"dup",
"dup2",
"dup3",
"epoll_create",
"epoll_create1",
"epoll_ctl",
"epoll_ctl_old",
"epoll_pwait",
"epoll_pwait2",
"epoll_wait",
"epoll_wait_old",
"eventfd",
"eventfd2",
"execve",
"execveat",
"exit",
"exit_group",
"faccessat",
"faccessat2",
"fadvise64",
"fadvise64_64",
"fallocate",
"fanotify_mark",
"fchdir",
"fchmod",
"fchmodat",
"fchown",
"fchown32",
"fchownat",
"fcntl",
"fcntl64",
"fdatasync",
"fgetxattr",
"flistxattr",
"flock",
"fork",
"fremovexattr",
"fsetxattr",
"fstat",
"fstat64",
"fstatat64",
"fstatfs",
"fstatfs64",
"fsync",
"ftruncate",
"ftruncate64",
"futex",
"futex_time64",
"futimesat",
"getcpu",
"getcwd",
"getdents",
"getdents64",
"getegid",
"getegid32",
"geteuid",
"geteuid32",
"getgid",
"getgid32",
"getgroups",
"getgroups32",
"getitimer",
"getpeername",
"getpgid",
"getpgrp",
"getpid",
"getppid",
"getpriority",
"getrandom",
"getresgid",
"getresgid32",
"getresuid",
"getresuid32",
"getrlimit",
"get_robust_list",
"getrusage",
"getsid",
"getsockname",
"getsockopt",
"get_thread_area",
"gettid",
"gettimeofday",
"getuid",
"getuid32",
"getxattr",
"inotify_add_watch",
"inotify_init",
"inotify_init1",
"inotify_rm_watch",
"io_cancel",
"ioctl",
"io_destroy",
"io_getevents",
"io_pgetevents",
"io_pgetevents_time64",
"ioprio_get",
"ioprio_set",
"io_setup",
"io_submit",
"io_uring_enter",
"io_uring_register",
"io_uring_setup",
"ipc",
"kill",
"lchown",
"lchown32",
"lgetxattr",
"link",
"linkat",
"listen",
"listxattr",
"llistxattr",
"_llseek",
"lremovexattr",
"lseek",
"lsetxattr",
"lstat",
"lstat64",
"madvise",
"membarrier",
"memfd_create",
"mincore",
"mkdir",
"mkdirat",
"mknod",
"mknodat",
"mlock",
"mlock2",
"mlockall",
"mmap",
"mmap2",
"mprotect",
"mq_getsetattr",
"mq_notify",
"mq_open",
"mq_timedreceive",
"mq_timedreceive_time64",
"mq_timedsend",
"mq_timedsend_time64",
"mq_unlink",
"mremap",
"msgctl",
"msgget",
"msgrcv",
"msgsnd",
"msync",
"munlock",
"munlockall",
"munmap",
"nanosleep",
"newfstatat",
"_newselect",
"open",
"openat",
"openat2",
"pause",
"pidfd_open",
"pidfd_send_signal",
"pipe",
"pipe2",
"poll",
"ppoll",
"ppoll_time64",
"prctl",
"pread64",
"preadv",
"preadv2",
"prlimit64",
"pselect6",
"pselect6_time64",
"pwrite64",
"pwritev",
"pwritev2",
"read",
"readahead",
"readlink",
"readlinkat",
"readv",
"recv",
"recvfrom",
"recvmmsg",
"recvmmsg_time64",
"recvmsg",
"remap_file_pages",
"removexattr",
"rename",
"renameat",
"renameat2",
"restart_syscall",
"rmdir",
"rseq",
"rt_sigaction",
"rt_sigpending",
"rt_sigprocmask",
"rt_sigqueueinfo",
"rt_sigreturn",
"rt_sigsuspend",
"rt_sigtimedwait",
"rt_sigtimedwait_time64",
"rt_tgsigqueueinfo",
"sched_getaffinity",
"sched_getattr",
"sched_getparam",
"sched_get_priority_max",
"sched_get_priority_min",
"sched_getscheduler",
"sched_rr_get_interval",
"sched_rr_get_interval_time64",
"sched_setaffinity",
"sched_setattr",
"sched_setparam",
"sched_setscheduler",
"sched_yield",
"seccomp",
"select",
"semctl",
"semget",
"semop",
"semtimedop",
"semtimedop_time64",
"send",
"sendfile",
"sendfile64",
"sendmmsg",
"sendmsg",
"sendto",
"setfsgid",
"setfsgid32",
"setfsuid",
"setfsuid32",
"setgid",
"setgid32",
"setgroups",
"setgroups32",
"setitimer",
"setpgid",
"setpriority",
"setregid",
"setregid32",
"setresgid",
"setresgid32",
"setresuid",
"setresuid32",
"setreuid",
"setreuid32",
"setrlimit",
"set_robust_list",
"setsid",
"setsockopt",
"set_thread_area",
"set_tid_address",
"setuid",
"setuid32",
"setxattr",
"shmat",
"shmctl",
"shmdt",
"shmget",
"shutdown",
"sigaltstack",
"signalfd",
"signalfd4",
"sigprocmask",
"sigreturn",
"socket",
"socketcall",
"socketpair",
"splice",
"stat",
"stat64",
"statfs",
"statfs64",
"statx",
"symlink",
"symlinkat",
"sync",
"sync_file_range",
"syncfs",
"sysinfo",
"tee",
"tgkill",
"time",
"timer_create",
"timer_delete",
"timer_getoverrun",
"timer_gettime",
"timer_gettime64",
"timer_settime",
"timer_settime64",
"timerfd_create",
"timerfd_gettime",
"timerfd_gettime64",
"timerfd_settime",
"timerfd_settime64",
"times",
"tkill",
"truncate",
"truncate64",
"ugetrlimit",
"umask",
"uname",
"unlink",
"unlinkat",
"utime",
"utimensat",
"utimensat_time64",
"utimes",
"vfork",
"vmsplice",
"wait4",
"waitid",
"waitpid",
"write",
"writev"
],
"action": "SCMP_ACT_ALLOW"
},
{
"names": [
"process_vm_readv",
"process_vm_writev",
"ptrace"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"minKernel": "4.8"
}
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 0,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 8,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 131072,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 131080,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 4294967295,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"sync_file_range2"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"ppc64le"
]
}
},
{
"names": [
"arm_fadvise64_64",
"arm_sync_file_range",
"sync_file_range2",
"breakpoint",
"cacheflush",
"set_tls"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"arm",
"arm64"
]
}
},
{
"names": [
"arch_prctl"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"amd64",
"x32"
]
}
},
{
"names": [
"modify_ldt"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"amd64",
"x32",
"x86"
]
}
},
{
"names": [
"s390_pci_mmio_read",
"s390_pci_mmio_write",
"s390_runtime_instr"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"s390",
"s390x"
]
}
},
{
"names": [
"open_by_handle_at"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_DAC_READ_SEARCH"
]
}
},
{
"names": [
"bpf",
"clone",
"clone3",
"fanotify_init",
"fsconfig",
"fsmount",
"fsopen",
"fspick",
"lookup_dcookie",
"mount",
"move_mount",
"name_to_handle_at",
"open_tree",
"perf_event_open",
"quotactl",
"setdomainname",
"sethostname",
"setns",
"syslog",
"umount",
"umount2",
"unshare"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_ADMIN"
]
}
},
{
"names": [
"clone"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 2114060288,
"op": "SCMP_CMP_MASKED_EQ"
}
],
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
],
"arches": [
"s390",
"s390x"
]
}
},
{
"names": [
"clone"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 1,
"value": 2114060288,
"op": "SCMP_CMP_MASKED_EQ"
}
],
"comment": "s390 parameter ordering for clone is different",
"includes": {
"arches": [
"s390",
"s390x"
]
},
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
]
}
},
{
"names": [
"clone3"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 38,
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
]
}
},
{
"names": [
"reboot"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_BOOT"
]
}
},
{
"names": [
"chroot"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_CHROOT"
]
}
},
{
"names": [
"delete_module",
"init_module",
"finit_module"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_MODULE"
]
}
},
{
"names": [
"acct"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_PACCT"
]
}
},
{
"names": [
"kcmp",
"pidfd_getfd",
"process_madvise",
"process_vm_readv",
"process_vm_writev",
"ptrace"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_PTRACE"
]
}
},
{
"names": [
"iopl",
"ioperm"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_RAWIO"
]
}
},
{
"names": [
"settimeofday",
"stime",
"clock_settime"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_TIME"
]
}
},
{
"names": [
"vhangup"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_TTY_CONFIG"
]
}
},
{
"names": [
"get_mempolicy",
"mbind",
"set_mempolicy"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_NICE"
]
}
},
{
"names": [
"syslog"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYSLOG"
]
}
}
]
}
Binary file not shown.
+32
View File
@@ -0,0 +1,32 @@
#!/bin/sh
# Copy config file if it doesn't exist so that the app can start
if [ ! -f /app/config/config.cfg ] ; then
echo "Config file not found at /app/config/config.cfg"
echo "Copying example configuration file to run."
mkdir -p /app/config
cp /app/docker/default-config-plugins.cfg /app/config/config.cfg || exit 1
# This file will be ignored when switching away from the default (sqlite)
cp /app/docker/gl.db /app/config/gl.db || exit 1
fi
echo "";
echo "Version and build information:";
echo "";
# Output version string to logs
/app/glauth --version
echo "";
echo "Starting GLauth now.";
echo "";
# Run app
cd /app
/app/glauth -c /app/config/config.cfg
echo ""
echo "GLauth has exited."
echo "Exiting."
@@ -4,7 +4,8 @@
if [ ! -f /app/config/config.cfg ] ; then
echo "Config file not found at /app/config/config.cfg"
echo "Copying example configuration file to run."
cp /app/docker/default-config.cfg /app/config/config.cfg || exit 1
mkdir -p /app/config
cp /app/docker/default-config-standalone.cfg /app/config/config.cfg || exit 1
fi
+127
View File
@@ -0,0 +1,127 @@
#!/bin/bash
inform() {
echo "--------------------------------------------------------------------------------"
echo "$@"
echo "--------------------------------------------------------------------------------"
}
image_name() {
[[ "$1" == "plugins" ]] && { echo "glauth-plugins"; } || { echo "glauth"; }
}
ldap_port() {
[[ "$1" == "plugins" ]] && { echo "3893"; } || { echo "3893"; }
}
prepare_image() {
profile="$1"
img="localhost/$(image_name $profile)"
inform "Building profile: $profile container: $img"
podman build --security-opt seccomp=scripts/docker/fastat-workaround.json -f Dockerfile-$profile -t $img
inform "Starting registry"
podman run -d --name registry -p 5000:5000 -v ./local/registry:/var/lib/registry --restart=unless-stopped registry:2
inform "Pushing image to container"
podman tag $img localhost:5000/$(image_name $profile)
podman push --tls-verify=false localhost:5000/$(image_name $profile)
if command -v ldapsearch &> /dev/null; then
inform "Running image"
podman run -d --name checkglauth -p $(ldap_port $profile):$(ldap_port $profile) localhost:5000/$(image_name $profile)
sleep 3
inform "Testing image"
checkfailed=0
if (ldapsearch -LLL -H ldap://localhost:$(ldap_port $profile) -D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com -w mysecret -x -bdc=glauth,dc=com cn=hackers | grep posixAccoun) &> /dev/null; then
echo "Checked glauth is responding properly to ldapsearch query."
else
checkfailed=1
echo "glauth check did not pass. Aborting."
fi
inform "Stopping image"
podman stop checkglauth && podman rm checkglauth
if [[ checkfailed -eq 1 ]]; then
# Note that we are not removing the registry so that
# we can manually check why we failed.
exit 1
fi
else
echo "Skipping ldapsearch sanity check. Command not present."
fi
inform "Stopping registry"
podman stop registry && podman rm registry
}
prepare_images() {
prepare_image standalone
prepare_image plugins
}
push_profile_to_docker() {
profile="$1"
tag="$2"
REGISTRY_AUTH_FILE=~/.podmanauth
echo "Pushing image to docker hub"
podman login
podman push localhost:5000/$(image_name $profile) docker.io/glauth/$(image_name $profile):$tag
}
push_to_docker() {
tag="$1"
push_profile_to_docker standalone $tag
push_profile_to_docker plugins $tag
}
TAG="$1"
if [[ "$1" != "--force" ]]; then
tested=0
found=0
if command -v lsof &> /dev/null; then
tested=1
if lsof -i :5000 &> /dev/null; then
found=1
fi
elif command -v fuser &> /dev/null; then
tested=1
if fuser 5000/tcp &> /dev/null; then
found=1
fi
elif command -v netstat &> /dev/null; then
tested=1
if (netstat -plnt | grep 5000) &> /dev/null; then
found=1
fi
fi
if [[ tested -eq 0 ]]; then
echo "Unable to figure out whether a registry is already running."
echo "Please install lsof, fuser or netstat, or run with '--force'"
exit 1
fi
if [[ found -eq 1 ]]; then
echo "Port 5000 is busy. I will not be able to run the local registry. Aborting."
echo "You can avoid this check by running with '--force'"
exit 1
fi
else
TAG="$2"
fi
if [[ "$TAG" == "" ]]; then
echo "Please provide a tag for this image."
exit 1
fi
prepare_images
while true; do
read -p "Everything seems ok. Push to Docker registry? (y/n) " yn
case $yn in
[Yy]* ) push_to_docker $TAG; break;;
[Nn]* ) exit;;
esac
done
+5
View File
@@ -0,0 +1,5 @@
# Contributor Code of Conduct
This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters.
For more information please visit the [No Code of Conduct](https://github.com/domgetter/NCoC) homepage.
+65
View File
@@ -0,0 +1,65 @@
#################
# Build Step
#################
FROM golang:buster as build
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
# Setup work env
RUN mkdir /app /tmp/gocode
ADD local/app /app/
WORKDIR /app/v2
# Required envs for GO
ENV GOPATH=/tmp/gocode
ENV GOOS=linux
ENV GOARCH=amd64
ENV GO_ENABLED=1
# Only needed for alpine builds // also: busybox
RUN apt -qq update && apt -qq install -y git make build-essential busybox-static dumb-init
# Install deps
RUN go get -d -v ./...
# Build and copy final result
RUN uname -a
RUN uname -m
RUN bash -c '[[ $(uname -m) == x86_64 ]] && { make linuxamd64 && cp ./bin/linuxamd64/glauth /app/v2/glauth; make plugins_linux_amd64 && cp ./bin/linuxamd64/*.so /app/v2/; } || { true; }'
RUN bash -c '[[ $(uname -m) == aarch64 ]] && { make linuxarm64 && cp ./bin/linuxarm64/glauth /app/v2/glauth; make plugins_linux_arm64 && cp ./bin/linuxarm64/*.so /app/v2/; } || { true; }'
RUN ls -l /app/v2/bin/linuxamd64
# Check glauth works
RUN /app/v2/glauth --version
#################
# Run Step
#################
FROM gcr.io/distroless/base-debian10 as run
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
# Copy binary from build container
COPY --from=build /app/v2/glauth /app/glauth
COPY --from=build /app/v2/*.so /app/
# Copy docker specific scripts from build container
COPY --from=build /app/v2/scripts/docker/start-plugins.sh /app/docker/
COPY --from=build /app/v2/scripts/docker/default-config-plugins.cfg /app/docker/
COPY --from=build /app/v2/scripts/docker/gl.db /app/docker/
# Just what we need
COPY --from=build /usr/bin/dumb-init /usr/bin/dumb-init
COPY --from=build /bin/busybox /bin/sh
COPY --from=build /bin/busybox /bin/ln
COPY --from=build /bin/busybox /bin/rm
# Debug env: COPY --from=build /bin/busybox /bin/busybox
RUN ln /bin/sh /usr/bin/cp && ln /bin/sh /usr/bin/mkdir && rm /bin/ln /bin/rm
# Install init
# Expose web and LDAP ports
EXPOSE 389 636 5555
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/bin/sh", "/app/docker/start-plugins.sh"]
+61
View File
@@ -0,0 +1,61 @@
#################
# Build Step
#################
FROM golang:alpine as build
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
# Setup work env
RUN mkdir /app /tmp/gocode
ADD local/app /app/
WORKDIR /app/v2
# Required envs for GO
ENV GOPATH=/tmp/gocode
ENV GOOS=linux
ENV GOARCH=amd64
ENV CGO_ENABLED=0
# Only needed for alpine builds // also: busybox
RUN apk add --no-cache git make busybox-static dumb-init
# Install deps
RUN go get -d -v ./...
# Build and copy final result
RUN uname -a
RUN if [ $(uname -m) == x86_64 ]; then make linuxamd64 && cp ./bin/linuxamd64/glauth /app/v2/glauth; fi
RUN if [ $(uname -m) == aarch64 ]; then make linuxarm64 && cp ./bin/linuxarm64/glauth /app/v2/glauth; fi
RUN if [ $(uname -m) == armv7l ]; then make linuxarm && cp ./bin/linuxarm/glauth /app/v2/glauth; fi
# Check glauth works
RUN /app/v2/glauth --version
#################
# Run Step
#################
FROM gcr.io/distroless/base-debian10 as run
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
# Copy binary from build container
COPY --from=build /app/v2/glauth /app/glauth
# Copy docker specific scripts from build container
COPY --from=build /app/v2/scripts/docker/start-standalone.sh /app/docker/
COPY --from=build /app/v2/scripts/docker/default-config-standalone.cfg /app/docker/
# Just what we need
COPY --from=build /usr/bin/dumb-init /usr/bin/dumb-init
COPY --from=build /bin/busybox.static /bin/sh
COPY --from=build /bin/busybox.static /bin/ln
COPY --from=build /bin/busybox.static /bin/rm
RUN ln /bin/sh /usr/bin/cp && ln /bin/sh /usr/bin/mkdir && rm /bin/ln /bin/rm
# Install init
# Expose web and LDAP ports
EXPOSE 389 636 5555
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/bin/sh", "/app/docker/start-standalone.sh"]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Ned McClain & Ben Yanke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+95
View File
@@ -0,0 +1,95 @@
VERSION=$(shell bin/linuxamd64/glauth --version)
GIT_COMMIT=$(shell git rev-list -1 HEAD )
BUILD_TIME=$(shell date -u +%Y%m%d_%H%M%SZ)
GIT_CLEAN=$(shell git status | grep -E "working (tree|directory) clean" | wc -l | sed 's/^[ ]*//')
# Last git tag
LAST_GIT_TAG=$(shell git describe --abbrev=0 --tags 2> /dev/null)
# this=1 if the current commit is the tagged commit (ie, if this is a release build)
GIT_IS_TAG_COMMIT=$(shell git describe --abbrev=0 --tags > /dev/null 2> /dev/null && echo "1" || echo "0")
# Used when a tag isn't available
GIT_BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
# Build variables
BUILD_VARS=-s -w -X main.GitCommit=${GIT_COMMIT} -X main.GitBranch=${GIT_BRANCH} -X main.BuildTime=${BUILD_TIME} -X main.GitClean=${GIT_CLEAN} -X main.LastGitTag=${LAST_GIT_TAG} -X main.GitTagIsCommit=${GIT_IS_TAG_COMMIT}
BUILD_FILES=glauth.go
TRIM_FLAGS=-gcflags "all=-trimpath=${PWD}" -asmflags "all=-trimpath=${PWD}"
# Plugins
include pkg/plugins/Makefile
#####################
# High level commands
#####################
# Build and run - used for development
run: setup devrun
# Run the integration test on linuxamd64 (eventually allow the binary to be set)
test: runtest
# Run build process for all binaries
all: setup binaries verify
# Run build process for only linuxamd64
fast: setup linuxamd64 verify
# list of binary formats to build
binaries: linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64
# Setup commands to always run
setup: getdeps format
#####################
# Subcommands
#####################
# Run integration test
runtest:
./scripts/ci/integration-test.sh cleanup
# Get all dependencies
getdeps:
go get -d ./...
updatetest:
./scripts/ci/integration-test.sh
format:
go fmt
devrun:
go run ${BUILD_FILES} -c sample-simple.cfg
linux386:
mkdir -p bin/$@ && GOOS=linux GOARCH=386 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/$@/glauth ${BUILD_FILES} && cd bin/$@ && sha256sum glauth > glauth.sha256
linuxamd64:
mkdir -p bin/$@ && GOOS=linux GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/$@/glauth ${BUILD_FILES} && cd bin/$@ && sha256sum glauth > glauth.sha256
linuxarm:
mkdir -p bin/$@ && GOOS=linux GOARCH=arm go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/$@/glauth ${BUILD_FILES} && cd bin/$@ && sha256sum glauth > glauth.sha256
linuxarm64:
mkdir -p bin/$@ && GOOS=linux GOARCH=arm64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/$@/glauth ${BUILD_FILES} && cd bin/$@ && sha256sum glauth > glauth.sha256
darwinamd64:
mkdir -p bin/$@ && GOOS=darwin GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/$@/glauth ${BUILD_FILES} && cd bin/$@ && sha256sum glauth > glauth.sha256
darwinarm64:
mkdir -p bin/$@ && GOOS=darwin GOARCH=arm64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/$@/glauth ${BUILD_FILES} && cd bin/$@ && sha256sum glauth > glauth.sha256
win386:
mkdir -p bin/$@ && GOOS=windows GOARCH=386 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/$@/glauth ${BUILD_FILES} && cd bin/$@ && sha256sum glauth > glauth.sha256
winamd64:
mkdir -p bin/$@ && GOOS=windows GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/$@/glauth ${BUILD_FILES} && cd bin/$@ && sha256sum glauth > glauth.sha256
verify:
@for binary in linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64; do cd bin/$$binary && sha256sum glauth.sha256 -c && cd ../..; done
release: linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64

Some files were not shown because too many files have changed in this diff Show More