mirror of
https://github.com/glauth/glauth.git
synced 2026-10-27 06:26:59 +00:00
Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f925565a6 | |||
| f5ea3d93bf | |||
| 34a6b30b8f | |||
| 9bbad511cb | |||
| 7cef07931c | |||
| 37fa74892e | |||
| efbd64504d | |||
| 1920c4324f | |||
| 7e7a5f59d1 | |||
| 83f4305e36 | |||
| b45259d567 | |||
| 3f9f67375f | |||
| cb761e8e70 | |||
| 7c3314a504 | |||
| 690917e4e0 | |||
| 0ccf4926ff | |||
| 598a688214 | |||
| 0e7769ff84 | |||
| 6500bbaf36 | |||
| e92197ac08 | |||
| 65385fb82d | |||
| aadf150fec | |||
| 122c86c446 | |||
| 0d03b226b6 | |||
| f41b5ee19e | |||
| aad166b3ec | |||
| 6b14714eb4 | |||
| 3db5c5e54a | |||
| 1a78b51174 | |||
| 9865de68ac | |||
| 32750d2abd | |||
| 021324c775 | |||
| edaddca4e7 | |||
| 1af3cf4f69 | |||
| 3e2e24cadb | |||
| 249388fdd1 | |||
| 45651645e7 | |||
| 42ff05f141 | |||
| 598d746ee8 | |||
| 6800da8bf8 | |||
| 57ee724556 | |||
| ea4cd57233 | |||
| 5d79fd9b03 | |||
| 385cc335ca | |||
| 851c21df04 | |||
| ceefc66dbb | |||
| e26219c2f4 | |||
| 2222a791e4 | |||
| eef3a5c6a0 | |||
| e7195ed983 | |||
| 1299a6d002 | |||
| 95ac9708ed | |||
| 363cd76fa2 | |||
| 82bc471735 | |||
| e1e586f464 | |||
| 31fbd790e8 | |||
| ef3151c287 | |||
| de3d1cec1c | |||
| 7faac6df69 | |||
| e86d6000d9 | |||
| c7098406d0 | |||
| 911e933020 | |||
| 53eddd67c9 | |||
| c2360259af | |||
| 7e49d25591 | |||
| 7267d7b129 | |||
| 0e8937c61e | |||
| fd85683ff0 | |||
| 2b45aefd74 | |||
| 05c537dabd | |||
| 8c8bc06c3b | |||
| fed91e4aef | |||
| 7be05404e1 | |||
| af9062f1a1 | |||
| aed137909a | |||
| 846f0509d6 | |||
| 06afbdcee5 | |||
| 2e0f7f7335 | |||
| b07cd1f256 | |||
| 2ccb8ce02e | |||
| 5dfbbb2f4b | |||
| 3a30656c18 | |||
| 3f53bb983a | |||
| 3b7a548389 | |||
| 0ec6f15957 | |||
| be26675ef8 | |||
| 8ef3e2b8d8 | |||
| ccb839f0a5 | |||
| 1b92cfbae6 | |||
| 8223029345 | |||
| 7c7934aea3 | |||
| 9c9294485b | |||
| 4cf38893ac | |||
| 674a6bdbf4 | |||
| 7b082dccb8 | |||
| 10b0191818 | |||
| 53ddd3c4f4 | |||
| 2065fc39f4 | |||
| e2724eadc2 | |||
| 9c197cc35e | |||
| 25a1648706 | |||
| cbb84b8641 | |||
| 2eb2215ae4 | |||
| 225f1b441b | |||
| 5681dc4d02 | |||
| bf5ddba2be | |||
| fcf784ecf0 | |||
| 07763f52e6 | |||
| 262465d63b | |||
| f28f1a115b | |||
| f4ceb0c3d4 | |||
| 9fb9c87625 | |||
| faed82ad44 | |||
| fd3ac7e4bb | |||
| 4f029234b2 | |||
| d6ba7d0cbc | |||
| 4d42af8aac | |||
| fda48f5524 | |||
| b7cfc11f5c | |||
| bbb381a68e | |||
| 28136302b7 | |||
| 4e600736f2 | |||
| 2d4f5d5476 | |||
| 29cf021694 | |||
| 592fdf4e79 |
@@ -0,0 +1,54 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/go
|
||||
{
|
||||
"name": "glauth",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/go:0-1.19-bullseye",
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
"features": {
|
||||
"docker-in-docker": {
|
||||
"version": "latest",
|
||||
"moby": true
|
||||
},
|
||||
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
|
||||
"packages": "oathtool,ldap-utils"
|
||||
}
|
||||
},
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
// Configure properties specific to VS Code.
|
||||
"vscode": {
|
||||
"settings": {},
|
||||
"extensions": [
|
||||
"golang.go",
|
||||
"ms-azuretools.vscode-docker"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"containerEnv": {
|
||||
"REPO": "glauth"
|
||||
},
|
||||
|
||||
"capAdd": ["SYS_PTRACE"],
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [9000],
|
||||
|
||||
// Use 'portsAttributes' to set default properties for specific forwarded ports.
|
||||
// More info: https://containers.dev/implementors/json_reference/#port-attributes
|
||||
"portsAttributes": {
|
||||
"3893": {
|
||||
"label": "LDAP",
|
||||
"onAutoForward": "notify"
|
||||
},
|
||||
"5555": {
|
||||
"label": "Web UI",
|
||||
"onAutoForward": "openBrowser"
|
||||
}
|
||||
},
|
||||
|
||||
"postCreateCommand": "cd v2 && make setup"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.19.x, 1.20.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 libpam0g-dev
|
||||
- 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 fast
|
||||
- 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
|
||||
@@ -0,0 +1,41 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master", "dev" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
schedule:
|
||||
- cron: "46 1 * * 3"
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ javascript ]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-and-quality
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
@@ -0,0 +1,22 @@
|
||||
# This workflow was added by CodeSee. Learn more at https://codesee.io/
|
||||
# This is v2.0 of this workflow file
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
name: CodeSee
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
codesee:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
name: Analyze the repo with CodeSee
|
||||
steps:
|
||||
- uses: Codesee-io/codesee-action@v2
|
||||
with:
|
||||
codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }}
|
||||
+5
-19
@@ -1,20 +1,6 @@
|
||||
# Ignore default config file for testing
|
||||
glauth.cfg
|
||||
|
||||
# Ignore the built binary during testing
|
||||
glauth
|
||||
|
||||
# Ignore generated certs in this folder
|
||||
certs/
|
||||
|
||||
# Built binaries
|
||||
bin
|
||||
|
||||
pkg/assets/bindata.go
|
||||
|
||||
.*.swp
|
||||
|
||||
# Ignoring travis CLI tool logs
|
||||
*travis.log
|
||||
|
||||
certs/
|
||||
v2/certs/
|
||||
v2/bin
|
||||
v2/docker/assets/linux
|
||||
v2/gl.db
|
||||
.vscode/
|
||||
|
||||
-79
@@ -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
@@ -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"]
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
VERSION=$(shell bin/glauth64 --version)
|
||||
|
||||
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)
|
||||
|
||||
# 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=-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}"
|
||||
|
||||
#####################
|
||||
# High level commands
|
||||
#####################
|
||||
|
||||
# Build and run - used for development
|
||||
run: setup devrun cleanup
|
||||
|
||||
# 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
|
||||
|
||||
# Run build process for only linux64
|
||||
fast: setup linux64 verify cleanup
|
||||
|
||||
# list of binary formats to build
|
||||
binaries: linux32 linux64 linuxarm32 linuxarm64 darwin64 win32 win64
|
||||
|
||||
# Setup commands to always run
|
||||
setup: getdeps bindata format
|
||||
|
||||
#####################
|
||||
# Subcommands
|
||||
#####################
|
||||
|
||||
# Run integration test
|
||||
runtest:
|
||||
./scripts/travis/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
|
||||
|
||||
format:
|
||||
go fmt
|
||||
|
||||
devrun:
|
||||
go run ${BUILD_FILES} -c glauth.cfg
|
||||
|
||||
|
||||
linux32:
|
||||
GOOS=linux GOARCH=386 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth32 ${BUILD_FILES} && cd bin && sha256sum glauth32 > glauth32.sha256
|
||||
|
||||
linux64:
|
||||
GOOS=linux GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth64 ${BUILD_FILES} && cd bin && sha256sum glauth64 > glauth64.sha256
|
||||
|
||||
linuxarm32:
|
||||
GOOS=linux GOARCH=arm go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-arm32 ${BUILD_FILES} && cd bin && sha256sum glauth-arm32 > glauth-arm32.sha256
|
||||
|
||||
linuxarm64:
|
||||
GOOS=linux GOARCH=arm64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-arm64 ${BUILD_FILES} && cd bin && sha256sum glauth-arm64 > glauth-arm64.sha256
|
||||
|
||||
darwin64:
|
||||
GOOS=darwin GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauthOSX ${BUILD_FILES} && cd bin && sha256sum glauthOSX > glauthOSX.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
|
||||
|
||||
win64:
|
||||
GOOS=windows GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-win64 ${BUILD_FILES} && cd bin && sha256sum glauth-win64 > glauth-win64.sha256
|
||||
|
||||
|
||||
verify:
|
||||
cd bin && sha256sum *.sha256 -c && cd ../;
|
||||
@@ -1,50 +1,51 @@
|
||||
# GLAuth: LDAP authentication server for developers
|
||||
Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ configurable backends.
|
||||
|
||||
[](https://travis-ci.org/glauth/glauth)
|
||||
[](https://github.com/glauth/glauth/graphs/commit-activity)
|
||||
[](https://gitter.im/glauth/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](hey)
|
||||
|
||||
[](https://hub.docker.com/r/glauth/glauth/)
|
||||
[](https://hub.docker.com/r/glauth/glauth/)
|
||||

|
||||

|
||||
|
||||
[](https://codeclimate.com/github/glauth/glauth/maintainability)
|
||||
[](https://codeclimate.com/github/glauth/glauth/test_coverage)
|
||||
|
||||
[](http://paypal.me/benyanke)
|
||||

|
||||

|
||||
|
||||
* 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!*
|
||||
|
||||
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).
|
||||
2. Download the [example config file](https://github.com/glauth/glauth/blob/master/v2/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
|
||||
|
||||
Note - makefile uses git data to inject build-time variables. For best results, run in the context of the git repo.
|
||||
|
||||
*make all* - run build binaries for platforms
|
||||
### Documentation
|
||||
|
||||
*make fast* - run build for only linux 64 bit
|
||||
<h4 align="center">:point_right: The latest version of GLauth's documentation is available at https://glauth.github.io/ :point_left:</h4>
|
||||
|
||||
*make run* - wrapper for the 'go run' command, setting up the needed tooling
|
||||
<hr>
|
||||
|
||||
*make test* - run the integration test on linux64 binary
|
||||
### Quickstart
|
||||
|
||||
Get started in three short [steps](https://glauth.github.io/docs/quickstart.html)
|
||||
|
||||
### Usage:
|
||||
```
|
||||
@@ -60,6 +61,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,145 +81,33 @@ 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
|
||||
```
|
||||
In order to use S3, you must set your AWS credentials. Either:
|
||||
|
||||
1. set the -K and -S command-line flags **OR**
|
||||
2. set the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.
|
||||
|
||||
More configuration options are documented here: https://github.com/glauth/glauth/blob/master/sample-simple.cfg
|
||||
|
||||
### Required Fields
|
||||
* Name
|
||||
* The user's username
|
||||
* ou
|
||||
* ID of the user's primary group
|
||||
* uidnumber
|
||||
* The user's unix user id
|
||||
* sshPublicKey
|
||||
* Specify an array of public keys
|
||||
|
||||
### Optional Fields
|
||||
* otherGroups
|
||||
* Array of IDs of groups the user is a member of.
|
||||
* Example: [5501, 5002]
|
||||
* default = blank
|
||||
* givenname
|
||||
* First name
|
||||
* Example: John
|
||||
* default = blank
|
||||
* sn
|
||||
* Last name
|
||||
* Example: Doe
|
||||
* default = blank
|
||||
* disabled
|
||||
* Specify if account is active.
|
||||
* Set to 'true' (without quotes) to make the LDAP entry add 'AccountStatus = inactive'
|
||||
* default = false (active)
|
||||
* mail
|
||||
* Specify an email
|
||||
* example: jdoe@example.com
|
||||
* default = blank
|
||||
* loginshell
|
||||
* Specify a different login shell for the user
|
||||
* Example: /bin/sh, or /sbin/nologin
|
||||
* default = /bin/bash
|
||||
* homedirectory
|
||||
* Specify an overridden home directory for the user
|
||||
* Example: /home/itadmin
|
||||
* default = /home/[username]
|
||||
* otpsecret
|
||||
* Specify OTP secret used to validate OTP passcode
|
||||
* Example: 3hnvnk4ycv44glzigd6s25j4dougs3rk
|
||||
* 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"]
|
||||
* default = blank
|
||||
* yubikey
|
||||
* Specify Yubikey ID for maching Yubikey OTP against the user
|
||||
* Example: cccjgjgkhcbb
|
||||
* default = blank
|
||||
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
#### 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.
|
||||
|
||||
#### App Passwords
|
||||
Additionally, you can specify an array of password hashes using the `passappsha256` for app passwords. These are not OTP validated, and are hashed in the same way as a password. This allows you to generate a long random string to be used in software which requires the ability to authenticate.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
When a user has been configured with either one of the OTP options, the OTP authentication is required for the user. If both are configured, either one will work.
|
||||
More configuration options are documented [here](https://glauth.github.io/docs/file.html) and in this [sample file](https://github.com/glauth/glauth/blob/master/v2/sample-simple.cfg)
|
||||
|
||||
### Backends:
|
||||
For advanced users, GLAuth supports pluggable backends. Currently, it can use a local file, S3 or an existing LDAP infrastructure. In the future, we hope to have backends that support Mongo, SQL, and other datastores.
|
||||
|
||||
For advanced users, GLAuth supports pluggable backends. Currently, it can use a local file, S3 or an existing LDAP infrastructure. Through the use of optional plugins, you can connect SQL databases, PAM, and other datastores.
|
||||
|
||||
```toml
|
||||
[backend]
|
||||
datastore = "ldap"
|
||||
servers = [ "ldaps://server1:636", "ldaps://server2:636" ]
|
||||
```
|
||||
|
||||
### Production:
|
||||
Any of the architectures above will work for production. Just remember:
|
||||
# Stargazers over time
|
||||
|
||||
* Always use legit SSL certs for production!
|
||||
[](https://starchart.cc/glauth/glauth)
|
||||
|
||||
# Other Architectures
|
||||
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
|
||||
```
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://starcharts.herokuapp.com/glauth/glauth)
|
||||
|
||||
### Support
|
||||
|
||||
Support the ongoing development of GLAuth!
|
||||
|
||||
**Paypal**
|
||||
|
||||
[](http://paypal.me/benyanke)
|
||||
|
||||
|
||||
**Bitcoin Address**
|
||||
|
||||
39z2Zkoc24LsuiqCQNFe7QrX4da3mzbGjK
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/GeertJohan/yubigo"
|
||||
docopt "github.com/docopt/docopt-go"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
"github.com/glauth/glauth/pkg/frontend"
|
||||
"github.com/glauth/glauth/pkg/server"
|
||||
"github.com/glauth/glauth/pkg/stats"
|
||||
"github.com/jinzhu/copier"
|
||||
logging "github.com/op/go-logging"
|
||||
"gopkg.in/amz.v1/aws"
|
||||
"gopkg.in/amz.v1/s3"
|
||||
)
|
||||
|
||||
// Set with buildtime vars
|
||||
var LastGitTag string
|
||||
var BuildTime string
|
||||
var GitCommit string
|
||||
var GitClean string
|
||||
var GitBranch string
|
||||
var GitTagIsCommit string
|
||||
|
||||
const programName = "glauth"
|
||||
|
||||
var usage = `glauth: securely expose your LDAP for external auth
|
||||
|
||||
Usage:
|
||||
glauth [options] -c <file|s3 url>
|
||||
glauth -h --help
|
||||
glauth --version
|
||||
|
||||
Options:
|
||||
-c, --config <file> Config file.
|
||||
-K <aws_key_id> AWS Key ID.
|
||||
-S <aws_secret_key> AWS Secret Key.
|
||||
-r <aws_region> AWS Region [default: us-east-1].
|
||||
-h, --help Show this screen.
|
||||
--version Show version.
|
||||
`
|
||||
|
||||
var (
|
||||
log *logging.Logger
|
||||
args map[string]interface{}
|
||||
stderr *logging.LogBackend
|
||||
yubiAuth *yubigo.YubiAuth
|
||||
|
||||
activeConfig = &config.Config{}
|
||||
)
|
||||
|
||||
// Reads builtime vars and returns a full string containing info about
|
||||
// the currently running version of the software. Primarily used by the
|
||||
// --version flag at runtime.
|
||||
func getVersionString() string {
|
||||
|
||||
var versionstr string
|
||||
|
||||
versionstr = "GLauth"
|
||||
|
||||
// Notate the git context of the build
|
||||
switch {
|
||||
// If a release, use the tag
|
||||
case GitClean == "1" && GitTagIsCommit == "1":
|
||||
versionstr += " " + LastGitTag + "\n\n"
|
||||
|
||||
// If this branch had a tag before, mention the branch and the tag to give a rough idea of the base version
|
||||
case len(GitBranch) > 1 && len(LastGitTag) > 1:
|
||||
versionstr += "\nNon-release build from branch " + GitBranch + ", based on tag " + LastGitTag + "\n\n"
|
||||
|
||||
// If no previous tag specified, just mention the branch
|
||||
case len(GitBranch) > 1:
|
||||
versionstr += "\nNon-release build from branch " + GitBranch + "\n\n"
|
||||
|
||||
// Fallback message, if all else fails
|
||||
default:
|
||||
versionstr += "\nNon-release build\n\n"
|
||||
}
|
||||
|
||||
// Include build time
|
||||
if len(BuildTime) > 1 {
|
||||
versionstr += "Build time: " + BuildTime + "\n"
|
||||
}
|
||||
|
||||
// Add commit hash
|
||||
if GitClean == "1" && len(GitCommit) > 1 {
|
||||
versionstr += "Commit: " + GitCommit + "\n"
|
||||
}
|
||||
|
||||
return versionstr
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
stderr = initLogging()
|
||||
log.Debug("AP start")
|
||||
|
||||
if err := parseArgs(); err != nil {
|
||||
log.Fatalf("Could not parse command-line arguments", err.Error())
|
||||
}
|
||||
if err := doConfig(); err != nil {
|
||||
log.Fatalf("Configuration file error: %s", err.Error())
|
||||
}
|
||||
|
||||
startService()
|
||||
}
|
||||
|
||||
func startService() {
|
||||
// stats
|
||||
stats.General.Set("version", stats.Stringer(LastGitTag))
|
||||
|
||||
// web API
|
||||
if activeConfig.API.Enabled {
|
||||
log.Debug("Web API enabled")
|
||||
go frontend.RunAPI(log, activeConfig)
|
||||
}
|
||||
|
||||
startConfigWatcher()
|
||||
|
||||
s, err := server.NewServer(log, activeConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not start server: %s", err.Error())
|
||||
}
|
||||
s.ListenAndServe()
|
||||
|
||||
log.Critical("AP exit")
|
||||
}
|
||||
|
||||
func startConfigWatcher() {
|
||||
configFileLocation := getConfigLocation()
|
||||
|
||||
if strings.HasPrefix(configFileLocation, "s3://") {
|
||||
return
|
||||
}
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatalf("Could not start config-watcher: %s", err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
if activeConfig.WatchConfig {
|
||||
log.Info("Error!", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
watcher.Add(configFileLocation)
|
||||
}
|
||||
|
||||
func parseArgs() error {
|
||||
var err error
|
||||
|
||||
if args, err = docopt.Parse(usage, nil, true, getVersionString(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getConfigLocation() string {
|
||||
return args["--config"].(string)
|
||||
}
|
||||
|
||||
func parseConfigFile(configFileLocation string) (*config.Config, error) {
|
||||
cfg := config.Config{}
|
||||
// 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://") {
|
||||
if _, present := aws.Regions[args["-r"].(string)]; present == false {
|
||||
return &cfg, fmt.Errorf("Invalid AWS region: %s", args["-r"])
|
||||
}
|
||||
region := aws.Regions[args["-r"].(string)]
|
||||
auth, err := aws.EnvAuth()
|
||||
if err != nil {
|
||||
if args["-K"] == nil || args["-S"] == nil {
|
||||
return &cfg, fmt.Errorf("AWS credentials not found: must use -K and -S flags, or set these env vars:\n\texport AWS_ACCESS_KEY_ID=\"AAA...\"\n\texport AWS_SECRET_ACCESS_KEY=\"BBBB...\"\n")
|
||||
}
|
||||
auth = aws.Auth{
|
||||
AccessKey: args["-K"].(string),
|
||||
SecretKey: args["-S"].(string),
|
||||
}
|
||||
}
|
||||
// parse S3 url
|
||||
s3url := strings.TrimPrefix(configFileLocation, "s3://")
|
||||
parts := strings.SplitN(s3url, "/", 2)
|
||||
if len(parts) != 2 {
|
||||
return &cfg, fmt.Errorf("Invalid S3 URL: %s", s3url)
|
||||
}
|
||||
b := s3.New(auth, region).Bucket(parts[0])
|
||||
tomlData, err := b.Get(parts[1])
|
||||
if err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
if _, err := toml.Decode(string(tomlData), &cfg); err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
} else { // local config file
|
||||
if _, err := toml.DecodeFile(configFileLocation, &cfg); err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func handleConfig(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])")
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
cfg.LDAP.Enabled = !cfg.Frontend.TLS
|
||||
cfg.LDAPS.Enabled = cfg.Frontend.TLS
|
||||
|
||||
if cfg.Frontend.TLS {
|
||||
cfg.LDAPS.Listen = cfg.Frontend.Listen
|
||||
} else {
|
||||
cfg.LDAP.Listen = cfg.Frontend.Listen
|
||||
}
|
||||
|
||||
if len(cfg.Frontend.Cert) > 0 {
|
||||
cfg.LDAPS.Cert = cfg.Frontend.Cert
|
||||
}
|
||||
if len(cfg.Frontend.Key) > 0 {
|
||||
cfg.LDAPS.Key = cfg.Frontend.Key
|
||||
}
|
||||
}
|
||||
|
||||
if !cfg.LDAP.Enabled && !cfg.LDAPS.Enabled {
|
||||
return &cfg, fmt.Errorf("No server configuration found: please provide either LDAP or LDAPS configuration")
|
||||
}
|
||||
|
||||
if cfg.LDAPS.Enabled {
|
||||
// LDAPS enabled - verify requirements (cert, key, listen)
|
||||
if len(cfg.LDAPS.Cert) == 0 || len(cfg.LDAPS.Key) == 0 {
|
||||
return &cfg, fmt.Errorf("LDAPS was enabled but no certificate or key were specified: please disable LDAPS or use the 'cert' and 'key' options")
|
||||
}
|
||||
|
||||
if len(cfg.LDAPS.Listen) == 0 {
|
||||
return &cfg, fmt.Errorf("No LDAPS bind address was specified: please disable LDAPS or use the 'listen' option")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.LDAP.Enabled {
|
||||
// LDAP enabled - verify listen
|
||||
if len(cfg.LDAP.Listen) == 0 {
|
||||
return &cfg, fmt.Errorf("No LDAP bind address was specified: please disable LDAP or use the 'listen' option")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// doConfig reads the cli flags and config file
|
||||
func doConfig() error {
|
||||
// Parse config-file into config{} struct
|
||||
cfg, err := parseConfigFile(getConfigLocation())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle validation and parsing of old [frontend] section into [ldap] and/or [ldaps] sections
|
||||
cfg, err = handleConfig(*cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Before greenlighting new config entirely, lets make sure the yubiauth works - in case they changed
|
||||
if activeConfig.YubikeyClientID != cfg.YubikeyClientID || activeConfig.YubikeySecret != cfg.YubikeySecret {
|
||||
if len(cfg.YubikeyClientID) > 0 && len(cfg.YubikeySecret) > 0 {
|
||||
_yubiAuth, err := yubigo.NewYubiAuth(cfg.YubikeyClientID, cfg.YubikeySecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// No errors, override
|
||||
yubiAuth = _yubiAuth
|
||||
}
|
||||
}
|
||||
|
||||
// All config is validated and alright, copy to ativeConfig
|
||||
if err := copier.Copy(activeConfig, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle logging settings for new config
|
||||
// - we do this last to make sure we only respect a fully validated config
|
||||
stderr = initLogging()
|
||||
|
||||
if activeConfig.Debug {
|
||||
logging.SetLevel(logging.DEBUG, programName)
|
||||
log.Debug("Debugging enabled")
|
||||
}
|
||||
if activeConfig.Syslog {
|
||||
enableSyslog(stderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initLogging sets up logging to stderr
|
||||
func initLogging() *logging.LogBackend {
|
||||
log = logging.MustGetLogger(programName)
|
||||
|
||||
format := "%{color}%{time:15:04:05.000000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}"
|
||||
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
|
||||
|
||||
logging.SetBackend(logBackend)
|
||||
logging.SetLevel(logging.NOTICE, programName)
|
||||
logging.SetFormatter(logging.MustStringFormatter(format))
|
||||
return logBackend
|
||||
}
|
||||
|
||||
// enableSyslog turns on syslog and turns off color
|
||||
func enableSyslog(stderrBackend *logging.LogBackend) {
|
||||
format := "%{time:15:04:05.000000} %{shortfunc} ▶ %{level:.4s} %{id:03x} %{message}"
|
||||
logging.SetFormatter(logging.MustStringFormatter(format))
|
||||
syslogBackend, err := logging.NewSyslogBackend("")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
logging.SetBackend(stderrBackend, syslogBackend)
|
||||
|
||||
log.Debug("Syslog enabled")
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package main
|
||||
|
||||
// Just the beginning of testing - more to be added
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetVersionString(t *testing.T) {
|
||||
|
||||
// Setup config and check output
|
||||
LastGitTag = "7"
|
||||
BuildTime = "20180616_042045Z"
|
||||
GitCommit = "a9837f3112fc049fd1e9faca6f56d8be1bec91bd"
|
||||
GitClean = "1"
|
||||
GitBranch = "dev"
|
||||
GitTagIsCommit = "0"
|
||||
|
||||
versionOutput := getVersionString()
|
||||
if versionOutput != "7" {
|
||||
t.Errorf("Finish writing this test, output was: %s", versionOutput)
|
||||
}
|
||||
|
||||
// TODO: write these cases
|
||||
|
||||
// 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
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
module github.com/glauth/glauth
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
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/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pquerna/otp v1.2.0
|
||||
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
|
||||
)
|
||||
@@ -1,46 +0,0 @@
|
||||
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/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/davecgh/go-spew v1.1.0/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/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/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/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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/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/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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/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/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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=
|
||||
@@ -1,78 +0,0 @@
|
||||
package config
|
||||
|
||||
// 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
|
||||
}
|
||||
type Frontend struct {
|
||||
AllowedBaseDNs []string // For LDAP backend only
|
||||
Listen string
|
||||
Cert string
|
||||
Key string
|
||||
TLS bool
|
||||
}
|
||||
type LDAP struct {
|
||||
Enabled bool
|
||||
Listen string
|
||||
}
|
||||
type LDAPS struct {
|
||||
Enabled bool
|
||||
Listen string
|
||||
Cert string
|
||||
Key string
|
||||
}
|
||||
type API struct {
|
||||
Cert string
|
||||
Enabled bool
|
||||
Key string
|
||||
Listen string
|
||||
SecretToken string
|
||||
TLS bool
|
||||
}
|
||||
type User struct {
|
||||
Name string
|
||||
OtherGroups []int
|
||||
PassSHA256 string
|
||||
PassAppSHA256 []string
|
||||
PrimaryGroup int
|
||||
SSHKeys []string
|
||||
OTPSecret string
|
||||
Yubikey string
|
||||
Disabled bool
|
||||
UnixID int
|
||||
Mail string
|
||||
LoginShell string
|
||||
GivenName string
|
||||
SN string
|
||||
Homedir string
|
||||
}
|
||||
type Group struct {
|
||||
Name string
|
||||
UnixID int
|
||||
IncludeGroups []int
|
||||
}
|
||||
type Config struct {
|
||||
API API
|
||||
Backend Backend
|
||||
Debug bool
|
||||
WatchConfig bool
|
||||
YubikeyClientID string
|
||||
YubikeySecret string
|
||||
Frontend Frontend
|
||||
LDAP LDAP
|
||||
LDAPS LDAPS
|
||||
Groups []Group
|
||||
Syslog bool
|
||||
Users []User
|
||||
ConfigFile string
|
||||
AwsAccessKeyId string
|
||||
AwsSecretAccessKey string
|
||||
AwsRegion string
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"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)
|
||||
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)
|
||||
if err != nil {
|
||||
log.Fatal(fmt.Sprintf("Error starting HTTP server: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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())
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -1,401 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
"github.com/glauth/glauth/pkg/stats"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/op/go-logging"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
type configHandler struct {
|
||||
log *logging.Logger
|
||||
cfg *config.Config
|
||||
yubikeyAuth *yubigo.YubiAuth
|
||||
}
|
||||
|
||||
func NewConfigHandler(log *logging.Logger, cfg *config.Config, yubikeyAuth *yubigo.YubiAuth) Handler {
|
||||
handler := configHandler{
|
||||
log: log,
|
||||
cfg: cfg,
|
||||
yubikeyAuth: yubikeyAuth}
|
||||
return handler
|
||||
}
|
||||
|
||||
//
|
||||
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)
|
||||
|
||||
h.log.Debug(fmt.Sprintf("Bind request: bindDN: %s, BaseDN: %s, source: %s", bindDN, h.cfg.Backend.BaseDN, conn.RemoteAddr().String()))
|
||||
|
||||
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.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
|
||||
}
|
||||
|
||||
// find the user
|
||||
user := config.User{}
|
||||
found := false
|
||||
for _, u := range h.cfg.Users {
|
||||
if 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
|
||||
group := config.Group{}
|
||||
found = false
|
||||
for _, g := range h.cfg.Groups {
|
||||
if 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
|
||||
}
|
||||
|
||||
//
|
||||
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
|
||||
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})
|
||||
}
|
||||
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})
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
//
|
||||
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)
|
||||
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)
|
||||
members[dn] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, g := range h.cfg.Groups {
|
||||
if gid == g.UnixID {
|
||||
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
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) getGroupMemberIDs(gid int) []string {
|
||||
members := make(map[string]bool)
|
||||
for _, u := range h.cfg.Users {
|
||||
if u.PrimaryGroup == gid {
|
||||
members[u.Name] = true
|
||||
} else {
|
||||
for _, othergid := range u.OtherGroups {
|
||||
if othergid == gid {
|
||||
members[u.Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, g := range h.cfg.Groups {
|
||||
if gid == g.UnixID {
|
||||
for _, includegroupid := range g.IncludeGroups {
|
||||
if includegroupid == gid {
|
||||
h.log.Warning(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
|
||||
}
|
||||
|
||||
// Converts an array of GUIDs into an array of DNs
|
||||
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)
|
||||
groups[dn] = true
|
||||
}
|
||||
|
||||
for _, includegroupid := range g.IncludeGroups {
|
||||
if includegroupid == gid && g.UnixID != gid {
|
||||
includegroupdns := h.getGroupDNs([]int{g.UnixID})
|
||||
|
||||
for _, includegroupdn := range includegroupdns {
|
||||
groups[includegroupdn] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g := []string{}
|
||||
for k, _ := range groups {
|
||||
g = append(g, k)
|
||||
}
|
||||
|
||||
sort.Strings(g)
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) getGroupName(gid int) string {
|
||||
for _, g := range h.cfg.Groups {
|
||||
if g.UnixID == gid {
|
||||
return g.Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package handler
|
||||
|
||||
import "github.com/nmcclain/ldap"
|
||||
|
||||
// interface for backend handler
|
||||
type Handler interface {
|
||||
ldap.Binder
|
||||
ldap.Searcher
|
||||
ldap.Closer
|
||||
}
|
||||
@@ -1,304 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
"github.com/glauth/glauth/pkg/stats"
|
||||
"github.com/kr/pretty"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
type ldapHandler struct {
|
||||
doPing chan bool
|
||||
log *logging.Logger
|
||||
cfg *config.Config
|
||||
lock *sync.Mutex // for sessions and servers
|
||||
sessions map[string]ldapSession
|
||||
servers []ldapBackend
|
||||
}
|
||||
|
||||
// global lock for ldapHandler sessions & servers manipulation
|
||||
var ldaplock sync.Mutex
|
||||
|
||||
type ldapSession struct {
|
||||
id string
|
||||
c net.Conn
|
||||
ldap *ldap.Conn
|
||||
}
|
||||
type ldapBackendStatus int
|
||||
|
||||
const (
|
||||
Down ldapBackendStatus = iota
|
||||
Up
|
||||
)
|
||||
|
||||
type ldapBackend struct {
|
||||
Scheme string
|
||||
Hostname string
|
||||
Port int
|
||||
Status ldapBackendStatus
|
||||
Ping time.Duration
|
||||
}
|
||||
|
||||
func NewLdapHandler(log *logging.Logger, cfg *config.Config) Handler {
|
||||
handler := ldapHandler{ // set non-zero-value defaults here
|
||||
sessions: make(map[string]ldapSession),
|
||||
doPing: make(chan bool),
|
||||
log: log,
|
||||
cfg: cfg,
|
||||
lock: &ldaplock,
|
||||
}
|
||||
// parse LDAP URLs
|
||||
for _, ldapurl := range cfg.Backend.Servers {
|
||||
l, err := parseURL(ldapurl)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
handler.servers = append(handler.servers, l)
|
||||
}
|
||||
|
||||
// test server connectivity before listening, then keep it updated
|
||||
handler.monitorServers()
|
||||
|
||||
return 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()))
|
||||
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()))
|
||||
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()))
|
||||
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
|
||||
}
|
||||
|
||||
//
|
||||
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))
|
||||
stats.Frontend.Add("search_reqs", 1)
|
||||
s, err := h.getSession(conn)
|
||||
if err != nil {
|
||||
stats.Frontend.Add("search_ldapSession_errors", 1)
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, nil
|
||||
}
|
||||
search := ldap.NewSearchRequest(
|
||||
searchReq.BaseDN,
|
||||
searchReq.Scope,
|
||||
searchReq.DerefAliases,
|
||||
searchReq.SizeLimit,
|
||||
searchReq.TimeLimit,
|
||||
searchReq.TypesOnly,
|
||||
searchReq.Filter,
|
||||
searchReq.Attributes,
|
||||
searchReq.Controls,
|
||||
)
|
||||
|
||||
h.log.Debug(fmt.Sprintf("Search req to backend: %# v", pretty.Formatter(search)))
|
||||
sr, err := s.ldap.Search(search)
|
||||
h.log.Debug(fmt.Sprintf("Backend Search result: %# v", pretty.Formatter(sr)))
|
||||
ssr := ldap.ServerSearchResult{
|
||||
Entries: sr.Entries,
|
||||
Referrals: sr.Referrals,
|
||||
Controls: sr.Controls,
|
||||
}
|
||||
h.log.Debug(fmt.Sprintf("Frontend Search result: %# v", pretty.Formatter(ssr)))
|
||||
if err != nil {
|
||||
e := err.(*ldap.Error)
|
||||
h.log.Debug(fmt.Sprintf("Search Err: %# v", pretty.Formatter(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)))
|
||||
return ssr, 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()
|
||||
defer h.lock.Unlock()
|
||||
delete(h.sessions, connID(conn))
|
||||
stats.Frontend.Add("closes", 1)
|
||||
stats.Backend.Add("closes", 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// monitorServers tests server connectivity before listening, then keeps it updated
|
||||
func (h *ldapHandler) monitorServers() {
|
||||
err := h.ping()
|
||||
if err != nil {
|
||||
h.log.Fatal(err)
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-h.doPing:
|
||||
h.log.Notice("doPing requested due to server failure")
|
||||
err = h.ping()
|
||||
if err != nil {
|
||||
h.log.Fatal(err)
|
||||
}
|
||||
case <-time.NewTimer(60 * time.Second).C:
|
||||
h.log.Debug("doPing after timeout")
|
||||
err = h.ping()
|
||||
if err != nil {
|
||||
h.log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//
|
||||
func (h ldapHandler) getSession(conn net.Conn) (ldapSession, error) {
|
||||
id := connID(conn)
|
||||
h.lock.Lock()
|
||||
s, ok := h.sessions[id] // use server connection if it exists
|
||||
h.lock.Unlock()
|
||||
if !ok { // open a new server connection if not
|
||||
var l *ldap.Conn
|
||||
server, err := h.getBestServer() // pick the best server
|
||||
if err != nil {
|
||||
return ldapSession{}, err
|
||||
}
|
||||
dest := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
|
||||
if server.Scheme == "ldaps" {
|
||||
tlsCfg := &tls.Config{}
|
||||
if h.cfg.Backend.Insecure {
|
||||
tlsCfg.InsecureSkipVerify = true
|
||||
}
|
||||
l, err = ldap.DialTLS("tcp", dest, tlsCfg)
|
||||
} else if server.Scheme == "ldap" {
|
||||
l, err = ldap.Dial("tcp", dest)
|
||||
}
|
||||
if err != nil {
|
||||
select {
|
||||
case h.doPing <- true: // non-blocking send
|
||||
default:
|
||||
}
|
||||
return ldapSession{}, err
|
||||
}
|
||||
s = ldapSession{id: id, c: conn, ldap: l}
|
||||
h.lock.Lock()
|
||||
h.sessions[s.id] = s
|
||||
h.lock.Unlock()
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
//
|
||||
func (h ldapHandler) ping() error {
|
||||
healthy := false
|
||||
for k, s := range h.servers {
|
||||
var l *ldap.Conn
|
||||
var err error
|
||||
dest := fmt.Sprintf("%s:%d", s.Hostname, s.Port)
|
||||
start := time.Now()
|
||||
if h.servers[0].Scheme == "ldaps" {
|
||||
tlsCfg := &tls.Config{}
|
||||
if h.cfg.Backend.Insecure {
|
||||
tlsCfg.InsecureSkipVerify = true
|
||||
}
|
||||
l, err = ldap.DialTLS("tcp", dest, tlsCfg)
|
||||
} else if h.servers[0].Scheme == "ldap" {
|
||||
l, err = ldap.Dial("tcp", dest)
|
||||
}
|
||||
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.servers[k].Ping = 0
|
||||
h.servers[k].Status = Down
|
||||
} else {
|
||||
healthy = true
|
||||
h.servers[k].Ping = elapsed
|
||||
h.servers[k].Status = Up
|
||||
l.Close() // prank caller
|
||||
}
|
||||
h.lock.Unlock()
|
||||
}
|
||||
h.log.Debug(fmt.Sprintf("Server health: %# v", pretty.Formatter(h.servers)))
|
||||
b, err := json.Marshal(h.servers)
|
||||
if err != nil {
|
||||
h.log.Error(fmt.Sprintf("Error encoding tail data: %s", err.Error()))
|
||||
}
|
||||
stats.Backend.Set("servers", stats.Stringer(string(b)))
|
||||
if healthy == false {
|
||||
return fmt.Errorf("No healthy servers")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
func (h ldapHandler) getBestServer() (ldapBackend, error) {
|
||||
favorite := ldapBackend{}
|
||||
forever, err := time.ParseDuration("30m")
|
||||
if err != nil {
|
||||
return ldapBackend{}, err
|
||||
}
|
||||
bestping := forever
|
||||
for _, s := range h.servers {
|
||||
if s.Status == Up && s.Ping < bestping {
|
||||
favorite = s
|
||||
bestping = s.Ping
|
||||
}
|
||||
}
|
||||
if bestping == forever {
|
||||
return ldapBackend{}, fmt.Errorf("No healthy servers found")
|
||||
}
|
||||
h.log.Debug(fmt.Sprintf("Best server: %# v", pretty.Formatter(favorite)))
|
||||
return favorite, nil
|
||||
}
|
||||
|
||||
// helper functions
|
||||
func connID(conn net.Conn) string {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(conn.LocalAddr().String() + conn.RemoteAddr().String()))
|
||||
sha := fmt.Sprintf("% x", h.Sum(nil))
|
||||
return string(sha)
|
||||
}
|
||||
func parseURL(ldapurl string) (ldapBackend, error) {
|
||||
u, err := url.Parse(ldapurl)
|
||||
if err != nil {
|
||||
return ldapBackend{}, err
|
||||
}
|
||||
var port int
|
||||
if u.Scheme == "ldaps" {
|
||||
port = 636
|
||||
} else if u.Scheme == "ldap" {
|
||||
port = 389
|
||||
} else {
|
||||
return ldapBackend{}, fmt.Errorf("Unknown LDAP scheme: %s", u.Scheme)
|
||||
}
|
||||
parts := strings.Split(u.Host, ":")
|
||||
hostname := parts[0]
|
||||
if len(parts) > 1 {
|
||||
port, err = strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return ldapBackend{}, err
|
||||
}
|
||||
}
|
||||
return ldapBackend{Scheme: u.Scheme, Hostname: hostname, Port: port}, nil
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
"github.com/glauth/glauth/pkg/handler"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
type LdapSvc struct {
|
||||
log *logging.Logger
|
||||
c *config.Config
|
||||
yubiAuth *yubigo.YubiAuth
|
||||
l *ldap.Server
|
||||
}
|
||||
|
||||
func NewServer(log *logging.Logger, cfg *config.Config) (*LdapSvc, error) {
|
||||
|
||||
s := LdapSvc{
|
||||
log: log,
|
||||
c: cfg,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if len(cfg.YubikeyClientID) > 0 && len(cfg.YubikeySecret) > 0 {
|
||||
s.yubiAuth, err = yubigo.NewYubiAuth(cfg.YubikeyClientID, cfg.YubikeySecret)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.New("Yubikey Auth failed")
|
||||
}
|
||||
}
|
||||
|
||||
// configure the backend
|
||||
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)
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package stats
|
||||
|
||||
type Stringer string
|
||||
|
||||
func (s Stringer) String() string { return string(s) }
|
||||
@@ -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"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
img="localhost/glauth-dev"
|
||||
|
||||
sudo docker rmi -f "$img"
|
||||
|
||||
sudo docker build -f Dockerfile -t "$img" .
|
||||
|
||||
sudo docker run -P "$img"
|
||||
@@ -1,105 +0,0 @@
|
||||
dn: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
cn: superheros
|
||||
uid: superheros
|
||||
description: superheros
|
||||
gidNumber: 5501
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=hackers,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=johndoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: hackers
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: johndoe
|
||||
memberUid: sarahdoe
|
||||
|
||||
dn: cn=svcaccts,ou=groups,dc=glauth,dc=com
|
||||
cn: svcaccts
|
||||
uid: svcaccts
|
||||
description: svcaccts
|
||||
gidNumber: 5502
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
|
||||
memberUid: serviceuser
|
||||
|
||||
dn: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
cn: vpnaccess
|
||||
uid: vpnaccess
|
||||
description: vpnaccess
|
||||
gidNumber: 5503
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=hackers,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=johndoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: hackers
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: johndoe
|
||||
memberUid: sarahdoe
|
||||
|
||||
dn: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
cn: allaccs
|
||||
uid: allaccs
|
||||
description: allaccs
|
||||
gidNumber: 5504
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=hackers,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=johndoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: hackers
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: johndoe
|
||||
memberUid: sarahdoe
|
||||
memberUid: serviceuser
|
||||
|
||||
dn: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
cn: mailadmin
|
||||
uid: mailadmin
|
||||
description: mailadmin
|
||||
gidNumber: 5505
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: sarahdoe
|
||||
|
||||
dn: cn=webmail,ou=groups,dc=glauth,dc=com
|
||||
cn: webmail
|
||||
uid: webmail
|
||||
description: webmail
|
||||
gidNumber: 5506
|
||||
objectClass: posixGroup
|
||||
|
||||
dn: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
cn: fulltime
|
||||
uid: fulltime
|
||||
description: fulltime
|
||||
gidNumber: 5507
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: sarahdoe
|
||||
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
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}"
|
||||
|
||||
# Targets
|
||||
MAIN_TARGETS=linux/amd64,linux/386,linux/arm64,linux/arm-7,darwin/amd64,darwin/arm64,windows/amd64,windows/386
|
||||
PLUGIN_TARGETS=linux/amd64,linux/386,linux/arm64,linux/arm-7,darwin/amd64,darwin/arm64
|
||||
|
||||
# Plugins
|
||||
-include pkg/plugins/*/Makefile
|
||||
|
||||
#####################
|
||||
# High level commands
|
||||
#####################
|
||||
|
||||
#help: @ List available tasks on this project
|
||||
help:
|
||||
@grep -E '[a-zA-Z\.\-]+:.*?@ .*$$' $(MAKEFILE_LIST)| tr -d '#' | sed -E 's/Makefile.//' | awk 'BEGIN {FS = ":.*?@ "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
#run: @ build and run - used for development
|
||||
run: setup devrun
|
||||
|
||||
#test: @ runs the integration test on linuxamd64 (eventually allow the binary to be set)
|
||||
test: runtest
|
||||
|
||||
#all: @ run build process for all binaries
|
||||
all: setup binaries verify
|
||||
|
||||
#fast: @ run build process for only linuxamd64
|
||||
fast: setup linuxamd64
|
||||
|
||||
#binaries: @ list of binary formats to build
|
||||
binaries: linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64
|
||||
|
||||
#setup: @ 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 CGO_ENABLED=1 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 CGO_ENABLED=1 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
|
||||
|
||||
pull-plugin:
|
||||
@git submodule add $U $M && \
|
||||
(for pkg in $$(cat $$M/go.mod | awk '/^require [a-z]/{print $$2} /^\)/{rblock=0} rblock{if($$4!="indirect" && $$1!~"glauth"){print $$1}} /^require \(/{rblock=1}'); do if [ "$$(grep $$pkg go.mod &>/dev/null)" = "" ]; then go get $$pkg; fi; done)
|
||||
|
||||
pull-base-plugins:
|
||||
@U=https://github.com/glauth/glauth-sqlite M=pkg/plugins/glauth-sqlite make pull-plugin && \
|
||||
U=https://github.com/glauth/glauth-mysql M=pkg/plugins/glauth-mysql make pull-plugin && \
|
||||
U=https://github.com/glauth/glauth-postgres M=pkg/plugins/glauth-postgres make pull-plugin && \
|
||||
U=https://github.com/glauth/glauth-pam M=pkg/plugins/glauth-pam make pull-plugin
|
||||
|
||||
forget-plugin:
|
||||
rm -rf $M ../.git/modules/v2/$M && \
|
||||
git config --remove-section submodule.v2/$M && \
|
||||
git rm --cache $M && \
|
||||
go mod tidy
|
||||
|
||||
forget-plugins:
|
||||
for pkg in pkg/plugins/*/go.mod; do\
|
||||
P=$$(echo $$pkg | cut -d'/' -f 3) M=$$(dirname $$pkg) make forget-plugin;\
|
||||
done
|
||||
|
||||
releasemain:
|
||||
@xgo -v -ldflags="${BUILD_VARS}" -trimpath -go 1.18.1 -out glauth -dest bin -buildvcs=false --targets="${MAIN_TARGETS}" .
|
||||
|
||||
releaseplugin:
|
||||
@xgo -v -ldflags="${BUILD_VARS}" -trimpath -go 1.18.1 -out $P -dest bin -buildvcs=false -buildmode=plugin --targets="${PLUGIN_TARGETS}" --pkg $M/$P.go . && \
|
||||
(cd bin && for lib in $$(ls $$P-*); do mv $$lib $$lib.so; done)
|
||||
|
||||
releaseplugins:
|
||||
@for pkg in pkg/plugins/*/go.mod; do\
|
||||
P=$$(echo $$pkg | cut -d'/' -f 3) && make release-$$P;\
|
||||
done
|
||||
|
||||
releasedockermain:
|
||||
$(if $(TAG),,$(error Must set TAG))
|
||||
$(if $(REPO),,$(error Must set REPO - glauth or other))
|
||||
@mkdir -p docker/assets/linux/amd64 docker/assets/linux/arm64 docker/assets/linux/arm/v7 && \
|
||||
cp -f bin/glauth-linux-amd64 docker/assets/linux/amd64/glauth && \
|
||||
cp -f bin/glauth-linux-arm64 docker/assets/linux/arm64/glauth && \
|
||||
cp -f bin/glauth-linux-arm-7 docker/assets/linux/arm/v7/glauth && \
|
||||
docker buildx build --tag $$REPO/glauth:$$TAG -t $$REPO/glauth:latest -f docker/Dockerfile-standalone --platform linux/amd64,linux/arm64,linux/arm/v7 --push docker
|
||||
|
||||
releasedockerplugins:
|
||||
$(if $(TAG),,$(error Must set TAG))
|
||||
$(if $(REPO),,$(error Must set REPO - glauth or other))
|
||||
@mkdir -p docker/assets/linux/amd64 docker/assets/linux/arm64 docker/assets/linux/arm/v7 && \
|
||||
cp -f bin/sqlite-linux-amd64.so docker/assets/linux/amd64/sqlite.so && \
|
||||
cp -f bin/sqlite-linux-arm64.so docker/assets/linux/arm64/sqlite.so && \
|
||||
cp -f bin/sqlite-linux-arm-7.so docker/assets/linux/arm/v7/sqlite.so && \
|
||||
cp -f bin/mysql-linux-amd64.so docker/assets/linux/amd64/mysql.so && \
|
||||
cp -f bin/mysql-linux-arm64.so docker/assets/linux/arm64/mysql.so && \
|
||||
cp -f bin/mysql-linux-arm-7.so docker/assets/linux/arm/v7/mysql.so && \
|
||||
cp -f bin/postgres-linux-amd64.so docker/assets/linux/amd64/postgres.so && \
|
||||
cp -f bin/postgres-linux-arm64.so docker/assets/linux/arm64/postgres.so && \
|
||||
cp -f bin/postgres-linux-arm-7.so docker/assets/linux/arm/v7/postgres.so && \
|
||||
docker buildx build --tag $$REPO/glauth-plugins:$$TAG -t $$REPO/glauth-plugins:latest -f docker/Dockerfile-plugins --platform linux/amd64,linux/arm64,linux/arm/v7 --push docker
|
||||
|
||||
releasedocker: releasedockermain releasedockerplugins
|
||||
|
||||
#testdocker: @ run integration test using docker
|
||||
testdocker:
|
||||
$(if $(REPO),,$(error Must set REPO - glauth or other))
|
||||
@echo "==> Cleaning up any existing image to be on the safe side..." && \
|
||||
(for image in $$(docker image ls -q "$$REPO/glauth*"); do \
|
||||
for container in $$(docker container ls -a -q -f ancestor=$$image); do \
|
||||
docker stop $$container; \
|
||||
while [ "$$(docker container ls -q -f ancestor=$$image)" != "" ]; do sleep 1; done; \
|
||||
docker rm $$container; \
|
||||
done; \
|
||||
docker rmi $$image; \
|
||||
done) && \
|
||||
echo "==> Running glauth main container..." && \
|
||||
docker run -d --name glauth-test -p 3893:3893 $$REPO/glauth:latest && \
|
||||
sleep 5 && \
|
||||
if [ "$$(ldapsearch -LLL -H ldap://localhost:3893 -D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com -w mysecret -x -bdc=glauth,dc=com cn=hackers | grep posixAccount)" != "" ]; then \
|
||||
echo "Checked: Glauth is responding properly to ldapsearch query."; \
|
||||
else \
|
||||
echo "glauth check did not pass. Aborting."; \
|
||||
exit 1; \
|
||||
fi && \
|
||||
echo "==> Stopping glauth main container..." && \
|
||||
docker stop glauth-test && \
|
||||
while [ "$$(docker ps -q -f name=glauth-test)" != "" ]; do sleep 1; done; \
|
||||
docker rm glauth-test && \
|
||||
echo "==> Running glauth plugins container..." && \
|
||||
docker run -d --name glauth-test -p 3893:3893 $$REPO/glauth-plugins:latest && \
|
||||
sleep 5 && \
|
||||
if [ "$$(ldapsearch -LLL -H ldap://localhost:3893 -D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com -w mysecret -x -bdc=glauth,dc=com cn=hackers | grep posixAccount)" != "" ]; then \
|
||||
echo "Checked: Glauth is responding properly to ldapsearch query."; \
|
||||
else \
|
||||
echo "glauth check did not pass. Aborting."; \
|
||||
exit 1; \
|
||||
fi && \
|
||||
echo "==> Stopping glauth plugins container..." && \
|
||||
docker stop glauth-test && \
|
||||
while [ "$$(docker ps -q -f name=glauth-test)" != "" ]; do sleep 1; done; \
|
||||
docker rm glauth-test && \
|
||||
echo "==> Testing complete."
|
||||
|
||||
.PHONY: all run test fast binaries setup getdeps runtest updatetest format devrun linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64 verify pull-plugin pull-base-plugins forget-plugin forget-plugins releasemain releaseplugin release releaseplugins releasedockermain releaserdockerplugins releasedocker testdocker
|
||||
@@ -0,0 +1,35 @@
|
||||
#################
|
||||
# Build Step
|
||||
#################
|
||||
|
||||
FROM alpine:latest as build
|
||||
|
||||
RUN apk add --no-cache git make busybox-static dumb-init
|
||||
|
||||
#################
|
||||
# Run Step
|
||||
#################
|
||||
|
||||
FROM gcr.io/distroless/base-debian11 as run
|
||||
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Copy docker specific scripts from build container
|
||||
COPY assets/start-plugins.sh /app/docker/start-plugins.sh
|
||||
COPY assets/default-config-plugins.cfg /app/docker/default-config-plugins.cfg
|
||||
COPY assets/gl.db /app/docker/gl.db
|
||||
COPY assets/$TARGETPLATFORM/glauth /app/glauth
|
||||
COPY assets/$TARGETPLATFORM/*.so /app/
|
||||
|
||||
# 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
|
||||
|
||||
# Expose web and LDAP ports
|
||||
EXPOSE 389 636 5555
|
||||
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
||||
CMD ["/bin/sh", "/app/docker/start-plugins.sh"]
|
||||
@@ -0,0 +1,33 @@
|
||||
#################
|
||||
# Build Step
|
||||
#################
|
||||
|
||||
FROM alpine:latest as build
|
||||
|
||||
RUN apk add --no-cache git make busybox-static dumb-init
|
||||
|
||||
#################
|
||||
# Run Step
|
||||
#################
|
||||
|
||||
FROM gcr.io/distroless/base-debian11 as run
|
||||
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Copy docker specific scripts from build container
|
||||
COPY assets/start-standalone.sh /app/docker/start-standalone.sh
|
||||
COPY assets/default-config-standalone.cfg /app/docker/default-config-standalone.cfg
|
||||
COPY assets/$TARGETPLATFORM/glauth /app/glauth
|
||||
|
||||
# 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
|
||||
|
||||
# Expose web and LDAP ports
|
||||
EXPOSE 389 636 5555
|
||||
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
||||
CMD ["/bin/sh", "/app/docker/start-standalone.sh"]
|
||||
@@ -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"
|
||||
|
||||
@@ -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,32 +92,52 @@ 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 ]
|
||||
|
||||
#################
|
||||
Binary file not shown.
Executable
+32
@@ -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
|
||||
|
||||
|
||||
+781
@@ -0,0 +1,781 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/arl/statsviz"
|
||||
docopt "github.com/docopt/docopt-go"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/glauth/v2/pkg/frontend"
|
||||
"github.com/glauth/glauth/v2/pkg/logging"
|
||||
"github.com/glauth/glauth/v2/pkg/server"
|
||||
"github.com/glauth/glauth/v2/pkg/stats"
|
||||
"github.com/hydronica/toml"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/rs/zerolog"
|
||||
"gopkg.in/amz.v3/aws"
|
||||
"gopkg.in/amz.v3/s3"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Set with buildtime vars
|
||||
var LastGitTag string
|
||||
var BuildTime string
|
||||
var GitCommit string
|
||||
var GitClean string
|
||||
var GitBranch string
|
||||
var GitTagIsCommit string
|
||||
|
||||
const programName = "glauth"
|
||||
|
||||
var usage = `glauth: securely expose your LDAP for external auth
|
||||
|
||||
Usage:
|
||||
glauth [options] -c <file|s3 url>
|
||||
glauth -h --help
|
||||
glauth --version
|
||||
|
||||
Options:
|
||||
-c, --config <file> Config file.
|
||||
-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.
|
||||
--check-config Check configuration file and exit.
|
||||
-h, --help Show this screen.
|
||||
--version Show version.
|
||||
`
|
||||
|
||||
var (
|
||||
log zerolog.Logger
|
||||
args map[string]interface{}
|
||||
yubiAuth *yubigo.YubiAuth
|
||||
|
||||
activeConfig = &config.Config{}
|
||||
)
|
||||
|
||||
// Reads builtime vars and returns a full string containing info about
|
||||
// the currently running version of the software. Primarily used by the
|
||||
// --version flag at runtime.
|
||||
func getVersionString() string {
|
||||
|
||||
var versionstr string
|
||||
|
||||
versionstr = "GLauth"
|
||||
|
||||
// Notate the git context of the build
|
||||
switch {
|
||||
// If a release, use the tag
|
||||
case GitClean == "1" && GitTagIsCommit == "1":
|
||||
versionstr += " " + LastGitTag + "\n\n"
|
||||
|
||||
// If this branch had a tag before, mention the branch and the tag to give a rough idea of the base version
|
||||
case len(GitBranch) > 1 && len(LastGitTag) > 1:
|
||||
versionstr += "\nNon-release build from branch " + GitBranch + ", based on tag " + LastGitTag + "\n\n"
|
||||
|
||||
// If no previous tag specified, just mention the branch
|
||||
case len(GitBranch) > 1:
|
||||
versionstr += "\nNon-release build from branch " + GitBranch + "\n\n"
|
||||
|
||||
// Fallback message, if all else fails
|
||||
default:
|
||||
versionstr += "\nNon-release build\n\n"
|
||||
}
|
||||
|
||||
// Include build time
|
||||
if len(BuildTime) > 1 {
|
||||
versionstr += "Build time: " + BuildTime + "\n"
|
||||
}
|
||||
|
||||
// Add commit hash
|
||||
if GitClean == "1" && len(GitCommit) > 1 {
|
||||
versionstr += "Commit: " + GitCommit + "\n"
|
||||
}
|
||||
|
||||
return versionstr
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := parseArgs(); err != nil {
|
||||
fmt.Println("Could not parse command-line arguments")
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
checkConfig := false
|
||||
if cc, ok := args["--check-config"]; ok {
|
||||
if cc == true {
|
||||
checkConfig = true
|
||||
}
|
||||
}
|
||||
if err := doConfig(checkConfig); err != nil {
|
||||
fmt.Println("Configuration file error")
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if checkConfig {
|
||||
fmt.Println("Config file seems ok (but I am not checking much at this time)")
|
||||
return
|
||||
}
|
||||
log.Info().Msg("AP start")
|
||||
|
||||
startService()
|
||||
}
|
||||
|
||||
func startService() {
|
||||
// stats
|
||||
stats.General.Set("version", stats.Stringer(LastGitTag))
|
||||
|
||||
// web API
|
||||
if activeConfig.API.Enabled {
|
||||
log.Info().Msg("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(
|
||||
server.Logger(log),
|
||||
server.Config(activeConfig),
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not create server")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
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(err).Msg("could not start LDAP server")
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
go func() {
|
||||
if err := s.ListenAndServe(); err != nil {
|
||||
log.Error().Err(err).Msg("could not start LDAP server")
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
if activeConfig.LDAPS.Enabled {
|
||||
// Always block here
|
||||
if err := s.ListenAndServeTLS(); err != nil {
|
||||
log.Error().Err(err).Msg("could not start LDAPS server")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().Msg("AP exit")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func startConfigWatcher() {
|
||||
configFileLocation := getConfigLocation()
|
||||
if !activeConfig.WatchConfig || strings.HasPrefix(configFileLocation, "s3://") {
|
||||
return
|
||||
}
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not start config-watcher")
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
go func() {
|
||||
isChanged, isRemoved := false, false
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
log.Info().Str("e", event.Op.String()).Msg("watcher got event")
|
||||
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:
|
||||
log.Error().Err(err).Msg("watcher error")
|
||||
case <-ticker.C:
|
||||
// wakeup, try finding removed config
|
||||
}
|
||||
if _, err := os.Stat(configFileLocation); !os.IsNotExist(err) && (isRemoved || isChanged) {
|
||||
if isRemoved {
|
||||
log.Info().Str("file", configFileLocation).Msg("rewatching config")
|
||||
watcher.Add(configFileLocation) // overwrite
|
||||
isChanged, isRemoved = true, false
|
||||
}
|
||||
if isChanged {
|
||||
if err := doConfig(false); err != nil {
|
||||
log.Info().Err(err).Msg("Could not reload config. Holding on to old config")
|
||||
} else {
|
||||
log.Info().Msg("Config was reloaded")
|
||||
}
|
||||
isChanged = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
watcher.Add(configFileLocation)
|
||||
}
|
||||
|
||||
func parseArgs() error {
|
||||
var err error
|
||||
|
||||
if args, err = docopt.Parse(usage, nil, true, getVersionString(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getConfigLocation() string {
|
||||
return args["--config"].(string)
|
||||
}
|
||||
|
||||
func parseConfigFile(configFileLocation string) (*config.Config, error) {
|
||||
cfg := config.Config{}
|
||||
// setup defaults
|
||||
cfg.LDAP.Enabled = false
|
||||
cfg.LDAPS.Enabled = true
|
||||
|
||||
// parse the config file
|
||||
if strings.HasPrefix(configFileLocation, "s3://") {
|
||||
if _, present := aws.Regions[args["-r"].(string)]; present == false {
|
||||
return &cfg, fmt.Errorf("invalid AWS region: %s", args["-r"])
|
||||
}
|
||||
region := aws.Regions[args["-r"].(string)]
|
||||
auth, err := aws.EnvAuth()
|
||||
if err != nil {
|
||||
if args["-K"] == nil || args["-S"] == nil {
|
||||
return &cfg, fmt.Errorf("AWS credentials not found: must use -K and -S flags, or set these env vars:\n\texport AWS_ACCESS_KEY_ID=\"AAA...\"\n\texport AWS_SECRET_ACCESS_KEY=\"BBBB...\"\n")
|
||||
}
|
||||
auth = aws.Auth{
|
||||
AccessKey: args["-K"].(string),
|
||||
SecretKey: args["-S"].(string),
|
||||
}
|
||||
}
|
||||
// parse S3 url
|
||||
s3url := strings.TrimPrefix(configFileLocation, "s3://")
|
||||
parts := strings.SplitN(s3url, "/", 2)
|
||||
if len(parts) != 2 {
|
||||
return &cfg, fmt.Errorf("invalid S3 URL: %s", s3url)
|
||||
}
|
||||
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
|
||||
}
|
||||
if _, err := toml.Decode(string(tomlData), &cfg); err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
} else { // local config file
|
||||
fInfo, err := os.Stat(configFileLocation)
|
||||
if err != nil {
|
||||
return &cfg, fmt.Errorf("non-existent config path: %s", configFileLocation)
|
||||
}
|
||||
|
||||
var md toml.MetaData
|
||||
|
||||
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 md, err = toml.Decode(destbuf.String(), &merged); err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
cfg = merged
|
||||
} else {
|
||||
md, err = toml.DecodeFile(configFileLocation, &cfg)
|
||||
if err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
}
|
||||
|
||||
switch users := md.Mappings()["users"].(type) {
|
||||
case []map[string]interface{}:
|
||||
for _, mduser := range users {
|
||||
if mduser["customattributes"] != nil {
|
||||
for idx, cfguser := range cfg.Users {
|
||||
if cfguser.Name == mduser["name"].(string) {
|
||||
switch attributes := mduser["customattributes"].(type) {
|
||||
case []map[string]interface{}:
|
||||
cfg.Users[idx].CustomAttrs = attributes[0]
|
||||
case map[string]interface{}:
|
||||
cfg.Users[idx].CustomAttrs = attributes
|
||||
default:
|
||||
log.Info().Interface("attributes", attributes).Msg("Unknown attribute structure in config file")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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 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.Info().Str("type", reflect.TypeOf(element2).String()).Msg("Unknown element type found in configuration file. Ignoring.")
|
||||
}
|
||||
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.Info().Msg("Unexpected interface type for an assignment. Ignoring.")
|
||||
returnElement = element1
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
log.Info().Str("type", reflect.TypeOf(element1).String()).Msg("Unknown element type found in configuration file. Ignoring.")
|
||||
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
|
||||
}
|
||||
//
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
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])")
|
||||
}
|
||||
|
||||
if len(cfg.Frontend.Listen) > 0 {
|
||||
// We're going with old format - parse it into new
|
||||
log.Info().Msg("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
|
||||
|
||||
if cfg.Frontend.TLS {
|
||||
cfg.LDAPS.Listen = cfg.Frontend.Listen
|
||||
} else {
|
||||
cfg.LDAP.Listen = cfg.Frontend.Listen
|
||||
}
|
||||
|
||||
if len(cfg.Frontend.Cert) > 0 {
|
||||
cfg.LDAPS.Cert = cfg.Frontend.Cert
|
||||
}
|
||||
if len(cfg.Frontend.Key) > 0 {
|
||||
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")
|
||||
}
|
||||
|
||||
if cfg.LDAPS.Enabled {
|
||||
// LDAPS enabled - verify requirements (cert, key, listen)
|
||||
if len(cfg.LDAPS.Cert) == 0 || len(cfg.LDAPS.Key) == 0 {
|
||||
return &cfg, fmt.Errorf("LDAPS was enabled but no certificate or key were specified: please disable LDAPS or use the 'cert' and 'key' options")
|
||||
}
|
||||
|
||||
if len(cfg.LDAPS.Listen) == 0 {
|
||||
return &cfg, fmt.Errorf("no LDAPS bind address was specified: please disable LDAPS or use the 'listen' option")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.LDAP.Enabled {
|
||||
// LDAP enabled - verify listen
|
||||
if len(cfg.LDAP.Listen) == 0 {
|
||||
return &cfg, fmt.Errorf("no LDAP bind address was specified: please disable LDAP or use the 'listen' option")
|
||||
}
|
||||
}
|
||||
|
||||
//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.Info().Msg(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.Info().Msg(fmt.Sprintf("Group '%s': 'unixid' is deprecated - please move to 'gidnumber' as per documentation", group.Name))
|
||||
}
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// doConfig reads the cli flags and config file
|
||||
func doConfig(checkConfig bool) error {
|
||||
// Parse config-file into config{} struct
|
||||
cfg, err := parseConfigFile(getConfigLocation())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Before greenlighting new config entirely, lets make sure the yubiauth works - in case they changed
|
||||
if activeConfig.YubikeyClientID != cfg.YubikeyClientID || activeConfig.YubikeySecret != cfg.YubikeySecret {
|
||||
if len(cfg.YubikeyClientID) > 0 && len(cfg.YubikeySecret) > 0 {
|
||||
_yubiAuth, err := yubigo.NewYubiAuth(cfg.YubikeyClientID, cfg.YubikeySecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// No errors, override
|
||||
yubiAuth = _yubiAuth
|
||||
}
|
||||
}
|
||||
|
||||
// All config is validated and alright, copy to activeConfig
|
||||
if err := copier.Copy(activeConfig, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle logging settings for new config
|
||||
// - we do this last to make sure we only respect a fully validated config
|
||||
log = logging.InitLogging(activeConfig.Debug, activeConfig.Syslog, activeConfig.StructuredLog)
|
||||
|
||||
if !checkConfig {
|
||||
if activeConfig.Debug {
|
||||
log.Info().Msg("Debugging enabled")
|
||||
}
|
||||
if activeConfig.Syslog {
|
||||
log.Info().Msg("Syslog enabled")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
type testEnv struct {
|
||||
checkanonymousrootDSE bool
|
||||
checkTOTP bool
|
||||
checkbindUPN bool
|
||||
svcdn string
|
||||
svcdnnogroup string
|
||||
otpdn string
|
||||
expectedinfo string
|
||||
expectedaccount string
|
||||
scopedaccount string
|
||||
expectedfirstaccount string
|
||||
expectedgroup string
|
||||
checkemployeetype string
|
||||
}
|
||||
|
||||
func TestIntegerStuff(t *testing.T) {
|
||||
|
||||
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,ou=users,dc=glauth,dc=com",
|
||||
scopedaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
|
||||
expectedfirstaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
|
||||
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
|
||||
checkemployeetype: "cn=hackers",
|
||||
})
|
||||
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,ou=users,dc=glauth,dc=com",
|
||||
scopedaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
|
||||
expectedfirstaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
|
||||
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
|
||||
checkemployeetype: "",
|
||||
})
|
||||
stopSvc(svc)
|
||||
})
|
||||
}
|
||||
|
||||
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",
|
||||
scopedaccount: "dn: cn=hackers,cn=superheros,dc=glauth,dc=com",
|
||||
expectedfirstaccount: "dn: cn=johndoe,cn=superheros,ou=users,dc=glauth,dc=com",
|
||||
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
|
||||
checkemployeetype: "",
|
||||
})
|
||||
stopSvc(svc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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=ou=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=ou=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.scopedaccount)
|
||||
})
|
||||
})
|
||||
|
||||
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")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if env.checkemployeetype != "" {
|
||||
Convey("When searching for the 'hacker' user", func() {
|
||||
out := doRunGetSecond(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", env.checkemployeetype, "employeetype")
|
||||
Convey("Employee type should be 'Intern'", func() {
|
||||
So(out, ShouldEqual, "employeetype: Intern")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// -----=============================================================================----
|
||||
|
||||
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))
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
module github.com/glauth/glauth/v2
|
||||
|
||||
go 1.19
|
||||
|
||||
// Do not mistake /vendored for /vendor!
|
||||
replace github.com/hydronica/toml => ./vendored/toml
|
||||
|
||||
require (
|
||||
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e
|
||||
github.com/arl/statsviz v0.4.0
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/hydronica/toml v0.4.2
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/rs/zerolog v1.28.0
|
||||
github.com/smartystreets/goconvey v1.6.4
|
||||
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc
|
||||
golang.org/x/crypto v0.1.0
|
||||
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
@@ -0,0 +1,534 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
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/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/arl/statsviz v0.4.0 h1:pv8DWfti0TC8M2Baold2xGnCnir9jhqv1BO6UNKQmqY=
|
||||
github.com/arl/statsviz v0.4.0/go.mod h1:+5inUy/dxy11x/KSmicG3ZrEEy0Yr81AFm3dn4QC04M=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
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/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
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/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
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-20210720162743-7f8d1e44eeba h1:DO8NFYdcRv1dnyAINJIBm6Bw2XibtLvQniNFGzf2W8E=
|
||||
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba/go.mod h1:4S0XndRL8HNOaQBfdViJ2F/GPCgL524xlXRuXFH12/U=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
|
||||
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
|
||||
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
|
||||
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
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/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
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=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/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-20190213061140-3a22650c66bd/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
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 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
@@ -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.
+5
File diff suppressed because one or more lines are too long
Vendored
+5
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
package assets
|
||||
|
||||
import "embed"
|
||||
|
||||
// Content is the embedded assets.
|
||||
//
|
||||
//go:embed *.tgz js/* css/* *.html
|
||||
var Content embed.FS
|
||||
Binary file not shown.
@@ -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>
|
||||
Vendored
+6
File diff suppressed because one or more lines are too long
@@ -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);
|
||||
Vendored
+28
File diff suppressed because one or more lines are too long
Vendored
+4
File diff suppressed because one or more lines are too long
+10
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,122 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
// config file
|
||||
type Backend struct {
|
||||
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
|
||||
GroupWithSearchCapability string // For PamLinux backend 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
|
||||
Listen string
|
||||
Cert string
|
||||
Key string
|
||||
TLS bool
|
||||
}
|
||||
type LDAP struct {
|
||||
Enabled bool
|
||||
Listen string
|
||||
}
|
||||
type LDAPS struct {
|
||||
Enabled bool
|
||||
Listen string
|
||||
Cert string
|
||||
Key string
|
||||
}
|
||||
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
|
||||
LegacyVersion int
|
||||
}
|
||||
type Capability struct {
|
||||
Action string
|
||||
Object string
|
||||
}
|
||||
|
||||
// UserAuthenticator authenticates a user via custom auth from a backend
|
||||
type UserAuthenticator func(user *User, pw string) error
|
||||
type User struct {
|
||||
Name string
|
||||
OtherGroups []int
|
||||
PassSHA256 string
|
||||
PassBcrypt string
|
||||
PassAppSHA256 []string
|
||||
PassAppBcrypt []string
|
||||
PassAppCustom UserAuthenticator
|
||||
PrimaryGroup int
|
||||
Capabilities []Capability
|
||||
SSHKeys []string
|
||||
OTPSecret string
|
||||
Yubikey string
|
||||
Disabled bool
|
||||
UnixID int // TODO: remove after deprecating UnixID on User and Group
|
||||
UIDNumber int
|
||||
Mail string
|
||||
LoginShell string
|
||||
GivenName string
|
||||
SN string
|
||||
Homedir string
|
||||
CustomAttrs map[string]interface{}
|
||||
}
|
||||
type Group struct {
|
||||
Name string
|
||||
UnixID int // TODO: remove after deprecating UnixID on User and Group
|
||||
GIDNumber int
|
||||
IncludeGroups []int
|
||||
}
|
||||
type Config struct {
|
||||
API API
|
||||
Backend Backend // Deprecated
|
||||
Backends []Backend
|
||||
Helper Helper
|
||||
Behaviors Behaviors
|
||||
Debug bool
|
||||
Syslog bool
|
||||
StructuredLog bool
|
||||
WatchConfig bool
|
||||
YubikeyClientID string
|
||||
YubikeySecret string
|
||||
Frontend Frontend
|
||||
LDAP LDAP
|
||||
LDAPS LDAPS
|
||||
Groups []Group
|
||||
Users []User
|
||||
ConfigFile string
|
||||
AwsAccessKeyId string
|
||||
AwsSecretAccessKey string
|
||||
AwsRegion string
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger zerolog.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 zerolog.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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/rs/zerolog"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/glauth/glauth/v2/pkg/assets"
|
||||
)
|
||||
|
||||
// RunAPI provides a basic REST API
|
||||
func RunAPI(opts ...Option) {
|
||||
options := newOptions(opts...)
|
||||
log := options.Logger
|
||||
cfg := options.Config
|
||||
|
||||
register(http.DefaultServeMux, log, cfg.TLS, cfg.Listen)
|
||||
if cfg.TLS {
|
||||
log.Info().Str("address", cfg.Listen).Msg("Starting HTTPS server")
|
||||
err := http.ListenAndServeTLS(cfg.Listen, cfg.Cert, cfg.Key, nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error starting HTTPS server")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.Info().Str("address", cfg.Listen).Msg("Starting HTTP server")
|
||||
err := http.ListenAndServe(cfg.Listen, nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error starting HTTP server")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func register(mux *http.ServeMux, log zerolog.Logger, tls bool, listen string) {
|
||||
fs := http.FileServer(http.FS(assets.Content))
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Info().Str("path", r.URL.Path).Msg("Web")
|
||||
if r.URL.Path != "/" {
|
||||
log.Info().Msg("Web 404")
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
fs.ServeHTTP(w, r)
|
||||
})
|
||||
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
|
||||
var url string
|
||||
if tls {
|
||||
url = fmt.Sprintf("https://%s/debug/vars", listen)
|
||||
} else {
|
||||
url = fmt.Sprintf("http://%s/debug/vars", listen)
|
||||
}
|
||||
c := &collector{
|
||||
url: url,
|
||||
names: map[string]string{
|
||||
"proxy": "glauth_proxy",
|
||||
"proxy_frontend": "glauth_proxy_frontend",
|
||||
"proxy_backend": "glauth_proxy_backend",
|
||||
},
|
||||
helps: map[string]string{
|
||||
"proxy": "General Metrics",
|
||||
"proxy_frontend": "Frontend Metrics",
|
||||
"proxy_backend": "Backend Metrics",
|
||||
},
|
||||
labelName: map[string]string{
|
||||
"proxy": "metric",
|
||||
"proxy_frontend": "metric",
|
||||
"proxy_backend": "metric",
|
||||
},
|
||||
}
|
||||
prometheus.MustRegister(c)
|
||||
mux.Handle("/metrics", promhttp.Handler())
|
||||
}
|
||||
|
||||
// The code below liberally borrows from https://github.com/albertito/prometheus-expvar-exporter
|
||||
// and is adapted (just a little) for our purpose.
|
||||
|
||||
type collector struct {
|
||||
url string
|
||||
names map[string]string
|
||||
helps map[string]string
|
||||
labelName map[string]string
|
||||
}
|
||||
|
||||
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||
resp, err := http.Get(c.url)
|
||||
if err != nil {
|
||||
log.Printf("Error scraping %q: %v", c.url, err)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("Error reading body of %q: %v", c.url, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Replace "\xNN" with "?" because the default parser doesn't handle them
|
||||
// well.
|
||||
re := regexp.MustCompile(`\\x..`)
|
||||
body = re.ReplaceAllFunc(body, func(s []byte) []byte {
|
||||
return []byte("?")
|
||||
})
|
||||
|
||||
var vs map[string]interface{}
|
||||
err = json.Unmarshal(body, &vs)
|
||||
if err != nil {
|
||||
log.Printf("Error unmarshalling json from %q: %v", c.url, err)
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range vs {
|
||||
name := strings.ReplaceAll(k, "/", "_")
|
||||
if n, ok := c.names[k]; ok {
|
||||
name = n
|
||||
}
|
||||
|
||||
help := fmt.Sprintf("expvar %q", k)
|
||||
if h, ok := c.helps[k]; ok {
|
||||
help = h
|
||||
}
|
||||
|
||||
lnames := []string{}
|
||||
if ln, ok := c.labelName[k]; ok {
|
||||
lnames = append(lnames, ln)
|
||||
}
|
||||
|
||||
desc := prometheus.NewDesc(name, help, lnames, nil)
|
||||
|
||||
switch v := v.(type) {
|
||||
case float64:
|
||||
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue, v)
|
||||
case bool:
|
||||
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue,
|
||||
valToFloat(v))
|
||||
case map[string]interface{}:
|
||||
// We only support explicitly written label names.
|
||||
if len(lnames) != 1 {
|
||||
continue
|
||||
}
|
||||
for lk, lv := range v {
|
||||
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue,
|
||||
valToFloat(lv), lk)
|
||||
}
|
||||
case string:
|
||||
// Not supported by Prometheus.
|
||||
continue
|
||||
case []interface{}:
|
||||
// Not supported by Prometheus.
|
||||
continue
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func valToFloat(v interface{}) float64 {
|
||||
switch v := v.(type) {
|
||||
case float64:
|
||||
return v
|
||||
case bool:
|
||||
if v {
|
||||
return 1.0
|
||||
}
|
||||
return 0.0
|
||||
case string:
|
||||
return 0.0
|
||||
}
|
||||
log.Printf("unexpected value type: %#v", v)
|
||||
return 0.0
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"github.com/rs/zerolog"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/glauth/glauth/v2/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()
|
||||
log := zerolog.New(os.Stdout).Level(zerolog.InfoLevel)
|
||||
register(mux, log, true, "0.0.0.0:5555")
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"net"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/glauth/v2/pkg/stats"
|
||||
"github.com/nmcclain/ldap"
|
||||
)
|
||||
|
||||
type configHandler struct {
|
||||
backend config.Backend
|
||||
log *zerolog.Logger
|
||||
cfg *config.Config
|
||||
yubikeyAuth *yubigo.YubiAuth
|
||||
ldohelper LDAPOpsHelper
|
||||
attmatcher *regexp.Regexp
|
||||
}
|
||||
|
||||
// NewConfigHandler creates a new config backed handler
|
||||
func NewConfigHandler(opts ...Option) Handler {
|
||||
options := newOptions(opts...)
|
||||
|
||||
handler := configHandler{
|
||||
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() *zerolog.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) {
|
||||
return h.ldohelper.Bind(h, bindDN, bindSimplePw, conn)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 searchByUPN {
|
||||
if strings.EqualFold(u.Mail, userName) {
|
||||
found = true
|
||||
user = u
|
||||
}
|
||||
} else {
|
||||
if strings.EqualFold(u.Name, userName) {
|
||||
found = true
|
||||
user = u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
for _, g := range h.cfg.Groups {
|
||||
if strings.EqualFold(g.Name, groupName) {
|
||||
found = true
|
||||
group = g
|
||||
}
|
||||
}
|
||||
return found, group, nil
|
||||
}
|
||||
|
||||
func (h configHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.Entry, err error) {
|
||||
entries := []*ldap.Entry{}
|
||||
|
||||
for _, u := range h.cfg.Users {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: h.backend.NameFormat, 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.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})
|
||||
}
|
||||
|
||||
if len(u.CustomAttrs) > 0 {
|
||||
for key, attr := range u.CustomAttrs {
|
||||
switch typedattr := attr.(type) {
|
||||
case []interface{}:
|
||||
var values []string
|
||||
for _, v := range typedattr {
|
||||
switch typedvalue := v.(type) {
|
||||
case string:
|
||||
values = append(values, MaybeDecode(typedvalue))
|
||||
default:
|
||||
values = append(values, MaybeDecode(fmt.Sprintf("%v", typedvalue)))
|
||||
}
|
||||
}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: key, Values: values})
|
||||
default:
|
||||
h.log.Warn().Str("key", key).Interface("value", attr).Msg("Unable to map custom attribute")
|
||||
}
|
||||
}
|
||||
}
|
||||
var dn string
|
||||
if hierarchy == "" {
|
||||
dn = fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
|
||||
} else {
|
||||
dn = fmt.Sprintf("%s=%s,%s=%s,%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), hierarchy, h.backend.BaseDN)
|
||||
}
|
||||
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (h configHandler) FindPosixGroups(hierarchy string) (entrylist []*ldap.Entry, err error) {
|
||||
asGroupOfUniqueNames := hierarchy == "ou=groups"
|
||||
|
||||
entries := []*ldap.Entry{}
|
||||
|
||||
for _, g := range h.cfg.Groups {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: h.backend.GroupFormat, 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: "uniqueMember", Values: h.getGroupMemberDNs(g.GIDNumber)})
|
||||
if asGroupOfUniqueNames {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"groupOfUniqueNames", "top"}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: h.getGroupMemberIDs(g.GIDNumber)})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixGroup", "top"}})
|
||||
}
|
||||
dn := fmt.Sprintf("%s=%s,%s,%s", h.backend.GroupFormat, g.Name, hierarchy, 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) getGroupMemberDNs(gid int) []string {
|
||||
var insertOuUsers string
|
||||
if h.cfg.Behaviors.LegacyVersion > 0 && h.cfg.Behaviors.LegacyVersion <= 20100 {
|
||||
insertOuUsers = ""
|
||||
} else {
|
||||
insertOuUsers = ",ou=users"
|
||||
}
|
||||
members := make(map[string]bool)
|
||||
for _, u := range h.cfg.Users {
|
||||
if u.PrimaryGroup == gid {
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
|
||||
members[dn] = true
|
||||
} else {
|
||||
for _, othergid := range u.OtherGroups {
|
||||
if othergid == gid {
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
|
||||
members[dn] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, g := range h.cfg.Groups {
|
||||
if gid == g.GIDNumber {
|
||||
for _, includegroupid := range g.IncludeGroups {
|
||||
if includegroupid != gid {
|
||||
includegroupmembers := h.getGroupMemberDNs(includegroupid)
|
||||
|
||||
for _, includegroupmember := range includegroupmembers {
|
||||
members[includegroupmember] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m := []string{}
|
||||
for k := range members {
|
||||
m = append(m, k)
|
||||
}
|
||||
|
||||
sort.Strings(m)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (h configHandler) getGroupMemberIDs(gid int) []string {
|
||||
members := make(map[string]bool)
|
||||
for _, u := range h.cfg.Users {
|
||||
if u.PrimaryGroup == gid {
|
||||
members[u.Name] = true
|
||||
} else {
|
||||
for _, othergid := range u.OtherGroups {
|
||||
if othergid == gid {
|
||||
members[u.Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, g := range h.cfg.Groups {
|
||||
if gid == g.GIDNumber {
|
||||
for _, includegroupid := range g.IncludeGroups {
|
||||
if includegroupid == gid {
|
||||
h.log.Warn().Int("groupid", includegroupid).Msg("Ignoring myself as included group")
|
||||
} 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
|
||||
}
|
||||
|
||||
// Converts an array of GUIDs into an array of DNs
|
||||
func (h configHandler) getGroupDNs(gids []int) []string {
|
||||
groups := make(map[string]bool)
|
||||
for _, gid := range gids {
|
||||
for _, g := range h.cfg.Groups {
|
||||
if g.GIDNumber == gid {
|
||||
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormat, 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
|
||||
}
|
||||
|
||||
func (h configHandler) getGroupName(gid int) string {
|
||||
for _, g := range h.cfg.Groups {
|
||||
if g.GIDNumber == gid {
|
||||
return g.Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/nmcclain/ldap"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MaybeDecode(value string) string {
|
||||
if strings.HasPrefix(value, "base64: ") {
|
||||
udec, err := base64.StdEncoding.DecodeString(value[8:])
|
||||
if err != nil {
|
||||
value = "* invalid value *"
|
||||
} else {
|
||||
value = string(udec)
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/glauth/v2/pkg/stats"
|
||||
"github.com/nmcclain/ldap"
|
||||
"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 *zerolog.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
|
||||
var ldaplock sync.Mutex
|
||||
|
||||
type ldapSession struct {
|
||||
id string
|
||||
c net.Conn
|
||||
ldap *ldap.Conn
|
||||
}
|
||||
type ldapBackendStatus int
|
||||
|
||||
const (
|
||||
Down ldapBackendStatus = iota
|
||||
Up
|
||||
)
|
||||
|
||||
type ldapBackend struct {
|
||||
Scheme string
|
||||
Hostname string
|
||||
Port int
|
||||
Status ldapBackendStatus
|
||||
Ping time.Duration
|
||||
}
|
||||
|
||||
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: options.Logger,
|
||||
helper: options.Helper,
|
||||
lock: &ldaplock,
|
||||
attm: ldapattributematcher,
|
||||
}
|
||||
// parse LDAP URLs
|
||||
for _, ldapurl := range handler.backend.Servers {
|
||||
l, err := parseURL(ldapurl)
|
||||
if err != nil {
|
||||
handler.log.Error().Err(err).Msg("could not parse url")
|
||||
os.Exit(1)
|
||||
}
|
||||
handler.servers = append(handler.servers, l)
|
||||
}
|
||||
|
||||
// test server connectivity before listening, then keep it updated
|
||||
handler.monitorServers()
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
||||
h.log.Debug().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind request")
|
||||
|
||||
// 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.Debug().Msg(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().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Err(err).Msg("could not get session")
|
||||
return ldap.LDAPResultOperationsError, err
|
||||
}
|
||||
if err := s.ldap.Bind(bindDN, bindSimplePw); err != nil {
|
||||
stats.Frontend.Add("bind_errors", 1)
|
||||
h.log.Debug().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("invalid creds")
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
stats.Frontend.Add("bind_successes", 1)
|
||||
h.log.Debug().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("bind success")
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
||||
wantAttributes := true
|
||||
wantTypesOnly := false
|
||||
|
||||
h.log.Debug().Str("binddn", boundDN).Str("src", conn.RemoteAddr().String()).Str("filter", searchReq.Filter).Msg("Search request")
|
||||
|
||||
// "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 {
|
||||
stats.Frontend.Add("search_ldapSession_errors", 1)
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, nil
|
||||
}
|
||||
search := ldap.NewSearchRequest(
|
||||
searchReq.BaseDN,
|
||||
searchReq.Scope,
|
||||
searchReq.DerefAliases,
|
||||
searchReq.SizeLimit,
|
||||
searchReq.TimeLimit,
|
||||
searchReq.TypesOnly,
|
||||
searchReq.Filter,
|
||||
searchReq.Attributes,
|
||||
searchReq.Controls,
|
||||
)
|
||||
|
||||
h.log.Debug().Interface("request", search).Msg("Search request to backend")
|
||||
sr, err := s.ldap.Search(search)
|
||||
h.log.Debug().Interface("result", sr).Msg("Backend Search result")
|
||||
|
||||
if !wantAttributes {
|
||||
h.log.Debug().Str("type", "No attributes").Msg("AP: Search Info")
|
||||
for _, entry := range sr.Entries {
|
||||
entry.Attributes = entry.Attributes[:0]
|
||||
}
|
||||
}
|
||||
|
||||
if wantTypesOnly {
|
||||
h.log.Debug().Str("type", "Types only").Msg("AP: Search Info")
|
||||
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.Debug().Str("type", "Root search detected").Msg("AP: Search Info")
|
||||
}
|
||||
|
||||
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().Interface("result", ssr).Msg("Frontend Search result")
|
||||
if err != nil {
|
||||
e := err.(*ldap.Error)
|
||||
h.log.Debug().Err(err).Msg("search 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().Str("filter", search.Filter).Int("numentries", len(ssr.Entries)).Msg("AP: Search OK")
|
||||
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()
|
||||
defer h.lock.Unlock()
|
||||
delete(h.sessions, connID(conn))
|
||||
stats.Frontend.Add("closes", 1)
|
||||
stats.Backend.Add("closes", 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// monitorServers tests server connectivity before listening, then keeps it updated
|
||||
func (h *ldapHandler) monitorServers() {
|
||||
err := h.ping()
|
||||
if err != nil {
|
||||
h.log.Error().Err(err).Msg("could not ping server")
|
||||
os.Exit(1)
|
||||
// TODO return error
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-h.doPing:
|
||||
h.log.Warn().Msg("doPing requested due to server failure")
|
||||
err = h.ping()
|
||||
if err != nil {
|
||||
h.log.Error().Err(err).Msg("could not ping server")
|
||||
os.Exit(1)
|
||||
// TODO return error
|
||||
}
|
||||
case <-time.NewTimer(60 * time.Second).C:
|
||||
h.log.Warn().Msg("doPing after timeout")
|
||||
err = h.ping()
|
||||
if err != nil {
|
||||
h.log.Error().Err(err).Msg("could not ping server")
|
||||
os.Exit(1)
|
||||
// TODO return error
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (h ldapHandler) getSession(conn net.Conn) (ldapSession, error) {
|
||||
id := connID(conn)
|
||||
h.lock.Lock()
|
||||
s, ok := h.sessions[id] // use server connection if it exists
|
||||
h.lock.Unlock()
|
||||
if !ok { // open a new server connection if not
|
||||
var l *ldap.Conn
|
||||
server, err := h.getBestServer() // pick the best server
|
||||
if err != nil {
|
||||
return ldapSession{}, err
|
||||
}
|
||||
dest := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
|
||||
if server.Scheme == "ldaps" {
|
||||
tlsCfg := &tls.Config{}
|
||||
if h.backend.Insecure {
|
||||
tlsCfg.InsecureSkipVerify = true
|
||||
}
|
||||
l, err = ldap.DialTLS("tcp", dest, tlsCfg)
|
||||
} else if server.Scheme == "ldap" {
|
||||
l, err = ldap.Dial("tcp", dest)
|
||||
}
|
||||
if err != nil {
|
||||
select {
|
||||
case h.doPing <- true: // non-blocking send
|
||||
default:
|
||||
}
|
||||
return ldapSession{}, err
|
||||
}
|
||||
s = ldapSession{id: id, c: conn, ldap: l}
|
||||
h.lock.Lock()
|
||||
h.sessions[s.id] = s
|
||||
h.lock.Unlock()
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (h ldapHandler) ping() error {
|
||||
healthy := false
|
||||
for k, s := range h.servers {
|
||||
var l *ldap.Conn
|
||||
var err error
|
||||
dest := fmt.Sprintf("%s:%d", s.Hostname, s.Port)
|
||||
start := time.Now()
|
||||
if h.servers[0].Scheme == "ldaps" {
|
||||
tlsCfg := &tls.Config{}
|
||||
if h.backend.Insecure {
|
||||
tlsCfg.InsecureSkipVerify = true
|
||||
}
|
||||
l, err = ldap.DialTLS("tcp", dest, tlsCfg)
|
||||
} else if h.servers[0].Scheme == "ldap" {
|
||||
l, err = ldap.Dial("tcp", dest)
|
||||
}
|
||||
elapsed := time.Since(start)
|
||||
h.lock.Lock()
|
||||
if err != nil || l == nil {
|
||||
h.log.Fatal().Str("hostname", s.Hostname).Int("port", s.Port).Err(err).Msg("server ping failed")
|
||||
h.servers[k].Ping = 0
|
||||
h.servers[k].Status = Down
|
||||
} else {
|
||||
healthy = true
|
||||
h.servers[k].Ping = elapsed
|
||||
h.servers[k].Status = Up
|
||||
l.Close() // prank caller
|
||||
}
|
||||
h.lock.Unlock()
|
||||
}
|
||||
h.log.Debug().Interface("servers", h.servers).Msg("Server health")
|
||||
b, err := json.Marshal(h.servers)
|
||||
if err != nil {
|
||||
h.log.Fatal().Err(err).Msg("error encoding tail data")
|
||||
}
|
||||
stats.Backend.Set("servers", stats.Stringer(string(b)))
|
||||
if healthy == false {
|
||||
return fmt.Errorf("no healthy servers")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h ldapHandler) getBestServer() (ldapBackend, error) {
|
||||
favorite := ldapBackend{}
|
||||
forever, err := time.ParseDuration("30m")
|
||||
if err != nil {
|
||||
return ldapBackend{}, err
|
||||
}
|
||||
bestping := forever
|
||||
for _, s := range h.servers {
|
||||
if s.Status == Up && s.Ping < bestping {
|
||||
favorite = s
|
||||
bestping = s.Ping
|
||||
}
|
||||
}
|
||||
if bestping == forever {
|
||||
return ldapBackend{}, fmt.Errorf("No healthy servers found")
|
||||
}
|
||||
h.log.Debug().Interface("favorite", favorite).Msg("Best server")
|
||||
return favorite, nil
|
||||
}
|
||||
|
||||
// helper functions
|
||||
func connID(conn net.Conn) string {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(conn.LocalAddr().String() + conn.RemoteAddr().String()))
|
||||
sha := fmt.Sprintf("% x", h.Sum(nil))
|
||||
return string(sha)
|
||||
}
|
||||
func parseURL(ldapurl string) (ldapBackend, error) {
|
||||
u, err := url.Parse(ldapurl)
|
||||
if err != nil {
|
||||
return ldapBackend{}, err
|
||||
}
|
||||
var port int
|
||||
if u.Scheme == "ldaps" {
|
||||
port = 636
|
||||
} else if u.Scheme == "ldap" {
|
||||
port = 389
|
||||
} else {
|
||||
return ldapBackend{}, fmt.Errorf("unknown LDAP scheme: %s", u.Scheme)
|
||||
}
|
||||
parts := strings.Split(u.Host, ":")
|
||||
hostname := parts[0]
|
||||
if len(parts) > 1 {
|
||||
port, err = strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return ldapBackend{}, err
|
||||
}
|
||||
}
|
||||
return ldapBackend{Scheme: u.Scheme, Hostname: hostname, Port: port}, nil
|
||||
}
|
||||
@@ -0,0 +1,775 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/glauth/v2/pkg/stats"
|
||||
"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() *zerolog.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(hierarchy string) (entrylist []*ldap.Entry, err error)
|
||||
FindPosixGroups(hierarchy string) (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().Info().Str("binddn", bindDN).Str("basedn", h.GetBackend().BaseDN).Str("src", conn.RemoteAddr().String()).Msg("Bind request")
|
||||
|
||||
stats.Frontend.Add("bind_reqs", 1)
|
||||
|
||||
// Special Case: bind as anonymous
|
||||
if bindDN == "" && bindSimplePw == "" {
|
||||
stats.Frontend.Add("bind_successes", 1)
|
||||
h.GetLog().Info().Str("src", conn.RemoteAddr().String()).Msg("Anonymous Bind success")
|
||||
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().Info().Str("incorrect stored hash", "(omitted)").Msg("invalid app credentials")
|
||||
} else {
|
||||
if bcrypt.CompareHashAndPassword(decoded, []byte(untouchedBindSimplePw)) == nil {
|
||||
stats.Frontend.Add("bind_successes", 1)
|
||||
h.GetLog().Info().Int("index", index).Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind success using app pw")
|
||||
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().Info().Int("index", index).Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Attempt to bind app pw failed")
|
||||
} else {
|
||||
stats.Frontend.Add("bind_successes", 1)
|
||||
h.GetLog().Info().Int("index", index).Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind success using app pw")
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if user.PassAppCustom != nil {
|
||||
err := user.PassAppCustom(user, untouchedBindSimplePw)
|
||||
if err != nil {
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Str("error", err.Error()).Msg("Attempt to bind app custom auth failed")
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
stats.Frontend.Add("bind_successes", 1)
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind success using app custom auth")
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
// Then ensure the OTP is valid before checking
|
||||
if !validotp {
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("invalid OTP token")
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
// Now, check the pasword hash
|
||||
if user.PassBcrypt != "" {
|
||||
decoded, err := hex.DecodeString(user.PassBcrypt)
|
||||
if err != nil {
|
||||
h.GetLog().Info().Str("incorrect stored hash", "(omitted)").Msg("invalid credentials")
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword(decoded, []byte(bindSimplePw)) != nil {
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("invalid credentials")
|
||||
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().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("invalid credentials")
|
||||
l.maybePutInTimeout(h, conn, true)
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
}
|
||||
|
||||
stats.Frontend.Add("bind_successes", 1)
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind success")
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO #1:
|
||||
* Is it possible to map, on-the-fly, ou= -> cn= to maintain backware compatibility? Could be a switch...
|
||||
* Or maybe sinmply configure in the .cfg file using the nameformat and groupformat settings?
|
||||
* In 3.0 we could change default from cn to ou
|
||||
* TODO #2: DONE
|
||||
* Returns values when scope==base or scope==sub on a group entry
|
||||
* TODO #3: DONE
|
||||
* Make sure that when scope==sub, we do not always return, but augment results instead
|
||||
* TODO #4: DONE
|
||||
* Handle groups as two distinct objectclasses like OLDAP does
|
||||
* Q: Does OLDAP return the groups twice when querying root+sub?
|
||||
* TODO #5:
|
||||
* Document roll out of schemas
|
||||
*/
|
||||
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)
|
||||
searchBaseDN := strings.ToLower(searchReq.BaseDN)
|
||||
|
||||
anonymous := len(bindDN) < 1
|
||||
|
||||
var boundUser *config.User
|
||||
var ldapcode ldap.LDAPResultCode
|
||||
|
||||
if !anonymous {
|
||||
if bindDN, boundUser, ldapcode = l.searchCheckBindDN(h, baseDN, bindDN, anonymous); ldapcode != ldap.LDAPResultSuccess {
|
||||
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Search Error: Potential bypass of BindDN %s", bindDN)
|
||||
}
|
||||
}
|
||||
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("basedn", baseDN).Str("searchbasedn", searchBaseDN).Str("src", conn.RemoteAddr().String()).Int("scope", searchReq.Scope).Str("filter", searchReq.Filter).Msg("Search request")
|
||||
stats.Frontend.Add("search_reqs", 1)
|
||||
|
||||
switch entries, ldapcode := l.searchMaybeRootDSEQuery(h, baseDN, searchBaseDN, searchReq, anonymous); ldapcode {
|
||||
case ldap.LDAPResultUnwillingToPerform:
|
||||
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Search Error: No BaseDN provided")
|
||||
case ldap.LDAPResultInsufficientAccessRights:
|
||||
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Root DSE Search Error: Anonymous BindDN not allowed %s", bindDN)
|
||||
case ldap.LDAPResultSuccess:
|
||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
||||
}
|
||||
|
||||
// Past this point, there is no reason to allow anonymous searches
|
||||
if anonymous {
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", bindDN)
|
||||
}
|
||||
|
||||
switch entries, ldapcode, attributename := l.searchMaybeSchemaQuery(h, baseDN, searchBaseDN, searchReq, anonymous); ldapcode {
|
||||
case ldap.LDAPResultOperationsError:
|
||||
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Schema Error: attribute %s cannot be read", attributename)
|
||||
case ldap.LDAPResultSuccess:
|
||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
||||
}
|
||||
|
||||
// Past this further point, we are looking at tree searches... not all standard searches yet, though
|
||||
|
||||
// But first, let's only allow legal searches
|
||||
if !strings.HasSuffix(bindDN, fmt.Sprintf(",%s", baseDN)) {
|
||||
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)
|
||||
}
|
||||
|
||||
switch entries, ldapcode := l.searchMaybeTopLevelNodes(h, baseDN, searchBaseDN, searchReq); ldapcode {
|
||||
case ldap.LDAPResultSuccess:
|
||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
||||
}
|
||||
|
||||
switch entries, ldapcode := l.searchMaybeTopLevelGroupsNode(h, baseDN, searchBaseDN, searchReq); ldapcode {
|
||||
case ldap.LDAPResultSuccess:
|
||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
||||
}
|
||||
|
||||
switch entries, ldapcode := l.searchMaybeTopLevelUsersNode(h, baseDN, searchBaseDN, searchReq); ldapcode {
|
||||
case ldap.LDAPResultSuccess:
|
||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
||||
}
|
||||
|
||||
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 entries, ldapcode := l.searchMaybePosixGroups(h, baseDN, searchBaseDN, searchReq, filterEntity); ldapcode {
|
||||
case ldap.LDAPResultSuccess:
|
||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
||||
}
|
||||
|
||||
switch entries, ldapcode := l.searchMaybePosixAccounts(h, baseDN, searchBaseDN, searchReq, filterEntity); ldapcode {
|
||||
case ldap.LDAPResultSuccess:
|
||||
stats.Frontend.Add("search_successes", 1)
|
||||
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Search OK")
|
||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
||||
}
|
||||
|
||||
// So, this should be an ERROR condition! Right..?
|
||||
entries := []*ldap.Entry{}
|
||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
|
||||
}
|
||||
|
||||
// Returns: LDAPResultSuccess or any ldap code returned by findUser
|
||||
func (l LDAPOpsHelper) searchCheckBindDN(h LDAPOpsHandler, baseDN string, bindDN string, anonymous bool) (newBindDN string, boundUser *config.User, ldapresultcode ldap.LDAPResultCode) {
|
||||
boundUser, ldapcode := l.findUser(h, bindDN, false /* checkGroup */)
|
||||
if ldapcode != ldap.LDAPResultSuccess {
|
||||
return "", nil, ldapcode
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
return bindDN, boundUser, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
// Search RootDSE and return information on the server
|
||||
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultUnwillingToPerform, LDAPResultInsufficientAccessRights
|
||||
func (l LDAPOpsHelper) searchMaybeRootDSEQuery(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest, anonymous bool) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
|
||||
if searchBaseDN != "" {
|
||||
return nil, ldap.LDAPResultOther // OK
|
||||
}
|
||||
/// Only base scope searches allowed if no basedn is provided
|
||||
if searchReq.Scope != ldap.ScopeBaseObject {
|
||||
h.GetLog().Info().Interface("src", searchReq.Controls).Msg("Search Error: No BaseDN provided")
|
||||
return nil, ldap.LDAPResultUnwillingToPerform // KO
|
||||
}
|
||||
if anonymous && !h.GetBackend().AnonymousDSE {
|
||||
return nil, ldap.LDAPResultInsufficientAccessRights // KO
|
||||
}
|
||||
|
||||
h.GetLog().Info().Str("special case", "root DSE").Msg("Search request")
|
||||
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().Info().Str("filter", searchReq.Filter).Msg("AP: Root Search OK")
|
||||
return entries, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
// Search and return the information, after indirection from the RootDSE
|
||||
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultOperationsError
|
||||
func (l LDAPOpsHelper) searchMaybeSchemaQuery(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest, anonymous bool) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode, attributename *string) {
|
||||
if searchBaseDN != "cn=schema" {
|
||||
return nil, ldap.LDAPResultOther, nil // OK
|
||||
}
|
||||
|
||||
h.GetLog().Info().Str("special case", "schema discovery").Msg("Search request")
|
||||
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 {
|
||||
attributename := new(string)
|
||||
*attributename = filename.Name()
|
||||
file, err := os.Open(filepath.Join("schema", *attributename))
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError, attributename
|
||||
}
|
||||
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().Info().Str("filter", searchReq.Filter).Msg("AP: Schema Discovery OK")
|
||||
return entries, ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
// Retrieve the top-levell nodes, i.e. the baseDN, groups, members...
|
||||
// Returns: LDAPResultSuccess, LDAPResultOther
|
||||
func (l LDAPOpsHelper) searchMaybeTopLevelNodes(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
|
||||
if baseDN != searchBaseDN {
|
||||
return nil, ldap.LDAPResultOther // OK
|
||||
}
|
||||
h.GetLog().Info().Str("special case", "top-level browse").Msg("Search request")
|
||||
entries := []*ldap.Entry{}
|
||||
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
entries = append(entries, l.topLevelRootNode(searchBaseDN))
|
||||
}
|
||||
entries = append(entries, l.topLevelGroupsNode(searchBaseDN, "groups"))
|
||||
entries = append(entries, l.topLevelUsersNode(searchBaseDN))
|
||||
if searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
groupentries, err := h.FindPosixGroups("ou=users")
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
entries = append(entries, groupentries...)
|
||||
|
||||
userentries, err := h.FindPosixAccounts("ou=users")
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
entries = append(entries, userentries...)
|
||||
}
|
||||
stats.Frontend.Add("search_successes", 1)
|
||||
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Top-Level Browse OK")
|
||||
return entries, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
// Search starting from and including the ou=groups node
|
||||
// Returns: LDAPResultSuccess, LDAPResultOther
|
||||
func (l LDAPOpsHelper) searchMaybeTopLevelGroupsNode(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
|
||||
if searchBaseDN != fmt.Sprintf("ou=groups,%s", baseDN) {
|
||||
return nil, ldap.LDAPResultOther // OK
|
||||
}
|
||||
h.GetLog().Info().Str("special case", "top-level groups node").Msg("Search request")
|
||||
entries := []*ldap.Entry{}
|
||||
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
entries = append(entries, l.topLevelGroupsNode(searchBaseDN, "groups"))
|
||||
}
|
||||
if searchReq.Scope == ldap.ScopeSingleLevel || searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
groupentries, err := h.FindPosixGroups("ou=groups")
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
entries = append(entries, groupentries...)
|
||||
}
|
||||
stats.Frontend.Add("search_successes", 1)
|
||||
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Top-Level Groups Browse OK")
|
||||
return entries, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
// Search starting from and including the ou=users node
|
||||
// Returns: LDAPResultSuccess, LDAPResultOther
|
||||
func (l LDAPOpsHelper) searchMaybeTopLevelUsersNode(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
|
||||
if searchBaseDN != fmt.Sprintf("ou=users,%s", baseDN) {
|
||||
return nil, ldap.LDAPResultOther // OK
|
||||
}
|
||||
h.GetLog().Info().Str("special case", "top-level users node").Msg("Search request")
|
||||
entries := []*ldap.Entry{}
|
||||
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
entries = append(entries, l.topLevelUsersNode(searchBaseDN))
|
||||
}
|
||||
if searchReq.Scope == ldap.ScopeSingleLevel || searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
groupentries, err := h.FindPosixGroups("ou=users")
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
entries = append(entries, groupentries...)
|
||||
}
|
||||
if searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
userentries, err := h.FindPosixAccounts("ou=users")
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
entries = append(entries, userentries...)
|
||||
}
|
||||
stats.Frontend.Add("search_successes", 1)
|
||||
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Top-Level Users Browse OK")
|
||||
return entries, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
// Look up posixgroup entries, either through objectlass or parent is ou=groups or ou=users
|
||||
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultOperationsError
|
||||
func (l LDAPOpsHelper) searchMaybePosixGroups(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest, filterEntity string) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
|
||||
hierarchy := "ou=groups"
|
||||
if filterEntity != "posixgroup" {
|
||||
bits := strings.Split(strings.Replace(searchBaseDN, baseDN, "", 1), ",")
|
||||
if len(bits) != 3 || (bits[1] != "ou=groups" && bits[1] != "ou=users") {
|
||||
return nil, ldap.LDAPResultOther // OK
|
||||
}
|
||||
hierarchy = bits[1]
|
||||
}
|
||||
h.GetLog().Info().Str("special case", "posix groups").Msg("Search request")
|
||||
entries := []*ldap.Entry{}
|
||||
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
groupentries, err := h.FindPosixGroups(hierarchy)
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
entries = append(entries, l.preFilterEntries(searchBaseDN, groupentries)...)
|
||||
}
|
||||
if searchReq.Scope == ldap.ScopeSingleLevel || searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
if hierarchy == "ou=users" {
|
||||
userentries, err := h.FindPosixAccounts("ou=users")
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
entries = append(entries, l.preFilterEntries(searchBaseDN, userentries)...)
|
||||
}
|
||||
}
|
||||
stats.Frontend.Add("search_successes", 1)
|
||||
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Posix Groups Search OK")
|
||||
return entries, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
// Lookup posixaccount entries
|
||||
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultOperationsError
|
||||
// This function ignores scopes... for now
|
||||
func (l LDAPOpsHelper) searchMaybePosixAccounts(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest, filterEntity string) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
|
||||
switch filterEntity {
|
||||
case "posixaccount", "shadowaccount", "":
|
||||
h.GetLog().Info().Str("default case", filterEntity).Msg("Search request")
|
||||
default:
|
||||
return nil, ldap.LDAPResultOther // OK
|
||||
}
|
||||
|
||||
// FixUp: we may be in the process of browsing users from a group ou
|
||||
hierarchyString := ""
|
||||
if strings.HasSuffix(searchBaseDN, fmt.Sprintf("ou=users,%s", baseDN)) {
|
||||
hierarchyString = "ou=users"
|
||||
}
|
||||
|
||||
unscopedEntries, err := h.FindPosixAccounts(hierarchyString)
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
|
||||
// Filter out entries, that are not in the search base dn
|
||||
entries := []*ldap.Entry{}
|
||||
for _, e := range unscopedEntries {
|
||||
if strings.HasSuffix(e.DN, searchBaseDN) {
|
||||
entries = append(entries, e)
|
||||
}
|
||||
}
|
||||
|
||||
stats.Frontend.Add("search_successes", 1)
|
||||
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Account Search OK")
|
||||
return entries, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
func (l LDAPOpsHelper) topLevelRootNode(searchBaseDN string) *ldap.Entry {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
dnBits := strings.Split(searchBaseDN, ",")
|
||||
for _, dnBit := range dnBits {
|
||||
chunk := strings.Split(dnBit, "=")
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: chunk[0], Values: []string{chunk[1]}})
|
||||
}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"organizationalUnit", "dcObject", "top"}})
|
||||
return &ldap.Entry{DN: searchBaseDN, Attributes: attrs}
|
||||
}
|
||||
|
||||
func (l LDAPOpsHelper) topLevelGroupsNode(searchBaseDN string, hierarchy string) *ldap.Entry {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{"groups"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"organizationalUnit", "top"}})
|
||||
hierarchyStringPrefix := fmt.Sprintf("ou=%s,", hierarchy)
|
||||
dn := searchBaseDN
|
||||
if !strings.HasPrefix(dn, hierarchyStringPrefix) {
|
||||
dn = fmt.Sprintf("%s%s", hierarchyStringPrefix, dn)
|
||||
}
|
||||
return &ldap.Entry{DN: dn, Attributes: attrs}
|
||||
}
|
||||
|
||||
func (l LDAPOpsHelper) topLevelUsersNode(searchBaseDN string) *ldap.Entry {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{"users"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"organizationalUnit", "top"}})
|
||||
dn := searchBaseDN
|
||||
if !strings.HasPrefix(dn, "ou=users,") {
|
||||
dn = fmt.Sprintf("ou=users,%s", dn)
|
||||
}
|
||||
return &ldap.Entry{DN: dn, Attributes: attrs}
|
||||
}
|
||||
|
||||
// I am not quite sure why but I found out that, maybe due to my playing around with their DN,
|
||||
// querying groups and users under a certain node (e.g. ou=users) with a scope of "sub"
|
||||
// (and only in this scenario!) will defeat the LDAP library's filtering capabilities.
|
||||
// Some day, hopefully, I'll fix this directly in the library.
|
||||
func (l LDAPOpsHelper) preFilterEntries(searchBaseDN string, entries []*ldap.Entry) (resultentries []*ldap.Entry) {
|
||||
filteredEntries := []*ldap.Entry{}
|
||||
for _, entry := range entries {
|
||||
if strings.HasSuffix(entry.DN, searchBaseDN) {
|
||||
filteredEntries = append(filteredEntries, entry)
|
||||
}
|
||||
}
|
||||
return filteredEntries
|
||||
}
|
||||
|
||||
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().Info().Str("userprincipalname", bindDN).Msg("User not found")
|
||||
return nil, ldap.LDAPResultInvalidCredentials
|
||||
}
|
||||
} else {
|
||||
// parse the bindDN - ensure that the bindDN ends with the BaseDN
|
||||
if !strings.HasSuffix(bindDN, baseDN) {
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("basedn", h.GetBackend().BaseDN).Msg("BindDN not part of our 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 || (len(parts) == 3 && parts[2] == fmt.Sprintf("%s=users", h.GetBackend().GroupFormat)) {
|
||||
userName = strings.TrimPrefix(parts[0], h.GetBackend().NameFormat+"=")
|
||||
groupName = strings.TrimPrefix(parts[1], h.GetBackend().GroupFormat+"=")
|
||||
} else {
|
||||
h.GetLog().Info().Str("binddn", bindDN).Int("numparts", len(parts)).Msg("BindDN should have only one or two parts")
|
||||
for _, part := range parts {
|
||||
h.GetLog().Info().Str("part", part).Msg("Parts")
|
||||
}
|
||||
return nil, ldap.LDAPResultInvalidCredentials
|
||||
}
|
||||
|
||||
// find the user
|
||||
var foundUser bool // = false
|
||||
foundUser, user, _ = h.FindUser(userName, false)
|
||||
if !foundUser {
|
||||
h.GetLog().Info().Str("username", userName).Msg("User not found")
|
||||
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().Info().Str("groupname", groupName).Msg("Group not found")
|
||||
return nil, ldap.LDAPResultInvalidCredentials
|
||||
}
|
||||
}
|
||||
// validate group membership
|
||||
if foundGroup {
|
||||
if user.PrimaryGroup != group.GIDNumber {
|
||||
h.GetLog().Info().Str("username", userName).Int("primarygroup", user.PrimaryGroup).Int("groupid", group.GIDNumber).Msg("primary group mismatch")
|
||||
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]
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
)
|
||||
|
||||
// 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 *zerolog.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
|
||||
}
|
||||
}
|
||||
|
||||
// Handlers 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 *zerolog.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
|
||||
}
|
||||
}
|
||||
|
||||
// Helper If we specified a helper, for instance for OTP injection
|
||||
func Helper(val Handler) Option {
|
||||
return func(o *Options) {
|
||||
o.Helper = val
|
||||
}
|
||||
}
|
||||
|
||||
// LDAPHelper Global LDAP Handler
|
||||
func LDAPHelper(val LDAPOpsHelper) Option {
|
||||
return func(o *Options) {
|
||||
o.LDAPHelper = val
|
||||
}
|
||||
}
|
||||
@@ -2,84 +2,92 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
"github.com/glauth/glauth/pkg/stats"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/glauth/v2/pkg/stats"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/op/go-logging"
|
||||
msgraph "github.com/yaegashi/msgraph.go/v1.0"
|
||||
)
|
||||
|
||||
type ownCloudSession struct {
|
||||
log *logging.Logger
|
||||
log *zerolog.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 *zerolog.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.Debug().Str("binddn", bindDN).Str("basedn", h.backend.BaseDN).Str("src", conn.RemoteAddr().String()).Msg("Bind request")
|
||||
|
||||
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.Warn().Str("binddn", bindDN).Str("basedn", h.backend.BaseDN).Msg("BindDN not part of our 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.Warn().Str("binddn", bindDN).Int("numparts", len(parts)).Msg("BindDN should have only one or two 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.Warn().Str("username", userName).Str("basedn", h.backend.BaseDN).Msg("Login failed")
|
||||
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.Debug().Str("binddn", bindDN).Str("basedn", h.backend.BaseDN).Str("src", conn.RemoteAddr().String()).Msg("Bind success")
|
||||
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.Debug().Str("binddn", bindDN).Str("basedn", baseDN).Str("src", conn.RemoteAddr().String()).Str("filter", searchReq.Filter).Msg("Search request")
|
||||
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{}
|
||||
@@ -113,9 +121,9 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
|
||||
}
|
||||
for _, g := range groups {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{*g.ID}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: h.backend.GroupFormat, 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,25 +133,25 @@ 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.Debug().Str("username", userName).Err(err).Msg("Could not get user")
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, errors.New("search error: error getting users")
|
||||
}
|
||||
for _, u := range users {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{*u.ID}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: h.backend.NameFormat, Values: []string{*u.ID}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{*u.ID}})
|
||||
if u.DisplayName != nil {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "givenName", Values: []string{*u.DisplayName}})
|
||||
@@ -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.Debug().Str("filter", searchReq.Filter).Msg("AP: Search OK")
|
||||
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(err).Int("status", resp.StatusCode).Msg("failed login")
|
||||
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.Debug().Msg("using graph api")
|
||||
ctx := context.Background()
|
||||
req := s.NewClient().Users()
|
||||
if len(userName) > 0 {
|
||||
s.log.Debug().Msg("fetching single user")
|
||||
u, err := req.ID(userName).Request().Get(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []msgraph.User{*u}, nil
|
||||
}
|
||||
s.log.Debug().Msg("fetching all users")
|
||||
return req.Request().Get(ctx)
|
||||
}
|
||||
usersUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/users?format=json", s.baseUrl)
|
||||
s.log.Debug().Msg("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.Debug().Str("username", s.user).Msg("Setting user and password")
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"io"
|
||||
"log"
|
||||
"log/syslog"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// We will use this package to wrap log messages coming from libraries who have no interest
|
||||
// in generating structured output.
|
||||
|
||||
var (
|
||||
ldapliblogmatcher = regexp.MustCompile(`^\d{4}\/\d{1,2}\/\d{1,2} \d{1,2}\:\d{1,2}\:\d{1,2} `)
|
||||
)
|
||||
|
||||
func InitLogging(reqdebug bool, reqsyslog bool, reqstructlog bool) zerolog.Logger {
|
||||
var level zerolog.Level
|
||||
if reqdebug {
|
||||
level = zerolog.DebugLevel
|
||||
} else {
|
||||
level = zerolog.InfoLevel
|
||||
}
|
||||
|
||||
var mainWriter io.Writer
|
||||
if reqstructlog {
|
||||
// Vroom vroom
|
||||
mainWriter = os.Stderr
|
||||
zerolog.TimeFieldFormat = time.RFC1123Z
|
||||
} else {
|
||||
// This is the inefficient writer
|
||||
mainWriter = zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC1123Z}
|
||||
}
|
||||
|
||||
var logr zerolog.Logger
|
||||
if reqsyslog {
|
||||
s, err := syslog.New(syslog.LOG_INFO, "glauth")
|
||||
if err != nil {
|
||||
fmt.Println("Unable to write to syslog: ignoring...")
|
||||
reqsyslog = false
|
||||
} else {
|
||||
writers := zerolog.MultiLevelWriter(mainWriter, zerolog.SyslogLevelWriter(s))
|
||||
logr = zerolog.New(writers).Level(level).With().Timestamp().Logger()
|
||||
}
|
||||
}
|
||||
|
||||
if !reqsyslog {
|
||||
logr = zerolog.New(mainWriter).Level(level).With().Timestamp().Logger()
|
||||
}
|
||||
|
||||
log.SetOutput(customWriter{logr: logr, structlog: reqstructlog})
|
||||
|
||||
return logr
|
||||
}
|
||||
|
||||
type customWriter struct {
|
||||
logr zerolog.Logger
|
||||
structlog bool
|
||||
}
|
||||
|
||||
func (e customWriter) Write(p []byte) (int, error) {
|
||||
submatchall := ldapliblogmatcher.FindAllString(string(p), 1)
|
||||
var msg string
|
||||
for _, element := range submatchall {
|
||||
msg = strings.TrimSpace(string(p[len(element):]))
|
||||
}
|
||||
if msg == "" {
|
||||
msg = strings.TrimSpace(string(p))
|
||||
}
|
||||
if e.structlog {
|
||||
fmt.Fprintf(os.Stderr, "{\"level\":\"info\",\"time\":\"%s\",\"message\":\"%s\"}\n", time.Now().Format(time.RFC1123Z), strings.Replace(strings.TrimSpace(msg), `"`, `\"`, -1))
|
||||
} else {
|
||||
e.logr.Info().Msg(msg)
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// We will use this package to wrap log messages coming from libraries who have no interest
|
||||
// in generating structured output.
|
||||
|
||||
var (
|
||||
ldapliblogmatcher = regexp.MustCompile(`^\d{4}\/\d{1,2}\/\d{1,2} \d{1,2}\:\d{1,2}\:\d{1,2} `)
|
||||
)
|
||||
|
||||
func InitLogging(reqdebug bool, reqsyslog bool, reqstructlog bool) zerolog.Logger {
|
||||
var level zerolog.Level
|
||||
if reqdebug {
|
||||
level = zerolog.DebugLevel
|
||||
} else {
|
||||
level = zerolog.InfoLevel
|
||||
}
|
||||
|
||||
var mainWriter io.Writer
|
||||
if reqstructlog {
|
||||
// Vroom vroom
|
||||
mainWriter = os.Stderr
|
||||
zerolog.TimeFieldFormat = time.RFC1123Z
|
||||
} else {
|
||||
// This is the inefficient writer
|
||||
mainWriter = zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC1123Z}
|
||||
}
|
||||
|
||||
logr := zerolog.New(mainWriter).Level(level).With().Timestamp().Logger()
|
||||
|
||||
log.SetOutput(customWriter{logr: logr, structlog: reqstructlog})
|
||||
|
||||
return logr
|
||||
}
|
||||
|
||||
type customWriter struct {
|
||||
logr zerolog.Logger
|
||||
structlog bool
|
||||
}
|
||||
|
||||
func (e customWriter) Write(p []byte) (int, error) {
|
||||
submatchall := ldapliblogmatcher.FindAllString(string(p), 1)
|
||||
var msg string
|
||||
for _, element := range submatchall {
|
||||
msg = strings.TrimSpace(string(p[len(element):]))
|
||||
}
|
||||
if msg == "" {
|
||||
msg = strings.TrimSpace(string(p))
|
||||
}
|
||||
if e.structlog {
|
||||
fmt.Fprintf(os.Stderr, "{\"level\":\"info\",\"time\":\"%s\",\"message\":\"%s\"}\n", time.Now().Format(time.RFC1123Z), strings.Replace(strings.TrimSpace(msg), `"`, `\"`, -1))
|
||||
} else {
|
||||
e.logr.Info().Msg(msg)
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
@@ -0,0 +1,588 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/glauth/v2/pkg/handler"
|
||||
"github.com/glauth/glauth/v2/pkg/stats"
|
||||
"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)
|
||||
// Migrate schema if necessary
|
||||
MigrateSchema(db *sql.DB, checker func(*sql.DB, string) bool)
|
||||
//
|
||||
GetPrepareSymbol() string
|
||||
}
|
||||
|
||||
type database struct {
|
||||
path string
|
||||
cnx *sql.DB
|
||||
}
|
||||
|
||||
type databaseHandler struct {
|
||||
backend config.Backend
|
||||
log *zerolog.Logger
|
||||
cfg *config.Config
|
||||
yubikeyAuth *yubigo.YubiAuth
|
||||
sqlBackend SqlBackend
|
||||
database database
|
||||
MemGroups []config.Group
|
||||
ldohelper handler.LDAPOpsHelper
|
||||
attmatcher *regexp.Regexp
|
||||
}
|
||||
|
||||
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(err).Msg(fmt.Sprintf("unable to open SQL database named '%s'", options.Backend.Database))
|
||||
os.Exit(1)
|
||||
}
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
options.Logger.Error().Err(err).Msg(fmt.Sprintf("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)
|
||||
sqlBackend.MigrateSchema(db, ColumnExists)
|
||||
|
||||
options.Logger.Info().Msg("Database (" + sqlBackend.GetDriverName() + "::" + options.Backend.Database + ") Plugin: Ready")
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func ColumnExists(db *sql.DB, columnName string) bool {
|
||||
var found string
|
||||
err := db.QueryRow(fmt.Sprintf(`SELECT COUNT(%s) FROM users`, columnName)).Scan(
|
||||
&found)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (h databaseHandler) GetBackend() config.Backend {
|
||||
return h.backend
|
||||
}
|
||||
func (h databaseHandler) GetLog() *zerolog.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
|
||||
|
||||
var disabled int
|
||||
err = h.database.cnx.QueryRow(fmt.Sprintf(`
|
||||
SELECT u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.disabled
|
||||
FROM users u WHERE %s=%s`,
|
||||
criterion,
|
||||
h.sqlBackend.GetPrepareSymbol()), userName).Scan(
|
||||
&user.UIDNumber, &user.PrimaryGroup, &user.PassBcrypt, &user.PassSHA256, &user.OTPSecret, &user.Yubikey, &disabled)
|
||||
if err == nil {
|
||||
user.Disabled = h.intToBool(disabled)
|
||||
if !user.Disabled {
|
||||
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(hierarchy string) (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,u.sshkeys,u.custattr
|
||||
FROM users u`)
|
||||
if err != nil {
|
||||
return entries, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var otherGroups string
|
||||
var disabled int
|
||||
var sshKeys string
|
||||
var custattrstr 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, &u.GivenName, &u.SN, &u.Mail, &u.LoginShell, &u.Homedir, &disabled, &sshKeys, &custattrstr)
|
||||
if err != nil {
|
||||
return entries, err
|
||||
}
|
||||
u.OtherGroups = h.commaListToIntTable(otherGroups)
|
||||
u.Disabled = h.intToBool(disabled)
|
||||
u.SSHKeys = h.commaListToStringTable(sshKeys)
|
||||
|
||||
entry := h.getAccount(hierarchy, u)
|
||||
|
||||
if custattrstr != "{}" {
|
||||
var r map[string]interface{}
|
||||
err := json.Unmarshal([]byte(custattrstr), &r)
|
||||
if err != nil {
|
||||
return entries, err
|
||||
}
|
||||
for key, attr := range r {
|
||||
switch typedattr := attr.(type) {
|
||||
case []interface{}:
|
||||
var values []string
|
||||
for _, v := range typedattr {
|
||||
switch typedvalue := v.(type) {
|
||||
case string:
|
||||
values = append(values, handler.MaybeDecode(typedvalue))
|
||||
default:
|
||||
values = append(values, handler.MaybeDecode(fmt.Sprintf("%v", typedvalue)))
|
||||
}
|
||||
}
|
||||
entry.Attributes = append(entry.Attributes, &ldap.EntryAttribute{Name: key, Values: values})
|
||||
default:
|
||||
h.log.Warn().Str("key", key).Interface("value", attr).Msg("Unable to map custom attribute")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (h databaseHandler) FindPosixGroups(hierarchy string) (entrylist []*ldap.Entry, err error) {
|
||||
entries := []*ldap.Entry{}
|
||||
|
||||
h.MemGroups, err = h.memoizeGroups()
|
||||
if err != nil {
|
||||
return entries, err
|
||||
}
|
||||
|
||||
for _, g := range h.MemGroups {
|
||||
info := h.getGroup(hierarchy, g)
|
||||
if hierarchy != "groups" {
|
||||
info.DN = strings.Replace(info.DN, ",ou=groups,", fmt.Sprintf(",%s,", hierarchy), 1)
|
||||
}
|
||||
entries = append(entries, info)
|
||||
}
|
||||
|
||||
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) commaListToIntTable(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) commaListToStringTable(commaList string) []string {
|
||||
if len(commaList) == 0 {
|
||||
return make([]string, 0)
|
||||
}
|
||||
return strings.Split(commaList, ",")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (h databaseHandler) getGroupMemberDNs(gid int) []string {
|
||||
var insertOuUsers string
|
||||
if h.cfg.Behaviors.LegacyVersion > 0 && h.cfg.Behaviors.LegacyVersion <= 20100 {
|
||||
insertOuUsers = ""
|
||||
} else {
|
||||
insertOuUsers = ",ou=users"
|
||||
}
|
||||
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 {
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
|
||||
members[dn] = true
|
||||
} else {
|
||||
u.OtherGroups = h.commaListToIntTable(otherGroups)
|
||||
for _, othergid := range u.OtherGroups {
|
||||
if othergid == gid {
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), insertOuUsers, 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.getGroupMemberDNs(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.commaListToIntTable(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.Warn().Msg(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("%s=%s,ou=groups,%s", h.backend.GroupFormat, 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(hierarchy string, g config.Group) *ldap.Entry {
|
||||
asGroupOfUniqueNames := hierarchy == "ou=groups"
|
||||
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{h.backend.GroupFormat, []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{"uniqueMember", h.getGroupMemberDNs(g.GIDNumber)})
|
||||
if asGroupOfUniqueNames {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"groupOfUniqueNames", "top"}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"memberUid", h.getGroupMemberIDs(g.GIDNumber)})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixGroup", "top"}})
|
||||
}
|
||||
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormat, g.Name, h.backend.BaseDN)
|
||||
return &ldap.Entry{dn, attrs}
|
||||
}
|
||||
|
||||
func (h databaseHandler) getAccount(hierarchy string, u config.User) *ldap.Entry {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{h.backend.NameFormat, []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})
|
||||
}
|
||||
var dn string
|
||||
if hierarchy == "" {
|
||||
dn = fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
|
||||
} else {
|
||||
dn = fmt.Sprintf("%s=%s,%s=%s,%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), hierarchy, h.backend.BaseDN)
|
||||
}
|
||||
return &ldap.Entry{dn, attrs}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
release-glauth-bogus:
|
||||
@echo Ignoring bogus plugin
|
||||
@@ -0,0 +1,50 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger zerolog.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 zerolog.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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"plugin"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/glauth/v2/pkg/handler"
|
||||
"github.com/nmcclain/ldap"
|
||||
)
|
||||
|
||||
type LdapSvc struct {
|
||||
log zerolog.Logger
|
||||
c *config.Config
|
||||
yubiAuth *yubigo.YubiAuth
|
||||
l *ldap.Server
|
||||
}
|
||||
|
||||
func NewServer(opts ...Option) (*LdapSvc, error) {
|
||||
options := newOptions(opts...)
|
||||
|
||||
s := LdapSvc{
|
||||
log: options.Logger,
|
||||
c: options.Config,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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.Info().Str("datastore", s.c.Helper.Datastore).Msg("Using helper")
|
||||
}
|
||||
|
||||
backendCounter := -1
|
||||
allHandlers := handler.HandlerWrapper{Handlers: make([]handler.Handler, 10), Count: &backendCounter}
|
||||
|
||||
// configure the backends
|
||||
s.l = ldap.NewServer()
|
||||
s.l.EnforceLDAP = true
|
||||
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.Info().Str("datastore", backend.Datastore).Int("position", i).Msg("Loading backend")
|
||||
|
||||
// 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++
|
||||
}
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the TCP network address s.c.LDAP.Listen
|
||||
func (s *LdapSvc) ListenAndServe() error {
|
||||
s.log.Info().Str("address", s.c.LDAP.Listen).Msg("LDAP server listening")
|
||||
return s.l.ListenAndServe(s.c.LDAP.Listen)
|
||||
}
|
||||
|
||||
// ListenAndServeTLS listens on the TCP network address s.c.LDAPS.Listen
|
||||
func (s *LdapSvc) ListenAndServeTLS() error {
|
||||
s.log.Info().Str("address", s.c.LDAPS.Listen).Msg("LDAPS server listening")
|
||||
return s.l.ListenAndServeTLS(
|
||||
s.c.LDAPS.Listen,
|
||||
s.c.LDAPS.Cert,
|
||||
s.c.LDAPS.Key,
|
||||
)
|
||||
}
|
||||
|
||||
// Shutdown ends listeners by sending true to the ldap serves quit channel
|
||||
func (s *LdapSvc) Shutdown() {
|
||||
s.l.Quit <- true
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package stats
|
||||
|
||||
import "expvar"
|
||||
import (
|
||||
"expvar"
|
||||
)
|
||||
|
||||
// exposed expvar variables
|
||||
var (
|
||||
@@ -0,0 +1,5 @@
|
||||
package stats
|
||||
|
||||
type Stringer string
|
||||
|
||||
func (s Stringer) String() string { return "\"" + string(s) + "\"" }
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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/"]
|
||||
@@ -0,0 +1,161 @@
|
||||
#################
|
||||
# glauth.conf
|
||||
|
||||
#################
|
||||
# General configuration.
|
||||
debug = true
|
||||
# syslog = true
|
||||
# structuredlog = 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 generate 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"
|
||||
nameformat = "cn"
|
||||
groupformat = "ou"
|
||||
|
||||
# If you are using a client that requires reading the root DSE first
|
||||
# such as SSSD
|
||||
# anonymousdse = true
|
||||
|
||||
## 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.customattributes]]
|
||||
employeetype = ["Intern", "Temp"]
|
||||
employeenumber = [12345, 54321]
|
||||
[[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
|
||||
internals = true # debug application performance
|
||||
tls = false # enable TLS for production!!
|
||||
listen = "0.0.0.0:5555"
|
||||
cert = "cert.pem"
|
||||
key = "key.pem"
|
||||
+40
-35
@@ -1,4 +1,4 @@
|
||||
dn: cn=hackers,ou=superheros,dc=glauth,dc=com
|
||||
dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
cn: hackers
|
||||
uid: hackers
|
||||
ou: superheros
|
||||
@@ -11,9 +11,9 @@ homeDirectory: /home/hackers
|
||||
description: hackers
|
||||
gecos: hackers
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
@@ -22,7 +22,7 @@ shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
|
||||
dn: cn=johndoe,ou=superheros,dc=glauth,dc=com
|
||||
dn: cn=johndoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
cn: johndoe
|
||||
uid: johndoe
|
||||
givenName: John
|
||||
@@ -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
|
||||
@@ -38,9 +39,9 @@ homeDirectory: /root
|
||||
description: johndoe
|
||||
gecos: johndoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
@@ -55,7 +56,7 @@ sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwV
|
||||
YnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+
|
||||
txSuhw== rsa-key-20160209
|
||||
|
||||
dn: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
dn: cn=jamesdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
cn: jamesdoe
|
||||
uid: jamesdoe
|
||||
givenName: James
|
||||
@@ -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
|
||||
@@ -71,11 +73,11 @@ homeDirectory: /home/jamesdoe
|
||||
description: jamesdoe
|
||||
gecos: jamesdoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
@@ -90,7 +92,7 @@ sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwV
|
||||
YnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+
|
||||
txSuhw== rsa-key-20160209
|
||||
|
||||
dn: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
dn: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
cn: alexdoe
|
||||
uid: alexdoe
|
||||
givenName: Alex
|
||||
@@ -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
|
||||
@@ -106,11 +109,11 @@ homeDirectory: /home/alexdoe
|
||||
description: alexdoe
|
||||
gecos: alexdoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
@@ -119,7 +122,7 @@ shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
|
||||
dn: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
dn: cn=jackdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
cn: jackdoe
|
||||
uid: jackdoe
|
||||
givenName: Jack
|
||||
@@ -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
|
||||
@@ -135,11 +139,11 @@ homeDirectory: /home/jackdoe
|
||||
description: jackdoe
|
||||
gecos: jackdoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
@@ -148,7 +152,7 @@ shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
|
||||
dn: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
dn: cn=sarahdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
cn: sarahdoe
|
||||
uid: sarahdoe
|
||||
givenName: Sarah
|
||||
@@ -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
|
||||
@@ -164,11 +169,11 @@ homeDirectory: /home/sarahdoe
|
||||
description: sarahdoe
|
||||
gecos: sarahdoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
@@ -177,7 +182,7 @@ shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
|
||||
dn: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
|
||||
dn: cn=serviceuser,ou=svcaccts,ou=users,dc=glauth,dc=com
|
||||
cn: serviceuser
|
||||
uid: serviceuser
|
||||
ou: svcaccts
|
||||
@@ -190,8 +195,8 @@ homeDirectory: /home/serviceuser
|
||||
description: serviceuser
|
||||
gecos: serviceuser
|
||||
gidNumber: 5502
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=svcaccts,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: ou=svcaccts,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
@@ -0,0 +1,112 @@
|
||||
dn: ou=superheros,ou=users,dc=glauth,dc=com
|
||||
ou: superheros
|
||||
uid: superheros
|
||||
description: superheros
|
||||
gidNumber: 5501
|
||||
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=johndoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: hackers
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: johndoe
|
||||
memberUid: sarahdoe
|
||||
objectClass: posixGroup
|
||||
objectClass: top
|
||||
|
||||
dn: ou=svcaccts,ou=users,dc=glauth,dc=com
|
||||
ou: svcaccts
|
||||
uid: svcaccts
|
||||
description: svcaccts
|
||||
gidNumber: 5502
|
||||
uniqueMember: cn=serviceuser,ou=svcaccts,ou=users,dc=glauth,dc=com
|
||||
memberUid: serviceuser
|
||||
objectClass: posixGroup
|
||||
objectClass: top
|
||||
|
||||
dn: ou=vpnaccess,ou=users,dc=glauth,dc=com
|
||||
ou: vpnaccess
|
||||
uid: vpnaccess
|
||||
description: vpnaccess
|
||||
gidNumber: 5503
|
||||
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=johndoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: hackers
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: johndoe
|
||||
memberUid: sarahdoe
|
||||
objectClass: posixGroup
|
||||
objectClass: top
|
||||
|
||||
dn: ou=allaccs,ou=users,dc=glauth,dc=com
|
||||
ou: allaccs
|
||||
uid: allaccs
|
||||
description: allaccs
|
||||
gidNumber: 5504
|
||||
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=johndoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=serviceuser,ou=svcaccts,ou=users,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: hackers
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: johndoe
|
||||
memberUid: sarahdoe
|
||||
memberUid: serviceuser
|
||||
objectClass: posixGroup
|
||||
objectClass: top
|
||||
|
||||
dn: ou=mailadmin,ou=users,dc=glauth,dc=com
|
||||
ou: mailadmin
|
||||
uid: mailadmin
|
||||
description: mailadmin
|
||||
gidNumber: 5505
|
||||
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: sarahdoe
|
||||
objectClass: posixGroup
|
||||
objectClass: top
|
||||
|
||||
dn: ou=webmail,ou=users,dc=glauth,dc=com
|
||||
ou: webmail
|
||||
uid: webmail
|
||||
description: webmail
|
||||
gidNumber: 5506
|
||||
objectClass: posixGroup
|
||||
objectClass: top
|
||||
|
||||
dn: ou=fulltime,ou=users,dc=glauth,dc=com
|
||||
ou: fulltime
|
||||
uid: fulltime
|
||||
description: fulltime
|
||||
gidNumber: 5507
|
||||
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: sarahdoe
|
||||
objectClass: posixGroup
|
||||
objectClass: top
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user