mirror of
https://github.com/glauth/glauth.git
synced 2026-10-27 06:26:59 +00:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 728d1e3274 | |||
| 48981b9462 | |||
| 80314d335b | |||
| 2ee3dbf3c7 | |||
| d7069b027b | |||
| 882714c9dc | |||
| 07e59076e4 | |||
| 5147d1165a | |||
| 374041c358 | |||
| 5bdeb5118b | |||
| 319cc9a3c7 | |||
| 26cd4447c8 | |||
| 96a283fea2 | |||
| c07a1c978c | |||
| c1c57c62e9 | |||
| 700745e640 | |||
| 1c497d76ba | |||
| cf0eb7f43f | |||
| fb65713fbd | |||
| cb121cdc2c | |||
| 4b2b1398b8 | |||
| be396ae635 | |||
| 719f6a2a2d | |||
| ff7a9e6215 | |||
| 45185dafd1 | |||
| aedc0f2867 | |||
| 0774c95c1a | |||
| 9e04c8b756 | |||
| d3df7252ec | |||
| 5123a3da8e | |||
| eabf67e266 | |||
| d68c78a18b | |||
| 645dbb2266 | |||
| eea7bfbb4f | |||
| b498736cfd | |||
| f8dee8da87 | |||
| c4a020a4f5 | |||
| e92008680c | |||
| 8f972db4cf | |||
| e9a4a5623a | |||
| 8e042199a2 | |||
| c0ed90822f | |||
| a5ecc26e06 | |||
| a90f37b23b | |||
| 8476506b8c | |||
| 262b5061ca | |||
| a2c151b0d0 | |||
| 42c448f3c7 | |||
| ed52a91ec4 | |||
| 6d161b7033 | |||
| 5eafb3bc15 | |||
| bc29d321bf | |||
| 540631cc9d | |||
| 3c3ac7e148 | |||
| 14bb205f90 | |||
| 10613f37df | |||
| 56ccb881b5 | |||
| 837da62607 | |||
| 0bf3d4a82a | |||
| f5bb80e024 | |||
| 1a37396109 | |||
| d178281474 | |||
| a0d1069841 | |||
| 67d77324a3 | |||
| 01bdb7148e | |||
| f692394942 | |||
| 46e49b6976 | |||
| 52a0adcbcf | |||
| 2ca5312696 | |||
| 6ee78cd881 | |||
| 1c2b23c00f | |||
| d05630f66b | |||
| 0cf04037a2 | |||
| 7174aba5ab | |||
| 5ddbd4f610 | |||
| 0b6cfcb1b4 | |||
| f3342fa709 | |||
| 545188e5f2 | |||
| c90cbfe5fb | |||
| 1721e5701d | |||
| ebdeae7073 | |||
| 94fe62cfe1 | |||
| f9addbc2c5 | |||
| 5fe8aca852 | |||
| 24455e3988 | |||
| 3ba8e11132 | |||
| accf15410c | |||
| 8739fd6e4f | |||
| 97118ff186 | |||
| 3943e7dc21 | |||
| 114b36f677 | |||
| 0904a24564 | |||
| 498cad48b1 | |||
| 19fa5045ee | |||
| 08a89a9861 | |||
| a4450d4558 | |||
| 0b7910c03c | |||
| 944d602077 | |||
| 596d28efbd | |||
| 6a58f35b14 | |||
| 7f925565a6 | |||
| f5ea3d93bf | |||
| 34a6b30b8f | |||
| 9bbad511cb | |||
| 7cef07931c | |||
| 37fa74892e | |||
| efbd64504d | |||
| 1920c4324f | |||
| 7e7a5f59d1 | |||
| 83f4305e36 | |||
| b45259d567 | |||
| 3f9f67375f | |||
| cb761e8e70 | |||
| 7c3314a504 | |||
| 690917e4e0 | |||
| 0ccf4926ff | |||
| 598a688214 | |||
| 0e7769ff84 | |||
| 6500bbaf36 |
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
# \todo Eliminate duplication when/if Dependabot supports YAML anchors
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
labels:
|
||||
- dependencies
|
||||
- actions
|
||||
schedule:
|
||||
day: sunday
|
||||
interval: weekly
|
||||
- package-ecosystem: gomod
|
||||
directory: /v2
|
||||
labels:
|
||||
- dependencies
|
||||
- go
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: friday
|
||||
@@ -4,16 +4,16 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.16.x, 1.17.x]
|
||||
go-version: [1.25.x]
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Updating packages database
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Install CLI Dependencies
|
||||
run: sudo apt-get install -y ldap-utils oathtool libpam0g-dev
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Fetch Go dependencies
|
||||
working-directory: ./v2
|
||||
run: go get -t -v ./...
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
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@v4
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-and-quality
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
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 }}
|
||||
@@ -0,0 +1,20 @@
|
||||
name: release-please
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
release-please:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: google-github-actions/release-please-action@v4
|
||||
with:
|
||||
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
||||
config-file: release-please-config.json
|
||||
manifest-file: .release-please-manifest.json
|
||||
+37
@@ -3,3 +3,40 @@ v2/certs/
|
||||
v2/bin
|
||||
v2/docker/assets/linux
|
||||
v2/gl.db
|
||||
v2/buildlogs
|
||||
.idea/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
### Go Patch ###
|
||||
/vendor/
|
||||
/Godeps/
|
||||
|
||||
# Various
|
||||
go.work.sum
|
||||
|
||||
# gomocks auto-generated
|
||||
**/mock_*.go
|
||||
|
||||
# Secrets & credentials
|
||||
*.pem
|
||||
*.key
|
||||
*.crt
|
||||
*.p12
|
||||
*.pfx
|
||||
*.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.tfvars
|
||||
credentials.json
|
||||
secrets.yaml
|
||||
secrets.yml
|
||||
|
||||
# Local GLAuth config that may contain bind passwords / secrets
|
||||
config/*.cfg
|
||||
!config/sample-simple.cfg
|
||||
!config/sample-*.cfg
|
||||
*.local.cfg
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
[submodule "v2/pkg/plugins/glauth-sqlite"]
|
||||
path = v2/pkg/plugins/glauth-sqlite
|
||||
url = git@github.com:glauth/glauth-sqlite
|
||||
[submodule "v2/pkg/plugins/glauth-mysql"]
|
||||
path = v2/pkg/plugins/glauth-mysql
|
||||
url = git@github.com:glauth/glauth-mysql
|
||||
[submodule "v2/pkg/plugins/glauth-postgres"]
|
||||
path = v2/pkg/plugins/glauth-postgres
|
||||
url = git@github.com:glauth/glauth-postgres
|
||||
[submodule "v2/pkg/plugins/glauth-pam"]
|
||||
path = v2/pkg/plugins/glauth-pam
|
||||
url = git@github.com:glauth/glauth-pam
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"v2": "2.5.3"
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# Contributing to GLAuth
|
||||
|
||||
Thanks for your interest in improving GLAuth! This guide covers how to set up your environment, submit changes, and work with the maintainers. Starting with GLAuth 2.4.0, we reworked and optimized the build workflow, and this contribution process reflects that.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Fork the repository and clone your fork locally.
|
||||
2. Review the [Building](https://glauth.github.io/docs/building.html) docs to compile GLAuth from source.
|
||||
3. Review the [Testing](https://glauth.github.io/docs/testing.html) docs to run the test suite before submitting changes.
|
||||
4. Create a topic branch for your work off of `master`.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
- Open an issue on the [GLAuth issue tracker](https://github.com/glauth/glauth/issues) for bugs and feature requests.
|
||||
- Include your GLAuth version, configuration (with secrets redacted), and clear steps to reproduce.
|
||||
|
||||
## Preparing to Commit
|
||||
|
||||
Format your code automatically using `gofmt -d ./` before committing. If you forget this step, a subsequent pull request may fail.
|
||||
|
||||
## Writing Commit Messages
|
||||
|
||||
Commit messages must be semantically correct. We follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
|
||||
|
||||
A commit message contains a prefix, followed by a short explanation. It may also contain "footer" annotations, but these are not mandatory (though appreciated).
|
||||
|
||||
Please don't bundle multiple unrelated items in the same commit (e.g., a new feature and a bug fix).
|
||||
|
||||
### Commit Prefix
|
||||
|
||||
A prefix is written imperatively, for example:
|
||||
|
||||
```
|
||||
feat(plugins)!: Change the plugins API to allow NoSQL databases.
|
||||
```
|
||||
|
||||
This is a new feature (`feat`), optionally scoped to an area of GLAuth (`(plugins)`), with `!` warning of a breaking change.
|
||||
|
||||
A more innocuous change:
|
||||
|
||||
```
|
||||
chore: Add more comments in all modules.
|
||||
```
|
||||
|
||||
Available prefixes: `feat`, `fix`, `chore`, `build`, `ci`, `perf`, `refactor`, `test`, `style`.
|
||||
|
||||
### Commit Footer Annotations
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
Reviewed-by: John Romero <john@romero.com>
|
||||
Tested-by: Joel Spolsky <joel@joelonsoftware.com>
|
||||
BREAKING-CHANGE: I broke the plugin
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Ned McClain & Ben Yanke
|
||||
Copyright (c) 2018 Ned McClain & Ben Yanke & Chris Ravenscroft
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# GLAuth: LDAP authentication server for developers
|
||||
Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ configurable backends.
|
||||
|
||||
[](https://gitter.im/glauth/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](hey)
|
||||
# GLAuth: LDAP authentication server for developers
|
||||
[](https://glauth.github.io/)
|
||||
[](https://github.com/fusion/kittendns)
|
||||
[](https://github.com/fusion/wing-vpn)
|
||||
|
||||
Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ configurable backends.
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
* Centrally manage accounts across your infrastructure
|
||||
* Centrally manage SSH keys, Linux accounts, and passwords for cloud servers.
|
||||
@@ -19,7 +19,23 @@ Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ co
|
||||
|
||||
Use it to centralize account management across your Linux servers, your OSX machines, and your support applications (Jenkins, Apache/Nginx, Graylog2, and many more!).
|
||||
|
||||
<hr>
|
||||
### Contributing
|
||||
- Please base all Pull Requests on [dev](https://github.com/glauth/glauth/tree/dev), not master.
|
||||
- Format your code automatically using `gofmt -d ./` before committing
|
||||
|
||||
### Quickstart
|
||||
This quickstart is a great way to try out GLAuth in a non-production environment. *Be warned that you should take the extra steps to setup SSL (TLS) for production use!*
|
||||
|
||||
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/v2/sample-simple.cfg).
|
||||
3. Start the GLAuth server, referencing the path to the desired config file with `-c`.
|
||||
- `./glauth64 -c sample-simple.cfg`
|
||||
4. Test with traditional LDAP tools
|
||||
- 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.
|
||||
|
||||
### Documentation
|
||||
|
||||
@@ -94,4 +110,3 @@ For advanced users, GLAuth supports pluggable backends. Currently, it can use a
|
||||
# Stargazers over time
|
||||
|
||||
[](https://starchart.cc/glauth/glauth)
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"packages": {
|
||||
"v2": {
|
||||
"release-type": "go",
|
||||
"package-name": "GLAuth",
|
||||
"pull-request-title-pattern": "ci: release ${version}",
|
||||
"extra-files": [
|
||||
"internal/version/const.go"
|
||||
]
|
||||
}
|
||||
},
|
||||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
# Changelog
|
||||
|
||||
## [2.5.3](https://github.com/glauth/glauth/compare/GLAuth-v2.5.2...GLAuth-v2.5.3) (2026-09-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove repetitions, reduce waitForPort scope, restore arbitrary port ([5bdeb51](https://github.com/glauth/glauth/commit/5bdeb5118b0689d92c4a0d12aea63d8b8d153b71))
|
||||
|
||||
## [2.5.2](https://github.com/glauth/glauth/compare/GLAuth-v2.5.1...GLAuth-v2.5.2) (2026-07-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* check plugin compatibility at release time ([700745e](https://github.com/glauth/glauth/commit/700745e640c5371361a58ef3c90245a4637c5224))
|
||||
|
||||
## [2.5.1](https://github.com/glauth/glauth/compare/GLAuth-v2.5.0...GLAuth-v2.5.1) (2026-07-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* reject disabled users on all backends in Bind ([#470](https://github.com/glauth/glauth/issues/470)) ([fb65713](https://github.com/glauth/glauth/commit/fb65713fbdfd626cb033675fb42724a5fb3d06ff))
|
||||
* team atlanta arbitrary password ([#471](https://github.com/glauth/glauth/issues/471)) ([cb121cd](https://github.com/glauth/glauth/commit/cb121cdc2c9b831abdf7831835ec30ead2536a7a))
|
||||
|
||||
## [2.5.0](https://github.com/glauth/glauth/compare/GLAuth-v2.5.0...GLAuth-v2.5.0) (2026-04-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow tracing configuration via main config ([f692394](https://github.com/glauth/glauth/commit/f692394942aa0f93a1aa1572331fd1c3e3553156))
|
||||
* alt container build registry ([f8dee8d](https://github.com/glauth/glauth/commit/f8dee8da879232f5c6ae2a2a19565562c7d0d9fd))
|
||||
* embedded plugins ([c0ed908](https://github.com/glauth/glauth/commit/c0ed90822fd4d2ee7104a1979d423a0c93d758ac))
|
||||
* introduce context for otlp spans into handler pkg ([d05630f](https://github.com/glauth/glauth/commit/d05630f66b80a776fd406782ad1fde5c6c66eac6))
|
||||
* introduce context for otlp spans into plugins pkg ([46e49b6](https://github.com/glauth/glauth/commit/46e49b6976318a9f3670b88bdcb3411dfac0a17c))
|
||||
* introduce legacy behavior, based on past versions ([#318](https://github.com/glauth/glauth/issues/318)) ([34a6b30](https://github.com/glauth/glauth/commit/34a6b30b8fe69c90cab09eb85890ffdfe8fc80fd))
|
||||
* introduce otelsql ([2ca5312](https://github.com/glauth/glauth/commit/2ca53126965aa7d42b23aee15f750df12822d454))
|
||||
* introduce otlp tracer ([0cf0403](https://github.com/glauth/glauth/commit/0cf04037a2a7b38c8ed7af2451b115f48c5427b5))
|
||||
* proxying ldap paging compat ([#422](https://github.com/glauth/glauth/issues/422)) ([8476506](https://github.com/glauth/glauth/commit/8476506b8c51e993eb90c73d612f69fec5350b89))
|
||||
* starttls and ldaps now matching features ([#458](https://github.com/glauth/glauth/issues/458)) ([d68c78a](https://github.com/glauth/glauth/commit/d68c78a18b150679038e5ce549b6b227c31518d7))
|
||||
* wire up basic tracer ([1c2b23c](https://github.com/glauth/glauth/commit/1c2b23c00ff85b83a6d2e4bf4a9a68081aaf2777))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 246 and [#252](https://github.com/glauth/glauth/issues/252) ([ceefc66](https://github.com/glauth/glauth/commit/ceefc66dbba9ae054958269825de31d22d30e262))
|
||||
* config watcher now updates whenf ile added to dir ([#424](https://github.com/glauth/glauth/issues/424)) ([a90f37b](https://github.com/glauth/glauth/commit/a90f37b23b76af22c700392d68bf227b7aee1894))
|
||||
* Credentials leaked on INFO log level ([#451](https://github.com/glauth/glauth/issues/451)) ([645dbb2](https://github.com/glauth/glauth/commit/645dbb226683ae57f3d5382e21ea42444e940752))
|
||||
* drop vendored toml ([24455e3](https://github.com/glauth/glauth/commit/24455e39889716ce7ae1e7e8a7dacfa7d4c96080))
|
||||
* formatting ([c90cbfe](https://github.com/glauth/glauth/commit/c90cbfe5fb090fc19a55d64e01cd0e31c38bf2f0))
|
||||
* go test not checking otp within allowed basedn ([#403](https://github.com/glauth/glauth/issues/403)) ([ed52a91](https://github.com/glauth/glauth/commit/ed52a91ec4117ff58fe606a1d8ba10786501a1e5))
|
||||
* move all toml parsing into a new internal package, drop the mappings in favour of toml.Primitive decoding ([3ba8e11](https://github.com/glauth/glauth/commit/3ba8e1113217be647d240261322453d213ea7da4))
|
||||
* patch group name fetches for the rest of handlers ([#450](https://github.com/glauth/glauth/issues/450)) ([b498736](https://github.com/glauth/glauth/commit/b498736cfdd0ee6f1585d682a5c54c9d927b8dbb))
|
||||
* remove spinlock in monitoring code ([#405](https://github.com/glauth/glauth/issues/405)) ([a2c151b](https://github.com/glauth/glauth/commit/a2c151b0d025332462369a846a51ef4deff5332b))
|
||||
* removed config setup from main, reshoring log configuration ([5fe8aca](https://github.com/glauth/glauth/commit/5fe8aca852bdfe7e375ea99b87074bae35fc3407))
|
||||
* update ldap library to fix [#389](https://github.com/glauth/glauth/issues/389) ([#430](https://github.com/glauth/glauth/issues/430)) ([a5ecc26](https://github.com/glauth/glauth/commit/a5ecc26e064cea9e4dab3dd234ebd5d5eb06be0e))
|
||||
* update tracing code to work with breaking otlp 1.20 changes ([1a37396](https://github.com/glauth/glauth/commit/1a3739610997b58100040d0c8a405596fccc8e23))
|
||||
* upgrade to use BurntSushi/toml ([f9addbc](https://github.com/glauth/glauth/commit/f9addbc2c5b13ccc2779dbabc4c55bc8706f53d6))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* force release 2.5.0 ([ff7a9e6](https://github.com/glauth/glauth/commit/ff7a9e6215857e0dd3450504b7b063409ccfc374))
|
||||
* release 2.3.1 ([0bf3d4a](https://github.com/glauth/glauth/commit/0bf3d4a82a8451e7bbda74e3730345aab5a855a7))
|
||||
|
||||
## [2.4.0](https://github.com/glauth/glauth/compare/v2.3.2...v2.4.0) (2024-08-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* proxying ldap paging compat ([#422](https://github.com/glauth/glauth/issues/422)) ([8476506](https://github.com/glauth/glauth/commit/8476506b8c51e993eb90c73d612f69fec5350b89))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* config watcher now updates whenf ile added to dir ([#424](https://github.com/glauth/glauth/issues/424)) ([a90f37b](https://github.com/glauth/glauth/commit/a90f37b23b76af22c700392d68bf227b7aee1894))
|
||||
* update ldap library to fix [#389](https://github.com/glauth/glauth/issues/389) ([#430](https://github.com/glauth/glauth/issues/430)) ([a5ecc26](https://github.com/glauth/glauth/commit/a5ecc26e064cea9e4dab3dd234ebd5d5eb06be0e))
|
||||
|
||||
## [2.3.2](https://github.com/glauth/glauth/compare/v2.3.1...v2.3.2) (2024-02-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove spinlock in monitoring code ([#405](https://github.com/glauth/glauth/issues/405)) ([a2c151b](https://github.com/glauth/glauth/commit/a2c151b0d025332462369a846a51ef4deff5332b))
|
||||
|
||||
## [2.3.1](https://github.com/glauth/glauth/compare/v2.2.1...v2.3.1) (2024-02-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow tracing configuration via main config ([f692394](https://github.com/glauth/glauth/commit/f692394942aa0f93a1aa1572331fd1c3e3553156))
|
||||
* introduce context for otlp spans into handler pkg ([d05630f](https://github.com/glauth/glauth/commit/d05630f66b80a776fd406782ad1fde5c6c66eac6))
|
||||
* introduce context for otlp spans into plugins pkg ([46e49b6](https://github.com/glauth/glauth/commit/46e49b6976318a9f3670b88bdcb3411dfac0a17c))
|
||||
* introduce otelsql ([2ca5312](https://github.com/glauth/glauth/commit/2ca53126965aa7d42b23aee15f750df12822d454))
|
||||
* introduce otlp tracer ([0cf0403](https://github.com/glauth/glauth/commit/0cf04037a2a7b38c8ed7af2451b115f48c5427b5))
|
||||
* wire up basic tracer ([1c2b23c](https://github.com/glauth/glauth/commit/1c2b23c00ff85b83a6d2e4bf4a9a68081aaf2777))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* drop vendored toml ([24455e3](https://github.com/glauth/glauth/commit/24455e39889716ce7ae1e7e8a7dacfa7d4c96080))
|
||||
* formatting ([c90cbfe](https://github.com/glauth/glauth/commit/c90cbfe5fb090fc19a55d64e01cd0e31c38bf2f0))
|
||||
* go test not checking otp within allowed basedn ([#403](https://github.com/glauth/glauth/issues/403)) ([ed52a91](https://github.com/glauth/glauth/commit/ed52a91ec4117ff58fe606a1d8ba10786501a1e5))
|
||||
* move all toml parsing into a new internal package, drop the mappings in favour of toml.Primitive decoding ([3ba8e11](https://github.com/glauth/glauth/commit/3ba8e1113217be647d240261322453d213ea7da4))
|
||||
* removed config setup from main, reshoring log configuration ([5fe8aca](https://github.com/glauth/glauth/commit/5fe8aca852bdfe7e375ea99b87074bae35fc3407))
|
||||
* update tracing code to work with breaking otlp 1.20 changes ([1a37396](https://github.com/glauth/glauth/commit/1a3739610997b58100040d0c8a405596fccc8e23))
|
||||
* upgrade to use BurntSushi/toml ([f9addbc](https://github.com/glauth/glauth/commit/f9addbc2c5b13ccc2779dbabc4c55bc8706f53d6))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 2.3.1 ([0bf3d4a](https://github.com/glauth/glauth/commit/0bf3d4a82a8451e7bbda74e3730345aab5a855a7))
|
||||
|
||||
## [2.3.0](https://github.com/glauth/glauth/compare/v2.2.0...v2.3.0) (2023-10-02)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **plugins:** Rename the groups table to ldapgroups ([#326](https://github.com/glauth/glauth/issues/326))
|
||||
|
||||
### Features
|
||||
|
||||
* Update migration code to support table names ([#339](https://github.com/glauth/glauth/issues/339)) ([349431c](https://github.com/glauth/glauth/commit/349431c6caa0388d17ab987621eb9be5f019155e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **plugins:** Rename the groups table to ldapgroups ([#326](https://github.com/glauth/glauth/issues/326)) ([675b236](https://github.com/glauth/glauth/commit/675b236328a21a65daa7876a1a3c6900b85b1964))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 2.3.0 ([0c11325](https://github.com/glauth/glauth/commit/0c11325a2482d5067c805a4c7ed948a5e337b8f8))
|
||||
+244
-61
@@ -1,4 +1,8 @@
|
||||
VERSION=$(shell bin/linuxamd64/glauth --version)
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
MAKEFLAGS += --no-print-directory
|
||||
|
||||
VERSION=$(shell if [ -x bin/linuxamd64/glauth ]; then bin/linuxamd64/glauth --version; else bin/glauth-linux-amd64 --version; fi)
|
||||
|
||||
GIT_COMMIT=$(shell git rev-list -1 HEAD )
|
||||
BUILD_TIME=$(shell date -u +%Y%m%d_%H%M%SZ)
|
||||
@@ -14,42 +18,89 @@ GIT_IS_TAG_COMMIT=$(shell git describe --abbrev=0 --tags > /dev/null 2> /dev/nul
|
||||
GIT_BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
# Build variables
|
||||
BUILD_VARS=-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}"
|
||||
BUILD_VARS?=-s -w
|
||||
BUILD_FILES=.
|
||||
TRIM_FLAGS>?=-trimpath
|
||||
BUILD_TAGS?=noembed
|
||||
|
||||
# Targets
|
||||
MAIN_TARGETS=linux/amd64,linux/386,linux/arm64,linux/arm-7,darwin/amd64,darwin/arm64,windows/amd64,windows/386
|
||||
PLUGIN_TARGETS=linux/amd64,linux/386,linux/arm64,linux/arm-7,darwin/amd64,darwin/arm64
|
||||
MAIN_TARGETS?=linux/amd64,linux/386,linux/arm64,linux/arm-7,linux/riscv64,darwin/amd64,darwin/arm64,windows/amd64,windows/386
|
||||
PLUGIN_TARGETS?=linux/amd64,linux/386,linux/arm64,linux/arm-7,linux/riscv64,darwin/amd64,darwin/arm64
|
||||
EMBED_TARGETS?=windows/amd64
|
||||
|
||||
# For release process
|
||||
GO_RELEASE_V=$(shell go version | { read _ _ v _; echo $${v#go}; })
|
||||
|
||||
# Build
|
||||
GOOS?=linux
|
||||
GOARCH?=amd64
|
||||
|
||||
# Plugins
|
||||
include pkg/plugins/*/Makefile
|
||||
-include pkg/plugins/*/Makefile
|
||||
|
||||
#####################
|
||||
# High level commands
|
||||
#####################
|
||||
|
||||
# Build and run - used for development
|
||||
#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
|
||||
|
||||
# Run the integration test on linuxamd64 (eventually allow the binary to be set)
|
||||
test: runtest
|
||||
mocks:
|
||||
go install go.uber.org/mock/mockgen@v0.3.0
|
||||
# generate gomocks
|
||||
go generate ./...
|
||||
.PHONY: mocks
|
||||
|
||||
# Run build process for all binaries
|
||||
vet:
|
||||
-go vet ./...
|
||||
.PHONY: vet
|
||||
#test: @ runs package, root integration, and shell integration tests
|
||||
test: mocks vet
|
||||
go test -v -cover -coverprofile coverage.out ./pkg/... ./internal/... .
|
||||
$(MAKE) runtest
|
||||
#all: @ run build process for all binaries
|
||||
all: setup binaries verify
|
||||
|
||||
# Run build process for only linuxamd64
|
||||
#fast: @ run build process for only linuxamd64
|
||||
fast: setup linuxamd64
|
||||
|
||||
# list of binary formats to build
|
||||
binaries: linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64
|
||||
#fastdebug: @ run build process for only linuxamd64 in debug mode
|
||||
fastdebug: setup linuxamd64debug
|
||||
|
||||
# Setup commands to always run
|
||||
#binaries: @ list of binary formats to build
|
||||
binaries: linux386 linuxamd64 linuxarm linuxarm64 linuxriscv64 darwinamd64 darwinarm64 win386 winamd64
|
||||
|
||||
#setup: @ setup commands to always run
|
||||
setup: getdeps format
|
||||
|
||||
#####################
|
||||
# Subcommands
|
||||
#####################
|
||||
# @echo \- removing any changes and pulling latest master && \
|
||||
# git reset --hard HEAD >/dev/null 2>&1 && \
|
||||
# git checkout master >/dev/null 2>&1 && \
|
||||
# git pull >/dev/null 2>&1 && \
|
||||
|
||||
prepbuild:
|
||||
echo \- taking care of ignore files && \
|
||||
sudo rm -rf bin/* >/dev/null && \
|
||||
echo \- cleaning up plugins remnants && \
|
||||
make forget-plugins && \
|
||||
make remember-plugins
|
||||
|
||||
forget-embed:
|
||||
@if [ -f pkg/server/embed_sqlite.go ]; then \
|
||||
mv pkg/server/embed_sqlite.go /tmp/; \
|
||||
fi
|
||||
|
||||
remember-embed:
|
||||
@if [ ! -f pkg/server/embed_sqlite.go ]; then \
|
||||
mv /tmp/embed_sqlite.go pkg/server/; \
|
||||
fi
|
||||
|
||||
# Run integration test
|
||||
runtest:
|
||||
@@ -63,97 +114,225 @@ updatetest:
|
||||
./scripts/ci/integration-test.sh
|
||||
|
||||
format:
|
||||
go fmt
|
||||
go fmt ./...
|
||||
|
||||
devrun:
|
||||
go run ${BUILD_FILES} -c sample-simple.cfg
|
||||
|
||||
#testplugin: @ start GLAuth with a dynamically loaded plugin and stop it cleanly
|
||||
GLAUTH_BIN?=bin/glauth-linux-amd64
|
||||
PLUGIN_BIN?=bin/sqlite-linux-amd64.so
|
||||
PLUGIN_CONFIG?=pkg/plugins/glauth-sqlite/sample-database.cfg
|
||||
PLUGIN_LDAP_PORT?=13893
|
||||
PLUGIN_API_PORT?=15555
|
||||
|
||||
testplugin:
|
||||
@set -e; \
|
||||
test -x "$(GLAUTH_BIN)"; \
|
||||
test -r "$(PLUGIN_BIN)"; \
|
||||
test -f "$(PLUGIN_CONFIG)"; \
|
||||
tmpdir=$$(mktemp -d); \
|
||||
trap 'rm -rf "$$tmpdir"' EXIT; \
|
||||
sed \
|
||||
-e 's#^[[:space:]]*plugin = .*# plugin = "$(abspath $(PLUGIN_BIN))"#' \
|
||||
-e "s#^[[:space:]]*database = .*# database = \"$$tmpdir/gl.db\"#" \
|
||||
-e 's#listen = "0.0.0.0:3893"#listen = "127.0.0.1:$(PLUGIN_LDAP_PORT)"#' \
|
||||
-e 's#listen = "0.0.0.0:5555"#listen = "127.0.0.1:$(PLUGIN_API_PORT)"#' \
|
||||
"$(PLUGIN_CONFIG)" > "$$tmpdir/config.cfg"; \
|
||||
"$(GLAUTH_BIN)" -c "$$tmpdir/config.cfg" > "$$tmpdir/glauth.log" 2>&1 & \
|
||||
pid=$$!; \
|
||||
sleep 2; \
|
||||
if ! kill -0 "$$pid" 2>/dev/null; then \
|
||||
set +e; wait "$$pid"; status=$$?; set -e; \
|
||||
cat "$$tmpdir/glauth.log"; \
|
||||
exit $$status; \
|
||||
fi; \
|
||||
kill -TERM "$$pid"; \
|
||||
set +e; wait "$$pid"; status=$$?; set -e; \
|
||||
cat "$$tmpdir/glauth.log"; \
|
||||
exit $$status
|
||||
|
||||
mkbindir:
|
||||
@echo "create directory bin/$(GOOS)$(GOARCH)"
|
||||
@mkdir -p bin/$(GOOS)$(GOARCH)
|
||||
.PHONY: mkbindir
|
||||
|
||||
build: mkbindir
|
||||
@go build ${TRIM_FLAGS} -tags ${BUILD_TAGS} -ldflags "${BUILD_VARS}" -o bin/$(GOOS)$(GOARCH)/glauth -buildvcs .
|
||||
$(MAKE) sha256
|
||||
.PHONY: build
|
||||
|
||||
builddebug: mkbindir
|
||||
@go build ${TRIM_FLAGS} -tags ${BUILD_TAGS} -gcflags="all=-N -l" -o bin/$(GOOS)$(GOARCH)/glauth -buildvcs .
|
||||
$(MAKE) sha256
|
||||
.PHONY: builddebug
|
||||
|
||||
sha256:
|
||||
@sha256sum bin/$(GOOS)$(GOARCH)/glauth > bin/$(GOOS)$(GOARCH)/glauth.sha256
|
||||
.PHONY: sha256
|
||||
|
||||
linuxamd64debug:
|
||||
GOOS=linux GOARCH=amd64 $(MAKE) builddebug
|
||||
|
||||
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
|
||||
GOOS=linux GOARCH=386 $(MAKE) build
|
||||
|
||||
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
|
||||
GOOS=linux GOARCH=amd64 $(MAKE) build
|
||||
|
||||
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
|
||||
GOOS=linux GOARCH=arm $(MAKE) build
|
||||
|
||||
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
|
||||
GOOS=linux GOARCH=arm64 $(MAKE) build
|
||||
|
||||
linuxriscv64:
|
||||
GOOS=linux GOARCH=riscv64 $(MAKE) build
|
||||
|
||||
testing:
|
||||
mkdir -p pkg/embed && \
|
||||
cat pkg/plugins/glauth-sqlite/sqlite.go | sed -e 's/func main() {}//' -e 's/^package main/package embed/' > pkg/embed/sqlite.go && \
|
||||
go mod tidy && \
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 BUILD_TAGS=embedsqlite $(MAKE) build && \
|
||||
rm -rf pkg/embed
|
||||
|
||||
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
|
||||
GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 $(MAKE) build
|
||||
|
||||
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
|
||||
GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 $(MAKE) build
|
||||
|
||||
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
|
||||
GOOS=windows GOARCH=386 $(MAKE) build
|
||||
|
||||
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
|
||||
GOOS=windows GOARCH=amd64 $(MAKE) build
|
||||
|
||||
verify:
|
||||
@for binary in linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64; do cd bin/$$binary && sha256sum glauth.sha256 -c && cd ../..; done
|
||||
@for binary in linux386 linuxamd64 linuxarm linuxarm64 linuxriscv64 darwinamd64 darwinarm64 win386 winamd64; do cd bin/$$binary && sha256sum glauth.sha256 -c && cd ../..; done
|
||||
|
||||
pull-plugin:
|
||||
@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-plugin-dependencies:
|
||||
(for pkg in $$(cat $$M/go.mod | awk '/^require [a-z]/{print $$2"@"$$3} /^\)/{rblock=0} rblock{if($$4!="indirect" && $$1!~"glauth"){print $$1"@"$$2}} /^require \(/{rblock=1}'); do if [ "$$(grep $${pkg%@*} go.mod 2>/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
|
||||
#prepare-plugins-buid: @ pull plugins dependencies for local building
|
||||
prepare-plugins-build:
|
||||
@M=pkg/plugins/glauth-sqlite make pull-plugin-dependencies && \
|
||||
M=pkg/plugins/glauth-mysql make pull-plugin-dependencies && \
|
||||
M=pkg/plugins/glauth-postgres make pull-plugin-dependencies && \
|
||||
M=pkg/plugins/glauth-pam make pull-plugin-dependencies
|
||||
|
||||
forget-plugin:
|
||||
rm -rf $M ../.git/modules/v2/$M && \
|
||||
git config --remove-section submodule.v2/$M && \
|
||||
git rm --cache $M && \
|
||||
go mod tidy
|
||||
rm -rf $M ../.git/modules/v2/$M; \
|
||||
git config --remove-section submodule.v2/$M; \
|
||||
git rm --cache $M; \
|
||||
go mod tidy; \
|
||||
true
|
||||
|
||||
#forget-plugin: @ remove plugins, restoring original file structure
|
||||
forget-plugins:
|
||||
for pkg in pkg/plugins/*/go.mod; do\
|
||||
P=$$(echo $$pkg | cut -d'/' -f 3) M=$$(dirname $$pkg) make forget-plugin;\
|
||||
done
|
||||
for pkg in pkg/plugins/*; do \
|
||||
if [ -d "$$pkg" ]; then \
|
||||
P=$$(echo $$pkg | cut -d'/' -f 3) M=$$pkg make forget-plugin >/dev/null 2>&1;\
|
||||
fi; \
|
||||
done; \
|
||||
rm -rf buildlogs
|
||||
|
||||
releasemain:
|
||||
@xgo -v -ldflags="${BUILD_VARS}" -trimpath -go 1.18.1 -out glauth -dest bin -buildvcs=false --targets="${MAIN_TARGETS}" .
|
||||
remember-plugins:
|
||||
cd ../ && \
|
||||
for module in $$(cat .gitmodules | awk '/path =/{P=$$3} /url =/{print $$3 "," P}'); do \
|
||||
git submodule add $${module%%,*} $${module#*,}; \
|
||||
done; \
|
||||
git submodule update --init --recursive
|
||||
|
||||
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)
|
||||
_buildmain:
|
||||
@xgo -v -ldflags="${BUILD_VARS}" -trimpath -go ${GO_RELEASE_V} -out glauth -dest bin -buildvcs=false --targets="${MAIN_TARGETS}" .
|
||||
|
||||
releaseplugins:
|
||||
@for pkg in pkg/plugins/*/go.mod; do\
|
||||
P=$$(echo $$pkg | cut -d'/' -f 3) && make release-$$P;\
|
||||
done
|
||||
#buildmain: @ build main binaries for distribution
|
||||
buildmain: prepbuild _buildmain
|
||||
|
||||
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 && \
|
||||
buildsingleembed:
|
||||
git checkout -b build_embeds && \
|
||||
mkdir -p pkg/embed && \
|
||||
cat pkg/plugins/$P/$M | sed -e 's/func main() {}//' -e 's/^package main/package embed/' > pkg/embed/$M && \
|
||||
go mod tidy && \
|
||||
git add pkg/embed/$M go.mod go.sum && \
|
||||
git commit -m "build cruft" && \
|
||||
git push -u origin build_embeds && \
|
||||
xgo -v -ldflags="${BUILD_VARS}" --tags=$T -trimpath -go ${GO_RELEASE_V} -out glauth -dest bin -buildvcs=false --targets="${EMBED_TARGETS}" . && \
|
||||
rm -rf pkg/embed && \
|
||||
git checkout master && \
|
||||
git push -d origin build_embeds && \
|
||||
git branch -D build_embeds
|
||||
|
||||
_buildmainembeds:
|
||||
P=glauth-sqlite M=sqlite.go T=embedsqlite make buildsingleembed
|
||||
|
||||
#buildmainembeds: @ build main binaries for distribution, including embedded plugins
|
||||
buildmainembeds: prepbuild _buildmainembeds
|
||||
|
||||
buildplugin:
|
||||
ifeq ($P,pam)
|
||||
xgo -v -ldflags="${BUILD_VARS}" -trimpath -go ${GO_RELEASE_V} -out $P -dest bin -buildvcs=false -buildmode=plugin --targets="${PLUGIN_TARGETS}" --pkg $M/$P.go --bringupcmd 'apt update && apt install -y libpam0g-dev' .
|
||||
else
|
||||
xgo -v -ldflags="${BUILD_VARS}" -trimpath -go ${GO_RELEASE_V} -out $P -dest bin -buildvcs=false -buildmode=plugin --targets="${PLUGIN_TARGETS}" --pkg $M/$P.go .
|
||||
endif
|
||||
(cd bin && for lib in $$(ls $$P-* | grep -v \.so$$); do sudo mv $$lib $$lib.so; done);
|
||||
|
||||
_buildplugins: prepare-plugins-build
|
||||
for pkg in pkg/plugins/*; do \
|
||||
if [ -d "$$pkg" ]; then \
|
||||
M=pkg/plugins/$$(echo $$pkg | cut -d'/' -f 3) && P=$$(echo $$M | grep -oP 'glauth-\K\w+') && M=$$M P=$$P make buildplugin;\
|
||||
fi; \
|
||||
done; \
|
||||
|
||||
#buildplugins: @ build base plugins for distribution
|
||||
buildplugins: forget-embed _buildplugins remember-embed
|
||||
|
||||
_checkdockerauth:
|
||||
@if ! grep -q "index.docker.io" $(HOME)/.docker/config.json; then \
|
||||
echo "❌ Not logged in to Docker Hub (index.docker.io not found in config)."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
if ! grep -q "ghcr.io" $(HOME)/.docker/config.json; then \
|
||||
echo "❌ Not logged in to GitHub Hub (ghcr.io not found in config)."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
builddockermain:
|
||||
$(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 docker/assets/linux/riscv64 && \
|
||||
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
|
||||
cp -f bin/glauth-linux-riscv64 docker/assets/linux/riscv64/glauth && \
|
||||
docker buildx build --tag ghcr.io/$$REPO/glauth:$$TAG -t ghcr.io/$$REPO/glauth:latest -f docker/Dockerfile-standalone --platform linux/amd64,linux/arm64,linux/arm/v7,linux/riscv64 --push docker && \
|
||||
docker buildx build --tag $$REPO/glauth:$$TAG -t $$REPO/glauth:latest -f docker/Dockerfile-standalone --platform linux/amd64,linux/arm64,linux/arm/v7,linux/riscv64 --push docker
|
||||
|
||||
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 && \
|
||||
builddockerplugins:
|
||||
$(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 docker/assets/linux/riscv64 && \
|
||||
cp -f bin/sqlite-linux-amd64.so docker/assets/linux/amd64/sqlite.so && \
|
||||
cp -f bin/sqlite-linux-arm64.so docker/assets/linux/arm64/sqlite.so && \
|
||||
cp -f bin/sqlite-linux-arm-7.so docker/assets/linux/arm/v7/sqlite.so && \
|
||||
cp -f bin/sqlite-linux-riscv64.so docker/assets/linux/riscv64/sqlite.so && \
|
||||
cp -f bin/mysql-linux-amd64.so docker/assets/linux/amd64/mysql.so && \
|
||||
cp -f bin/mysql-linux-arm64.so docker/assets/linux/arm64/mysql.so && \
|
||||
cp -f bin/mysql-linux-arm-7.so docker/assets/linux/arm/v7/mysql.so && \
|
||||
cp -f bin/mysql-linux-riscv64.so docker/assets/linux/riscv64/mysql.so && \
|
||||
cp -f bin/postgres-linux-amd64.so docker/assets/linux/amd64/postgres.so && \
|
||||
cp -f bin/postgres-linux-arm64.so docker/assets/linux/arm64/postgres.so && \
|
||||
cp -f bin/postgres-linux-arm-7.so docker/assets/linux/arm/v7/postgres.so && \
|
||||
docker buildx build --tag $$REPO/glauth-plugins:$$TAG -t $$REPO/glauth-plugins:latest -f docker/Dockerfile-plugins --platform linux/amd64,linux/arm64,linux/arm/v7 --push docker
|
||||
cp -f bin/postgres-linux-riscv64.so docker/assets/linux/riscv64/postgres.so && \
|
||||
docker buildx build --tag ghcr.io/$$REPO/glauth-plugins:$$TAG -t ghcr.io/$$REPO/glauth-plugins:latest -f docker/Dockerfile-plugins --platform linux/amd64,linux/arm64,linux/arm/v7,linux/riscv64 --push docker && \
|
||||
docker buildx build --tag $$REPO/glauth-plugins:$$TAG -t $$REPO/glauth-plugins:latest -f docker/Dockerfile-plugins --platform linux/amd64,linux/arm64,linux/arm/v7,linux/riscv64 --push docker
|
||||
|
||||
releasedocker: releasedockermain releasedockerplugins
|
||||
builddocker: _checkdockerauth builddockermain builddockerplugins
|
||||
|
||||
buildeverything: prepbuild forget-embed _buildplugins _buildmain testplugin remember-embed _buildmainembeds builddocker
|
||||
|
||||
#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..." && \
|
||||
@@ -193,4 +372,8 @@ testdocker:
|
||||
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
|
||||
.PHONY: all \
|
||||
prepbuild run test fast binaries setup getdeps runtest updatetest format devrun \
|
||||
linux386 linuxamd64 linuxarm linuxarm64 linuxriscv64 darwinamd64 darwinarm64 win386 winamd64 verify \
|
||||
pull-plugin pull-base-plugins forget-plugin forget-plugins buildmain buildplugin build \
|
||||
buildplugins builddockermain builddockerplugins builddocker testdocker testplugin buildeverything
|
||||
|
||||
@@ -10,8 +10,17 @@ 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>"
|
||||
FROM gcr.io/distroless/base-debian13 as run
|
||||
|
||||
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>" \
|
||||
org.opencontainers.image.title="GLauth" \
|
||||
org.opencontainers.image.description="A simple LDAP server for development, home use, or CI pipelines" \
|
||||
org.opencontainers.image.authors="Ben Yanke, Jörn Friedrich Dreyer, Chris F Ravenscroft" \
|
||||
org.opencontainers.image.source="https://github.com/glauth/glauth" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.version="latest" \
|
||||
org.opencontainers.image.url="https://github.com/glauth/glauth"
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Copy docker specific scripts from build container
|
||||
|
||||
@@ -10,8 +10,17 @@ 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>"
|
||||
FROM gcr.io/distroless/base-debian13 as run
|
||||
|
||||
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>" \
|
||||
org.opencontainers.image.title="GLauth" \
|
||||
org.opencontainers.image.description="A simple LDAP server for development, home use, or CI pipelines" \
|
||||
org.opencontainers.image.authors="Ben Yanke, Jörn Friedrich Dreyer, Chris F Ravenscroft" \
|
||||
org.opencontainers.image.source="https://github.com/glauth/glauth" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.version="latest" \
|
||||
org.opencontainers.image.url="https://github.com/glauth/glauth"
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Copy docker specific scripts from build container
|
||||
|
||||
Binary file not shown.
+109
-642
@@ -1,40 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/arl/statsviz"
|
||||
docopt "github.com/docopt/docopt-go"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/glauth/glauth/v2/internal/monitoring"
|
||||
_tls "github.com/glauth/glauth/v2/internal/tls"
|
||||
"github.com/glauth/glauth/v2/internal/toml"
|
||||
"github.com/glauth/glauth/v2/internal/tracing"
|
||||
"github.com/glauth/glauth/v2/internal/version"
|
||||
"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"
|
||||
"io/ioutil"
|
||||
"log/syslog"
|
||||
"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
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
const programName = "glauth"
|
||||
|
||||
@@ -50,6 +43,7 @@ Options:
|
||||
-K <aws_key_id> AWS Key ID.
|
||||
-S <aws_secret_key> AWS Secret Key.
|
||||
-r <aws_region> AWS Region [default: us-east-1].
|
||||
--aws_endpoint_url <url> Custom S3 endpoint.
|
||||
--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.
|
||||
@@ -67,49 +61,8 @@ var (
|
||||
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)
|
||||
@@ -121,15 +74,35 @@ func main() {
|
||||
checkConfig = true
|
||||
}
|
||||
}
|
||||
if err := doConfig(checkConfig); err != nil {
|
||||
|
||||
cfg, err := toml.NewConfig(checkConfig, getConfigLocation(), args)
|
||||
|
||||
if 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
|
||||
}
|
||||
|
||||
if err := copier.Copy(activeConfig, cfg); err != nil {
|
||||
log.Info().Err(err).Msg("Could not save reloaded config. Holding on to old config")
|
||||
}
|
||||
|
||||
log = logging.InitLogging(activeConfig.Debug, activeConfig.Syslog, activeConfig.StructuredLog)
|
||||
|
||||
if !checkConfig {
|
||||
if cfg.Debug {
|
||||
log.Info().Msg("Debugging enabled")
|
||||
}
|
||||
if cfg.Syslog {
|
||||
log.Info().Msg("Syslog enabled")
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().Msg("AP start")
|
||||
|
||||
startService()
|
||||
@@ -137,7 +110,7 @@ func main() {
|
||||
|
||||
func startService() {
|
||||
// stats
|
||||
stats.General.Set("version", stats.Stringer(LastGitTag))
|
||||
stats.General.Set("version", stats.Stringer(version.Version))
|
||||
|
||||
// web API
|
||||
if activeConfig.API.Enabled {
|
||||
@@ -157,46 +130,87 @@ func startService() {
|
||||
)
|
||||
}
|
||||
|
||||
monitor := monitoring.NewMonitor(&log)
|
||||
tracer := tracing.NewTracer(
|
||||
tracing.NewConfig(
|
||||
activeConfig.Tracing.Enabled,
|
||||
activeConfig.Tracing.GRPCEndpoint,
|
||||
activeConfig.Tracing.HTTPEndpoint,
|
||||
&log,
|
||||
),
|
||||
)
|
||||
|
||||
startConfigWatcher()
|
||||
|
||||
var err error
|
||||
var starttlsConfig *tls.Config
|
||||
if c := activeConfig.LDAP; c.Enabled && c.TLS {
|
||||
// TODO check if tls params are string or bytes and change config accordingly
|
||||
starttlsConfig, err = _tls.MakeTLS([]byte(c.TLSCert), []byte(c.TLSKey), c.LegacyTLS)
|
||||
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("unable to configure TLS for StartTLS: StartTLS won't be supported")
|
||||
}
|
||||
}
|
||||
|
||||
var ldapstlsConfig *tls.Config
|
||||
if c := activeConfig.LDAPS; c.Enabled {
|
||||
// TODO check if tls params are string or bytes and change config accordingly
|
||||
ldapstlsConfig, err = _tls.MakeTLS([]byte(c.Cert), []byte(c.Key), c.LegacyTLS)
|
||||
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("unable to configure TLS for LDAPS")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
s, err := server.NewServer(
|
||||
server.Logger(log),
|
||||
server.Config(activeConfig),
|
||||
server.StartTLSConfig(starttlsConfig),
|
||||
server.LDAPSTLSConfig(ldapstlsConfig),
|
||||
server.Monitor(monitor),
|
||||
server.Tracer(tracer),
|
||||
)
|
||||
|
||||
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 {
|
||||
go func() {
|
||||
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)
|
||||
}
|
||||
go func() {
|
||||
if err := s.ListenAndServeTLS(); err != nil {
|
||||
log.Error().Err(err).Msg("could not start LDAPS server")
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// Block until we receive our signal.
|
||||
<-c
|
||||
|
||||
// Doesn't block if no connections, but will otherwise wait
|
||||
// until the timeout deadline.
|
||||
s.Shutdown()
|
||||
|
||||
// Optionally, you could run srv.Shutdown in a goroutine and block on
|
||||
// <-ctx.Done() if your application should wait for other services
|
||||
// to finalize based on context cancellation.
|
||||
log.Info().Msg("AP exit")
|
||||
os.Exit(1)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func startConfigWatcher() {
|
||||
@@ -222,6 +236,8 @@ func startConfigWatcher() {
|
||||
isChanged = true
|
||||
} else if event.Op&fsnotify.Remove == fsnotify.Remove { // vim edit file with rename/remove
|
||||
isChanged, isRemoved = true, true
|
||||
} else if event.Op&fsnotify.Create == fsnotify.Create { // only when watching a directory
|
||||
isChanged = true
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
log.Error().Err(err).Msg("watcher error")
|
||||
@@ -235,10 +251,17 @@ func startConfigWatcher() {
|
||||
isChanged, isRemoved = true, false
|
||||
}
|
||||
if isChanged {
|
||||
if err := doConfig(false); err != nil {
|
||||
|
||||
cfg, err := toml.NewConfig(false, configFileLocation, args)
|
||||
|
||||
if err != nil {
|
||||
log.Info().Err(err).Msg("Could not reload config. Holding on to old config")
|
||||
} else {
|
||||
log.Info().Msg("Config was reloaded")
|
||||
|
||||
if err := copier.Copy(activeConfig, cfg); err != nil {
|
||||
log.Info().Err(err).Msg("Could not save reloaded config. Holding on to old config")
|
||||
}
|
||||
}
|
||||
isChanged = false
|
||||
}
|
||||
@@ -252,7 +275,7 @@ func startConfigWatcher() {
|
||||
func parseArgs() error {
|
||||
var err error
|
||||
|
||||
if args, err = docopt.Parse(usage, nil, true, getVersionString(), false); err != nil {
|
||||
if args, err = docopt.Parse(usage, nil, true, version.GetVersion(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -262,559 +285,3 @@ func parseArgs() error {
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
// initLogging sets up logging to stderr
|
||||
func initLogging(reqdebug bool, reqsyslog bool, reqstructlog bool) {
|
||||
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}
|
||||
}
|
||||
|
||||
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))
|
||||
log = zerolog.New(writers).Level(level).With().Timestamp().Logger()
|
||||
}
|
||||
}
|
||||
|
||||
if !reqsyslog {
|
||||
log = zerolog.New(mainWriter).Level(level).With().Timestamp().Logger()
|
||||
}
|
||||
|
||||
logging.RewireLogging(log, reqstructlog)
|
||||
}
|
||||
|
||||
+387
-173
@@ -1,12 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"github.com/glauth/ldap"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
type testEnv struct {
|
||||
@@ -24,217 +32,423 @@ type testEnv struct {
|
||||
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)
|
||||
})
|
||||
func glauthBinary() string {
|
||||
if binary := os.Getenv("GLAUTH_TEST_BINARY"); binary != "" {
|
||||
return binary
|
||||
}
|
||||
|
||||
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)
|
||||
qualified := filepath.Join("bin", runtime.GOOS+runtime.GOARCH, "glauth")
|
||||
if _, err := os.Stat(qualified); err == nil {
|
||||
return qualified
|
||||
}
|
||||
|
||||
distribution := filepath.Join("bin", "glauth-"+runtime.GOOS+"-"+runtime.GOARCH)
|
||||
if runtime.GOOS == "windows" {
|
||||
distribution += ".exe"
|
||||
}
|
||||
if _, err := os.Stat(distribution); err == nil {
|
||||
return distribution
|
||||
}
|
||||
|
||||
return "bin/glauth"
|
||||
}
|
||||
|
||||
func TestProperBuild(t *testing.T) {
|
||||
info, err := os.Stat(glauthBinary())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mode := uint32(info.Mode())
|
||||
if mode&0b001001001 == 0 {
|
||||
t.Fatalf("bad file mode: %b", mode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigBackendRejectsPasswordlessUserBinds(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port := listener.Addr().(*net.TCPAddr).Port
|
||||
listener.Close()
|
||||
|
||||
configPath := filepath.Join(t.TempDir(), "passwordless.cfg")
|
||||
config := fmt.Sprintf(`debug = false
|
||||
|
||||
[ldap]
|
||||
enabled = true
|
||||
listen = "127.0.0.1:%d"
|
||||
tls = false
|
||||
|
||||
[ldaps]
|
||||
enabled = false
|
||||
|
||||
[backend]
|
||||
datastore = "config"
|
||||
baseDN = "dc=glauth,dc=com"
|
||||
|
||||
[behaviors]
|
||||
LimitFailedBinds = false
|
||||
|
||||
[[groups]]
|
||||
name = "svc"
|
||||
gidnumber = 5501
|
||||
|
||||
[[users]]
|
||||
name = "nopass"
|
||||
uidnumber = 5001
|
||||
primarygroup = 5501
|
||||
[[users.capabilities]]
|
||||
action = "search"
|
||||
object = "*"
|
||||
`, port)
|
||||
if err := os.WriteFile(configPath, []byte(config), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
svc := startSvc(t, fmt.Sprintf("127.0.0.1:%d", port), glauthBinary(), "-c", configPath)
|
||||
defer stopSvc(svc)
|
||||
|
||||
bindDN := "cn=nopass,dc=glauth,dc=com"
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
password string
|
||||
want string
|
||||
}{
|
||||
{name: "arbitrary password", password: "definitely-wrong", want: "exit status 49"},
|
||||
{name: "empty password", password: "", want: "exit status 53"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := runAndSelect(t, getFirst, "ldapsearch", "-LLL", "-H", fmt.Sprintf("ldap://127.0.0.1:%d", port), "-D", bindDN, "-w", tc.password, "-x", "-b", "dc=glauth,dc=com", "(cn=nopass)")
|
||||
if got != tc.want {
|
||||
t.Fatalf("bind result = %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func 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)
|
||||
})
|
||||
})
|
||||
func ldapOnlyConfig(t *testing.T, source string) string {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
contents := string(data)
|
||||
sectionMarker := "\n[ldaps]\n"
|
||||
markerStart := strings.Index(contents, sectionMarker)
|
||||
if markerStart < 0 {
|
||||
t.Fatalf("%s has no [ldaps] section", source)
|
||||
}
|
||||
start := markerStart + 1
|
||||
relEnd := strings.Index(contents[start+len("[ldaps]"):], "\n[")
|
||||
if relEnd < 0 {
|
||||
t.Fatalf("%s has no section after [ldaps]", source)
|
||||
}
|
||||
end := start + len("[ldaps]") + relEnd
|
||||
section := contents[start:end]
|
||||
sectionWithoutLDAPS := strings.Replace(section, "enabled = true", "enabled = false", 1)
|
||||
if section == sectionWithoutLDAPS {
|
||||
t.Fatalf("%s [ldaps] section does not enable LDAPS", source)
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
contents = contents[:start] + sectionWithoutLDAPS + contents[end:]
|
||||
path := filepath.Join(t.TempDir(), filepath.Base(source))
|
||||
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
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")
|
||||
})
|
||||
})
|
||||
func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
// We aren't checking that "github.com/pquerna/otp/totp" creates correct values.
|
||||
// That's what their own tests are for.
|
||||
// We are checking that the totp code gets through glauth code successfully
|
||||
otpvalue, err := totp.GenerateCode("3hnvnk4ycv44glzigd6s25j4dougs3rk", time.Now())
|
||||
if err != nil {
|
||||
t.Fatal("Failed to generate totp code:", err)
|
||||
}
|
||||
|
||||
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")
|
||||
})
|
||||
})
|
||||
const addr = "ldap://127.0.0.1:3893"
|
||||
bind := func(dn, password string, rest ...string) []string {
|
||||
return append([]string{"-LLL", "-H", addr, "-D", dn, "-w", password, "-x"}, rest...)
|
||||
}
|
||||
svc := func(rest ...string) []string { return bind(env.svcdn, "mysecret", rest...) }
|
||||
otp := func(password string) []string {
|
||||
return bind(env.otpdn, password, "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
|
||||
}
|
||||
|
||||
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 (
|
||||
inSuperheros = "should find them in the 'superheros' group"
|
||||
listFromUser = "should get a list starting with the 'hackers' user"
|
||||
badCreds = "should get 'Invalid credentials(49)'"
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
skip bool
|
||||
args []string
|
||||
line func(string) string
|
||||
want string
|
||||
msg string
|
||||
}{
|
||||
{
|
||||
name: "searching for the 'hackers' user",
|
||||
args: svc("-bdc=glauth,dc=com", "cn=hackers"),
|
||||
want: env.expectedaccount,
|
||||
msg: inSuperheros,
|
||||
},
|
||||
{
|
||||
name: "searching for the 'hackers' user without binding with a group",
|
||||
skip: env.svcdnnogroup == "",
|
||||
args: bind(env.svcdnnogroup, "mysecret", "-bdc=glauth,dc=com", "cn=hackers"),
|
||||
want: env.expectedaccount,
|
||||
msg: inSuperheros,
|
||||
},
|
||||
{
|
||||
name: "searching for the 'hackers' user after binding using the account's UPN",
|
||||
skip: !env.checkbindUPN,
|
||||
args: bind("serviceuser@example.com", "mysecret", "-bdc=glauth,dc=com", "cn=hackers"),
|
||||
want: env.expectedaccount,
|
||||
msg: inSuperheros,
|
||||
},
|
||||
{
|
||||
name: "querying the root SDE",
|
||||
args: svc("-s", "base", "(objectclass=*)"),
|
||||
line: getSecond,
|
||||
want: env.expectedinfo,
|
||||
msg: "should get some meta information",
|
||||
},
|
||||
{
|
||||
name: "querying the root SDE anonymously without authorizing in config file",
|
||||
skip: !env.checkanonymousrootDSE,
|
||||
args: []string{"-LLL", "-H", addr, "-x", "-s", "base", "(objectclass=*)"},
|
||||
want: "exit status 50",
|
||||
msg: "should get error 50",
|
||||
},
|
||||
{
|
||||
name: "enumerating posix groups",
|
||||
args: svc("-bdc=glauth,dc=com", "(objectclass=posixgroup)"),
|
||||
want: env.expectedgroup,
|
||||
msg: "should get a list starting with the 'superheros' group",
|
||||
},
|
||||
{
|
||||
name: "searching for members of the 'superheros' group",
|
||||
args: svc("-bdc=glauth,dc=com", "(memberOf=ou=superheros,ou=groups,dc=glauth,dc=com)"),
|
||||
want: env.expectedfirstaccount,
|
||||
msg: listFromUser,
|
||||
},
|
||||
{
|
||||
name: "performing a complex search for members of 'superheros' group",
|
||||
args: svc("-bdc=glauth,dc=com", "(&(objectClass=*)(memberOf=ou=superheros,ou=groups,dc=glauth,dc=com))"),
|
||||
want: env.expectedfirstaccount,
|
||||
msg: listFromUser,
|
||||
},
|
||||
{
|
||||
name: "searching for the 'hacker' user using a TOTP-enabled account",
|
||||
skip: !env.checkTOTP,
|
||||
args: otp("mysecret" + otpvalue),
|
||||
want: env.scopedaccount,
|
||||
msg: inSuperheros,
|
||||
},
|
||||
{
|
||||
name: "searching for the 'hacker' user using a TOTP-enabled account and no value",
|
||||
skip: !env.checkTOTP,
|
||||
args: otp("mysecret"),
|
||||
want: "exit status 49",
|
||||
msg: badCreds,
|
||||
},
|
||||
{
|
||||
name: "searching for the 'hacker' user using a TOTP-enabled account and the wrong value",
|
||||
skip: !env.checkTOTP,
|
||||
args: otp("mysecret123456"),
|
||||
want: "exit status 49",
|
||||
msg: badCreds,
|
||||
},
|
||||
{
|
||||
name: "searching for the 'hacker' user",
|
||||
skip: env.checkemployeetype == "",
|
||||
args: svc("-bdc=glauth,dc=com", env.checkemployeetype, "employeetype"),
|
||||
line: getSecond,
|
||||
want: "employeetype: Intern",
|
||||
msg: "type should be 'Intern'",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.skip {
|
||||
t.SkipNow()
|
||||
}
|
||||
line := tc.line
|
||||
if line == nil {
|
||||
line = getFirst
|
||||
}
|
||||
if got := runAndSelect(t, line, "ldapsearch", tc.args...); got != tc.want {
|
||||
t.Fatalf("%s\ngot: %s\nwant: %s", tc.msg, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSampleSimple(t *testing.T) {
|
||||
env := 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",
|
||||
}
|
||||
|
||||
svc := startSvc(t, "127.0.0.1:3893", glauthBinary(), "-c", ldapOnlyConfig(t, "sample-simple.cfg"))
|
||||
batteryOfTests(t, &env)
|
||||
stopSvc(svc)
|
||||
}
|
||||
|
||||
func TestSQLitePlugin(t *testing.T) {
|
||||
_, err := os.Stat("bin/sqlite.so")
|
||||
if err != nil {
|
||||
t.Log("Unable to find sqlite plugin")
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
env := 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: "",
|
||||
}
|
||||
|
||||
svc := startSvc(t, "127.0.0.1:3893", glauthBinary(), "-c", "pkg/plugins/glauth-sqlite/sample-database.cfg")
|
||||
batteryOfTests(t, &env)
|
||||
stopSvc(svc)
|
||||
}
|
||||
|
||||
func TestLdapInjection(t *testing.T) {
|
||||
out, err := doRun("sh", "-c", "docker ps | grep ldap-service | wc -l")
|
||||
if err != nil {
|
||||
t.Log("Unable to find docker container:", out)
|
||||
t.SkipNow()
|
||||
}
|
||||
matchingContainers := getFirst(out)
|
||||
if matchingContainers != "1" {
|
||||
t.Log("Unable to find docker container:", out)
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
env := 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,ou=users,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: "",
|
||||
}
|
||||
|
||||
svc := startSvc(t, "127.0.0.1:3893", glauthBinary(), "-c", "sample-ldap-injection.cfg")
|
||||
batteryOfTests(t, &env)
|
||||
stopSvc(svc)
|
||||
}
|
||||
|
||||
// -----=============================================================================----
|
||||
|
||||
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)
|
||||
func waitForPort(host string, timeout time.Duration) error {
|
||||
start := time.Now()
|
||||
for {
|
||||
conn, err := ldap.Dial("tcp", host)
|
||||
if err == nil {
|
||||
err = conn.Bind("_", "_")
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
return fmt.Errorf("Successfully authenticated to glauth. This should not happen")
|
||||
}
|
||||
e := &ldap.Error{}
|
||||
if errors.As(err, &e) && e.ResultCode == 49 {
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
conn.Close()
|
||||
// We have an error, it's probably a connection error or an error saying that ldap isn't setup yet
|
||||
// try again
|
||||
}
|
||||
if time.Since(start) > timeout {
|
||||
return fmt.Errorf("timeout waiting for port %s: %w", host, err)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func startSvc(t *testing.T, addr string, name string, arg ...string) *exec.Cmd {
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd.Stderr = t.Output()
|
||||
cmd.Stdout = t.Output()
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatal("Failed to start glauth:", err)
|
||||
}
|
||||
if err := waitForPort(addr, time.Second*2); err != nil {
|
||||
t.Fatal("Failed to wait for glauth to start:", err)
|
||||
}
|
||||
// the port is listening but the ldap server may not be accepting connections yet
|
||||
// TODO: make glauth listen as the last part of the initialization that way as soon as it listens it can process connections
|
||||
return cmd
|
||||
}
|
||||
|
||||
func stopSvc(svc *exec.Cmd) {
|
||||
svc.Process.Kill()
|
||||
svc.Process.Wait()
|
||||
}
|
||||
|
||||
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 {
|
||||
func getFirst(output string) string {
|
||||
out := strings.SplitN(output, "\n", 2)
|
||||
if 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 {
|
||||
func getSecond(output string) string {
|
||||
out := strings.SplitN(output, "\n", 3)
|
||||
if 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()
|
||||
func doRun(name string, arg ...string) (string, error) {
|
||||
out, err := exec.Command(name, arg...).CombinedOutput()
|
||||
if err != nil {
|
||||
return string(bytes.TrimSpace(out)), err
|
||||
}
|
||||
|
||||
return string(bytes.TrimSpace(out)), nil
|
||||
}
|
||||
|
||||
func runAndSelect(t testing.TB, pick func(string) string, name string, arg ...string) string {
|
||||
t.Helper()
|
||||
out, err := doRun(name, arg...)
|
||||
if err != nil {
|
||||
t.Log(out)
|
||||
return err.Error()
|
||||
}
|
||||
if delay != 0 {
|
||||
time.Sleep(time.Second * delay)
|
||||
}
|
||||
return strings.TrimSpace(string(out))
|
||||
return pick(out)
|
||||
}
|
||||
|
||||
@@ -1,27 +1,62 @@
|
||||
module github.com/glauth/glauth/v2
|
||||
|
||||
go 1.16
|
||||
|
||||
// Do not mistake /vendored for /vendor!
|
||||
replace github.com/hydronica/toml => ./vendored/toml
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e
|
||||
github.com/arl/statsviz v0.4.0
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/arl/statsviz v0.6.0
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/go-logr/logr v0.4.0
|
||||
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/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
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.0.0-20210711020723-a769d52b0f97
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/glauth/ldap v0.0.0-20240419171521-1f14f5c1b4ad
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
github.com/prometheus/client_model v0.5.0
|
||||
github.com/rs/zerolog v1.31.0
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3
|
||||
github.com/yaegashi/msgraph.go v0.1.4
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.46.0
|
||||
go.opentelemetry.io/otel v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.46.0
|
||||
go.opentelemetry.io/otel/sdk v1.46.0
|
||||
go.opentelemetry.io/otel/trace v1.46.0
|
||||
go.uber.org/mock v0.4.0
|
||||
golang.org/x/crypto v0.55.0
|
||||
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
github.com/prometheus/common v0.45.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/rickb777/date v1.12.4 // indirect
|
||||
github.com/rickb777/plural v1.2.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.46.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
|
||||
golang.org/x/net v0.58.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect
|
||||
google.golang.org/grpc v1.83.1 // indirect
|
||||
google.golang.org/protobuf v1.36.12 // indirect
|
||||
)
|
||||
|
||||
@@ -1,541 +1,261 @@
|
||||
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/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
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/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE=
|
||||
github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s=
|
||||
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 h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
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/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/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-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
|
||||
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/glauth/ldap v0.0.0-20240419171521-1f14f5c1b4ad h1:2ERGXiofR9ISXg4evamCngs2EeuMhVJRuDwWGa6bS6s=
|
||||
github.com/glauth/ldap v0.0.0-20240419171521-1f14f5c1b4ad/go.mod h1:5ueZMujvJ5nuYPyBj6uQTPV4R1YshboRa/osaP4Emfs=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
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/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
|
||||
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
|
||||
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/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
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/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/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/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||
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/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
|
||||
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/rickb777/date v1.12.4 h1:+6IzcCCS/1t17DrmnEvrznyq7nM8vPwir6/UhlyohKw=
|
||||
github.com/rickb777/date v1.12.4/go.mod h1:xP0eo/I5qmUt97yRGClHZfyLZ3ikMw6v6SU5MOGZTE0=
|
||||
github.com/rickb777/plural v1.2.0 h1:5tvEc7UBCZ7l8h/2UeybSkt/uu1DQsZFOFdNevmUhlE=
|
||||
github.com/rickb777/plural v1.2.0/go.mod h1:UdpyWFCGbo3mvK3f/PfZOAOrkjzJlYN/sD46XNWJ+Es=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
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/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3 h1:LNi0Qa7869/loPjz2kmMvp/jwZZnMZ9scMJKhDJ1DIo=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3/go.mod h1:jyigonKik3C5V895QNiAGpKYKEvFuqjw9qAEZks1mUg=
|
||||
github.com/yaegashi/msgraph.go v0.1.4 h1:leDXSczAbwBpYFSmmZrdByTiPoUw8dbTfNMetAjJvbw=
|
||||
github.com/yaegashi/msgraph.go v0.1.4/go.mod h1:vgeYhHa5skJt/3lTyjGXThTZhwbhRnGo6uUxzoJIGME=
|
||||
github.com/yaegashi/wtz.go v0.0.2/go.mod h1:nOLA5QXsmdkRxBkP5tljhua13ADHCKirLBrzPf4PEJc=
|
||||
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=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.21.1 h1:f4beMGDKiVzg9IcX7/VuWVy+oGdjx3dNJ72YehmtY5k=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.21.1/go.mod h1:U9jhkEl8d1LL+QXY7q3kneJWJugiN3kZJV2OWz3hkBY=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.46.0 h1:uxl0SGcmuBkHj/Adl9oftEAyiawQBPL5RzMAmt/Yvq4=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.46.0/go.mod h1:LiOkxCIvoLofmRps7f8l0NkBtmObnAyQ5trteFs6wj8=
|
||||
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc=
|
||||
go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 h1:OFnwLJr+pF3iHrlGSzbxyuo6/6HyBlnlN1CWEJmBVcw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0/go.mod h1:716wFneO0ov19A2beH5hjfh9AK5z/VWNAtDijp1Y0/g=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0 h1:w53CDeOA/Kurp7yRsegSr6pbbr759dOvJ+yNmWM6Hxs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0/go.mod h1:BOmGMCbAtvcJiSJ+hLuhgPLdDbimnraSl8irz3iY8sY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 h1:lgh3PiVrRUWMLOVSkQicxzZll5NjF1r+AtsX1XRIHw0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0/go.mod h1:5Cnhth3m/AgOeTgE3ex12pPmiu/gGtZit03kSzx9X7s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0 h1:KrC1YrQeSt46ITMWAbgQx1M1eV1/1TKzttrBzymPmss=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0/go.mod h1:zDSEzoEqsOrgBeGvH66KRgxh90VonFyJqBHA0Pk3+rM=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 h1:bl2S7Ubua0Nms+D/gAmznQTd4dxxMA93aKbcpKqiTCs=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0/go.mod h1:L0hRV50XdVIODHUfWEqGRCXQvj2rV82STVo12FMFBU0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.46.0 h1:KdRxPiAoMptR3vfWzvjjvutTsSiwbC2uG0496rzZNfo=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.46.0/go.mod h1:K/qSA+3G7Eovxi4K09wzrAgkWRnosS0DAOZeEpve7sM=
|
||||
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8=
|
||||
go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI=
|
||||
go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM=
|
||||
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
||||
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c=
|
||||
go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk=
|
||||
go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
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.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
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/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
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/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
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/sync v0.0.0-20220601150217-0de741cfad7f/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.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
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/tools v0.0.0-20200529172331-a64b76657301/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
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/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
|
||||
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y=
|
||||
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
|
||||
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=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
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=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package monitoring
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type API struct {
|
||||
logger zerolog.Logger
|
||||
}
|
||||
|
||||
func (a *API) RegisterEndpoints(router *http.ServeMux) {
|
||||
router.HandleFunc("/metrics", a.prometheusHTTP)
|
||||
}
|
||||
|
||||
func (a *API) prometheusHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
promhttp.Handler().ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewAPI(logger zerolog.Logger) *API {
|
||||
a := new(API)
|
||||
|
||||
a.logger = logger
|
||||
|
||||
return a
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package monitoring
|
||||
|
||||
import "github.com/glauth/ldap"
|
||||
|
||||
type MonitorInterface interface {
|
||||
SetResponseTimeMetric(map[string]string, float64) error
|
||||
SetLDAPMetric(map[string]string, float64) error
|
||||
}
|
||||
|
||||
type LDAPServerInterface interface {
|
||||
SetStats(bool)
|
||||
GetStats() ldap.Stats
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package monitoring
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type LDAPMonitorWatcher struct {
|
||||
syncTicker *time.Ticker
|
||||
|
||||
ldap LDAPServerInterface
|
||||
|
||||
monitor MonitorInterface
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
func (m *LDAPMonitorWatcher) sync() {
|
||||
for {
|
||||
select {
|
||||
case tick := <-m.syncTicker.C:
|
||||
m.logger.Debug().Time("value", tick).Msg("Tick")
|
||||
m.storeMetrics()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *LDAPMonitorWatcher) storeMetrics() {
|
||||
stats := m.ldap.GetStats()
|
||||
|
||||
if err := m.monitor.SetLDAPMetric(map[string]string{"type": "conns"}, float64(stats.Conns)); err != nil {
|
||||
m.logger.Error().Err(err).Msg("failed to set metric")
|
||||
}
|
||||
if err := m.monitor.SetLDAPMetric(map[string]string{"type": "binds"}, float64(stats.Binds)); err != nil {
|
||||
m.logger.Error().Err(err).Msg("failed to set metric")
|
||||
}
|
||||
if err := m.monitor.SetLDAPMetric(map[string]string{"type": "unbinds"}, float64(stats.Unbinds)); err != nil {
|
||||
m.logger.Error().Err(err).Msg("failed to set metric")
|
||||
}
|
||||
if err := m.monitor.SetLDAPMetric(map[string]string{"type": "searches"}, float64(stats.Searches)); err != nil {
|
||||
m.logger.Error().Err(err).Msg("failed to set metric")
|
||||
}
|
||||
}
|
||||
|
||||
func NewLDAPMonitorWatcher(ldap LDAPServerInterface, monitor MonitorInterface, logger *zerolog.Logger) *LDAPMonitorWatcher {
|
||||
m := new(LDAPMonitorWatcher)
|
||||
|
||||
m.syncTicker = time.NewTicker(15 * time.Second)
|
||||
m.ldap = ldap
|
||||
m.monitor = monitor
|
||||
m.logger = logger
|
||||
|
||||
m.ldap.SetStats(true)
|
||||
|
||||
go m.sync()
|
||||
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package monitoring
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/glauth/ldap"
|
||||
"github.com/rs/zerolog"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
//go:generate mockgen -build_flags=--mod=mod -package monitoring -destination ./mock_interfaces.go -source=./interfaces.go
|
||||
|
||||
func TestNewLDAPMonitorWatcherRunsOnASchedule(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
mockMonitor := NewMockMonitorInterface(ctrl)
|
||||
mockLDAPServer := NewMockLDAPServerInterface(ctrl)
|
||||
|
||||
stats := ldap.Stats{}
|
||||
|
||||
mockLDAPServer.EXPECT().SetStats(true).Times(1)
|
||||
mockLDAPServer.EXPECT().GetStats().MinTimes(1).Return(stats)
|
||||
mockMonitor.EXPECT().SetLDAPMetric(map[string]string{"type": "conns"}, float64(0)).MinTimes(1)
|
||||
mockMonitor.EXPECT().SetLDAPMetric(map[string]string{"type": "binds"}, float64(0)).MinTimes(1)
|
||||
mockMonitor.EXPECT().SetLDAPMetric(map[string]string{"type": "unbinds"}, float64(0)).MinTimes(1)
|
||||
mockMonitor.EXPECT().SetLDAPMetric(map[string]string{"type": "searches"}, float64(0)).MinTimes(1)
|
||||
|
||||
logger := zerolog.Nop()
|
||||
m := NewLDAPMonitorWatcher(mockLDAPServer, mockMonitor, &logger)
|
||||
|
||||
m.syncTicker = time.NewTicker(5 * time.Microsecond)
|
||||
|
||||
// allow goroutine to start and ticker to tick
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package monitoring
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// 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 := io.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 NewCollector(url string) *Collector {
|
||||
c := new(Collector)
|
||||
c.url = url
|
||||
c.names = map[string]string{
|
||||
"proxy": "glauth_proxy",
|
||||
"proxy_frontend": "glauth_proxy_frontend",
|
||||
"proxy_backend": "glauth_proxy_backend",
|
||||
}
|
||||
c.helps = map[string]string{
|
||||
"proxy": "General Metrics",
|
||||
"proxy_frontend": "Frontend Metrics",
|
||||
"proxy_backend": "Backend Metrics",
|
||||
}
|
||||
c.labelName = map[string]string{
|
||||
"proxy": "metric",
|
||||
"proxy_frontend": "metric",
|
||||
"proxy_backend": "metric",
|
||||
}
|
||||
|
||||
prometheus.MustRegister(c)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
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,109 @@
|
||||
package monitoring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
responseTime *prometheus.HistogramVec
|
||||
ldapMetric *prometheus.GaugeVec
|
||||
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
func (m *Monitor) SetResponseTimeMetric(tags map[string]string, value float64) error {
|
||||
if m.responseTime == nil {
|
||||
return fmt.Errorf("metric not instantiated")
|
||||
}
|
||||
|
||||
m.responseTime.With(tags).Observe(value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Monitor) SetLDAPMetric(tags map[string]string, value float64) error {
|
||||
if m.ldapMetric == nil {
|
||||
return fmt.Errorf("metric not instantiated")
|
||||
}
|
||||
|
||||
m.ldapMetric.With(tags).Set(value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Monitor) constLabels() map[string]string {
|
||||
return map[string]string{
|
||||
"library": "github.com/glauth/glauth",
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *Monitor) registerHistograms() {
|
||||
histograms := make([]*prometheus.HistogramVec, 0)
|
||||
|
||||
m.responseTime = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "tcp_response_time_seconds",
|
||||
Help: "tcp_response_time_seconds",
|
||||
ConstLabels: m.constLabels(),
|
||||
},
|
||||
[]string{"operation", "status"},
|
||||
)
|
||||
|
||||
histograms = append(histograms, m.responseTime)
|
||||
|
||||
for _, histogram := range histograms {
|
||||
err := prometheus.Register(histogram)
|
||||
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
return
|
||||
case prometheus.AlreadyRegisteredError:
|
||||
m.logger.Debug().Interface("metric", histogram).Msg("metric already registered")
|
||||
default:
|
||||
m.logger.Error().Interface("metric", histogram).Msg("metric could not be registered")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Monitor) registerGauges() {
|
||||
gauges := make([]*prometheus.GaugeVec, 0)
|
||||
|
||||
m.ldapMetric = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "ldap_metric",
|
||||
Help: "ldap_metric",
|
||||
ConstLabels: m.constLabels(),
|
||||
},
|
||||
[]string{"type"},
|
||||
)
|
||||
|
||||
gauges = append(gauges, m.ldapMetric)
|
||||
|
||||
for _, gauge := range gauges {
|
||||
err := prometheus.Register(gauge)
|
||||
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
return
|
||||
case prometheus.AlreadyRegisteredError:
|
||||
m.logger.Debug().Interface("metric", gauge).Msg("metric already registered")
|
||||
default:
|
||||
m.logger.Error().Interface("metric", gauge).Msg("metric could not be registered")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewMonitor(logger *zerolog.Logger) *Monitor {
|
||||
m := new(Monitor)
|
||||
|
||||
m.logger = logger
|
||||
|
||||
m.registerHistograms()
|
||||
m.registerGauges()
|
||||
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package monitoring
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
func TestNewMonitorImplementsInterface(t *testing.T) {
|
||||
logger := zerolog.Nop()
|
||||
m := NewMonitor(&logger)
|
||||
|
||||
i := reflect.TypeOf((*MonitorInterface)(nil)).Elem()
|
||||
|
||||
if !reflect.TypeOf(m).Implements(i) {
|
||||
t.Fatal("Monitor doesn't implement MonitorInterface")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitorSetLDAPMetricSucceeds(t *testing.T) {
|
||||
logger := zerolog.Nop()
|
||||
m := NewMonitor(&logger)
|
||||
|
||||
labels := map[string]string{"type": "test"}
|
||||
m.SetLDAPMetric(labels, float64(10))
|
||||
|
||||
mLDAPMetric := dto.Metric{}
|
||||
m.ldapMetric.With(labels).Write(&mLDAPMetric)
|
||||
|
||||
if mLDAPMetric.GetGauge().GetValue() != float64(10) {
|
||||
t.Fatalf("metric value should have been set to %v", float64(10))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitorSetResponseTimeMetricSucceeds(t *testing.T) {
|
||||
logger := zerolog.Nop()
|
||||
m := NewMonitor(&logger)
|
||||
|
||||
labels := map[string]string{"operation": "test", "status": "0"}
|
||||
m.SetResponseTimeMetric(labels, float64(10))
|
||||
|
||||
mResponseTimeMetric := dto.Metric{}
|
||||
m.responseTime.With(labels).(prometheus.Metric).Write(&mResponseTimeMetric)
|
||||
|
||||
if mResponseTimeMetric.GetHistogram().GetSampleSum() != float64(10) {
|
||||
t.Fatalf("metric value should have been set to %v", float64(10))
|
||||
}
|
||||
|
||||
for _, bucket := range mResponseTimeMetric.GetHistogram().GetBucket() {
|
||||
if bucket.GetUpperBound() < float64(10) && bucket.GetCumulativeCount() != 0 {
|
||||
t.Fatal("there should be no count for this metric bucket")
|
||||
}
|
||||
|
||||
if bucket.GetUpperBound() >= float64(10) && bucket.GetCumulativeCount() != 1 {
|
||||
t.Fatal("there should be one entry into this metric bucket")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
tls "crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
var (
|
||||
log zerolog.Logger
|
||||
)
|
||||
|
||||
var secureCipherSuites = []uint16{
|
||||
// TLS 1.3 cipher suites (automatically used when TLS 1.3 is negotiated)
|
||||
tls.TLS_AES_128_GCM_SHA256,
|
||||
tls.TLS_AES_256_GCM_SHA384,
|
||||
tls.TLS_CHACHA20_POLY1305_SHA256,
|
||||
|
||||
// TLS 1.2 ECDHE cipher suites (Forward Secrecy)
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
|
||||
// Additional secure TLS 1.2 cipher suites (CBC with HMAC-SHA256)
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
// tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
// tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
|
||||
}
|
||||
|
||||
// MakeTLS generates a tls.Config
|
||||
func MakeTLS(clientCert, key []byte, legacy bool) (*tls.Config, error) {
|
||||
if clientCert == nil && key == nil {
|
||||
return new(tls.Config), nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
cert, err := tls.X509KeyPair(clientCert, key)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug().Str("key", string(key))
|
||||
log.Debug().Str("client.certificate", string(clientCert))
|
||||
log.Debug().Interface("certificate", cert)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the SystemCertPool, continue with an empty pool on error
|
||||
rootCAs, err := x509.SystemCertPool()
|
||||
|
||||
if rootCAs == nil {
|
||||
rootCAs = x509.NewCertPool()
|
||||
log.Warn().Err(err).Msg("Using empty cert-pool")
|
||||
} else {
|
||||
log.Info().Msg("Using system cert-pool")
|
||||
}
|
||||
|
||||
for _, cert := range DecodePEM(clientCert).Certificate {
|
||||
x509Cert, err := x509.ParseCertificate(cert)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("issue parsing cert PEM")
|
||||
}
|
||||
rootCAs.AddCert(x509Cert)
|
||||
}
|
||||
|
||||
log.Debug().Interface("root.ca", rootCAs)
|
||||
log.Debug().Interface("certificates", []tls.Certificate{cert})
|
||||
|
||||
if legacy {
|
||||
return &tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
MinVersion: tls.VersionTLS10,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: nil,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: secureCipherSuites,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DecodePEM builds a PEM certificate object
|
||||
func DecodePEM(certPEM []byte) tls.Certificate {
|
||||
var cert tls.Certificate
|
||||
var certDER *pem.Block
|
||||
for {
|
||||
certDER, certPEM = pem.Decode(certPEM)
|
||||
if certDER == nil {
|
||||
break
|
||||
}
|
||||
if certDER.Type == "CERTIFICATE" {
|
||||
cert.Certificate = append(cert.Certificate, certDER.Bytes)
|
||||
}
|
||||
}
|
||||
|
||||
return cert
|
||||
}
|
||||
|
||||
func CipherSuiteNames(suites []uint16) []string {
|
||||
var names []string
|
||||
for _, suite := range suites {
|
||||
names = append(names, tls.CipherSuiteName(suite))
|
||||
}
|
||||
return names
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/amz.v3/aws"
|
||||
"gopkg.in/amz.v3/s3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Users []toml.Primitive
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
CustomAttributes []toml.Primitive
|
||||
}
|
||||
|
||||
// NewConfig reads the cli flags and config file
|
||||
func NewConfig(checkConfig bool, location string, args map[string]interface{}) (*config.Config, error) {
|
||||
// Parse config-file into config{} struct
|
||||
cfg, err := parseConfigFile(location, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handle parsed flags
|
||||
cfg, err = handleArgs(cfg, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handle parsing of legacy [frontend] section into [ldap] and/or [ldaps] sections
|
||||
cfg, err = handleLegacyConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg, err = validateConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO @shipperizer reinstate this
|
||||
// // Before greenlighting new config entirely, lets make sure the yubiauth works - in case they changed
|
||||
|
||||
if _, err := yubigo.NewYubiAuth(cfg.YubikeyClientID, cfg.YubikeySecret); err != nil && len(cfg.YubikeyClientID) > 0 && len(cfg.YubikeySecret) > 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func parseConfigFile(configFileLocation string, args map[string]interface{}) (*config.Config, error) {
|
||||
cfg := new(config.Config)
|
||||
// setup defaults
|
||||
cfg.LDAP.Enabled = false
|
||||
cfg.LDAPS.Enabled = true
|
||||
|
||||
// parse the config file
|
||||
if strings.HasPrefix(configFileLocation, "s3://") {
|
||||
region, present := aws.Regions[args["-r"].(string)]
|
||||
if !present {
|
||||
return cfg, fmt.Errorf("invalid AWS region: %s", args["-r"])
|
||||
}
|
||||
if args["--aws_endpoint_url"] != nil {
|
||||
region = aws.Region{
|
||||
Name: "User defined",
|
||||
S3Endpoint: args["--aws_endpoint_url"].(string),
|
||||
}
|
||||
present = true
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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, _ := os.ReadDir(configFileLocation)
|
||||
for _, f := range files {
|
||||
canonicalName := filepath.Join(configFileLocation, f.Name())
|
||||
|
||||
bs, _ := os.ReadFile(canonicalName)
|
||||
var curRawCfgStruct interface{}
|
||||
if err := toml.Unmarshal(bs, &curRawCfgStruct); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
if err = mergeConfigs(&rawCfgStruct, curRawCfgStruct); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
}
|
||||
|
||||
destbuf := new(bytes.Buffer)
|
||||
err = toml.NewEncoder(destbuf).Encode(rawCfgStruct)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
fmt.Println(destbuf.String())
|
||||
merged := config.Config{}
|
||||
if _, err = toml.Decode(destbuf.String(), &merged); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
cfg = &merged
|
||||
} else {
|
||||
_, err = toml.DecodeFile(configFileLocation, cfg)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
}
|
||||
|
||||
usersCustomAttributes(configFileLocation, cfg)
|
||||
}
|
||||
|
||||
// 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,uid"
|
||||
}
|
||||
cfg.Backends[i].NameFormatAsArray = strings.Split(cfg.Backends[i].NameFormat, ",")
|
||||
if cfg.Backends[i].GroupFormat == "" {
|
||||
cfg.Backends[i].GroupFormat = "ou,cn"
|
||||
}
|
||||
cfg.Backends[i].GroupFormatAsArray = strings.Split(cfg.Backends[i].GroupFormat, ",")
|
||||
if cfg.Backends[i].SSHKeyAttr == "" {
|
||||
cfg.Backends[i].SSHKeyAttr = "sshPublicKey"
|
||||
}
|
||||
}
|
||||
//
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// usersCustomAttributes changes config passed in by adding extra information coming from the custom attributes
|
||||
func usersCustomAttributes(location string, config *config.Config) {
|
||||
// TODO @shipperizer deal with multiple files like in line #126
|
||||
c := new(Config)
|
||||
|
||||
md, err := toml.DecodeFile(location, c)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("issues parsing users...keep going")
|
||||
return
|
||||
}
|
||||
|
||||
for _, u := range c.Users {
|
||||
user := new(User)
|
||||
md.PrimitiveDecode(u, user)
|
||||
|
||||
if user.CustomAttributes == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for idx, cUser := range config.Users {
|
||||
if cUser.Name != user.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
x := make(map[string]interface{})
|
||||
|
||||
for _, attribute := range user.CustomAttributes {
|
||||
_ = md.PrimitiveDecode(attribute, x)
|
||||
|
||||
for k, v := range x {
|
||||
|
||||
if config.Users[idx].CustomAttrs == nil {
|
||||
config.Users[idx].CustomAttrs = make(map[string]interface{})
|
||||
}
|
||||
|
||||
config.Users[idx].CustomAttrs[k] = v
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 handleArgs(cfg *config.Config, args map[string]interface{}) (*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.Listen) == 0 {
|
||||
return cfg, fmt.Errorf("no LDAPS bind address was specified: please disable LDAPS or use the 'listen' option")
|
||||
}
|
||||
|
||||
if cfg.LDAPS.Cert == "" && cfg.LDAPS.CertPath != "" {
|
||||
byteData, err := os.ReadFile(cfg.LDAPS.CertPath)
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("unable to read TLS certificate file")
|
||||
}
|
||||
cfg.LDAPS.Cert = string(byteData)
|
||||
}
|
||||
|
||||
if cfg.LDAPS.Key == "" && cfg.LDAPS.KeyPath != "" {
|
||||
byteData, err := os.ReadFile(cfg.LDAPS.KeyPath)
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("unable to read TLS key file")
|
||||
}
|
||||
cfg.LDAPS.Key = string(byteData)
|
||||
}
|
||||
|
||||
// Ugly wart for backward compatibility
|
||||
// In the olden times, we would simply say "please read from this file"
|
||||
// This is now the role of the Path files.
|
||||
if !strings.HasPrefix(cfg.LDAPS.Cert, "-----") {
|
||||
byteData, err := os.ReadFile(cfg.LDAPS.Cert)
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("unable to read TLS certificate file")
|
||||
}
|
||||
cfg.LDAPS.Cert = string(byteData)
|
||||
}
|
||||
if !strings.HasPrefix(cfg.LDAPS.Key, "-----") {
|
||||
byteData, err := os.ReadFile(cfg.LDAPS.Key)
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("unable to read TLS key file")
|
||||
}
|
||||
cfg.LDAPS.Key = string(byteData)
|
||||
}
|
||||
|
||||
if cfg.LDAPS.Cert == "" || cfg.LDAPS.Key == "" {
|
||||
return cfg, fmt.Errorf("LDAPS was enabled but no certificate or key were specified: please disable LDAPS or use the 'cert'/'key' or 'certpath'/'keypath' options")
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
if cfg.LDAP.TLS {
|
||||
if cfg.LDAP.TLSCert == "" && cfg.LDAP.TLSCertPath != "" {
|
||||
byteData, err := os.ReadFile(cfg.LDAP.TLSCertPath)
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("unable to read TLS certificate file")
|
||||
}
|
||||
cfg.LDAP.TLSCert = string(byteData)
|
||||
}
|
||||
|
||||
if cfg.LDAP.TLSKey == "" && cfg.LDAP.TLSKeyPath != "" {
|
||||
byteData, err := os.ReadFile(cfg.LDAP.TLSKeyPath)
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("unable to read TLS key file")
|
||||
}
|
||||
cfg.LDAP.TLSKey = string(byteData)
|
||||
}
|
||||
|
||||
if cfg.LDAP.TLSCert == "" || cfg.LDAP.TLSKey == "" {
|
||||
return cfg, fmt.Errorf("StartTLS was enabled but no certificate or key were specified: please disable StartTLS or use the 'tlscert'/'tlskey' or 'tlscertpath'/'tlskeypath' options")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//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":
|
||||
case "embed":
|
||||
default:
|
||||
return cfg, fmt.Errorf("invalid backend %s - must be 'config', 'ldap', 'owncloud', 'plugin' or 'embed", 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
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
OtelHTTPEndpoint string
|
||||
OtelGRPCEndpoint string
|
||||
Logger *zerolog.Logger
|
||||
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
func NewConfig(enabled bool, otelGRPCEndpoint, otelHTTPEndpoint string, logger *zerolog.Logger) *Config {
|
||||
c := new(Config)
|
||||
|
||||
c.OtelGRPCEndpoint = otelGRPCEndpoint
|
||||
c.OtelHTTPEndpoint = otelHTTPEndpoint
|
||||
c.Logger = logger
|
||||
c.Enabled = enabled
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/contrib/propagators/jaeger"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/embedded"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
)
|
||||
|
||||
type Tracer struct {
|
||||
embedded.Tracer
|
||||
|
||||
tracer trace.Tracer
|
||||
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
func (t *Tracer) init(service string, e sdktrace.SpanExporter) {
|
||||
traceProvider := sdktrace.NewTracerProvider(
|
||||
sdktrace.WithSampler(sdktrace.AlwaysSample()),
|
||||
sdktrace.WithBatcher(e),
|
||||
sdktrace.WithResource(
|
||||
t.buildResource(service),
|
||||
),
|
||||
)
|
||||
|
||||
otel.SetTracerProvider(traceProvider)
|
||||
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}, jaeger.Jaeger{}))
|
||||
|
||||
t.tracer = otel.Tracer(service)
|
||||
}
|
||||
|
||||
func (t *Tracer) gitRevision(settings []debug.BuildSetting) string {
|
||||
for _, setting := range settings {
|
||||
if setting.Key == "vcs.revision" {
|
||||
return setting.Value
|
||||
}
|
||||
}
|
||||
|
||||
return "n/a"
|
||||
}
|
||||
|
||||
func (t *Tracer) buildResource(service string) *resource.Resource {
|
||||
var res *resource.Resource
|
||||
|
||||
res = resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.ServiceName(service),
|
||||
semconv.ServiceVersion("n/a"),
|
||||
)
|
||||
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
if service == "" {
|
||||
service = info.Path
|
||||
}
|
||||
|
||||
res = resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.ServiceName(service),
|
||||
attribute.String("git_sha", t.gitRevision(info.Settings)),
|
||||
attribute.String("app", info.Main.Path),
|
||||
)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (t *Tracer) Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
||||
return t.tracer.Start(ctx, spanName, opts...)
|
||||
}
|
||||
|
||||
// basic tracer implementation of trace.Tracer, just adding some extra configuration
|
||||
func NewTracer(cfg *Config) *Tracer {
|
||||
t := new(Tracer)
|
||||
|
||||
t.logger = cfg.Logger
|
||||
|
||||
// if tracing disabled skip the config
|
||||
if !cfg.Enabled {
|
||||
t.tracer = noop.NewTracerProvider().Tracer("github.com/glauth/glauth")
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
var err error
|
||||
var exporter sdktrace.SpanExporter
|
||||
|
||||
if cfg.OtelGRPCEndpoint != "" {
|
||||
exporter, err = otlptrace.New(
|
||||
context.TODO(),
|
||||
otlptracegrpc.NewClient(
|
||||
otlptracegrpc.WithEndpoint(cfg.OtelGRPCEndpoint),
|
||||
otlptracegrpc.WithInsecure(),
|
||||
),
|
||||
)
|
||||
} else if cfg.OtelHTTPEndpoint != "" {
|
||||
exporter, err = otlptrace.New(
|
||||
context.TODO(),
|
||||
otlptracehttp.NewClient(
|
||||
otlptracehttp.WithEndpoint(cfg.OtelHTTPEndpoint),
|
||||
otlptracehttp.WithInsecure(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
exporter, err = stdouttrace.New(
|
||||
stdouttrace.WithPrettyPrint(),
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.logger.Error().Err(err).Msg("unable to initialize tracing exporter")
|
||||
return nil
|
||||
}
|
||||
|
||||
// set tracer provider and propagator properly, this is to ensure all
|
||||
// instrumentation library could run well
|
||||
t.init("github.com/glauth/glauth", exporter)
|
||||
|
||||
return t
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package version
|
||||
|
||||
// Version is the tag of the latest release, it gets changed by the ci
|
||||
// process when a release is happening
|
||||
const Version = "v2.5.3" // x-release-please-version
|
||||
@@ -0,0 +1,68 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
type gitInfo struct {
|
||||
BuildTime string
|
||||
Commit string
|
||||
Dirty bool
|
||||
}
|
||||
|
||||
// GetVersion 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 GetVersion() string {
|
||||
|
||||
// TODO @shipperizer see if it's worth replacing with info.Main.Path
|
||||
// aka github.com/glauth/glauth/v2 - not great but worth checking
|
||||
|
||||
name := "GLauth"
|
||||
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
|
||||
if !ok {
|
||||
return fmt.Sprintf(`
|
||||
%s
|
||||
Non-release build`,
|
||||
name)
|
||||
}
|
||||
|
||||
gitInfo := vcsInfo(info.Settings)
|
||||
|
||||
// If a release, use the tag
|
||||
if !gitInfo.Dirty {
|
||||
return fmt.Sprintf(`%s %s
|
||||
Build time: %s
|
||||
Commit: %s`, name, Version, gitInfo.BuildTime, gitInfo.Commit)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`%s
|
||||
Non-release build based on tag %s
|
||||
Build time: %s
|
||||
Commit: %s`, name, Version, gitInfo.BuildTime, gitInfo.Commit)
|
||||
|
||||
}
|
||||
|
||||
func vcsInfo(settings []debug.BuildSetting) *gitInfo {
|
||||
info := new(gitInfo)
|
||||
|
||||
info.BuildTime = "unknown"
|
||||
info.Commit = "unknown"
|
||||
info.Dirty = false
|
||||
|
||||
for _, v := range settings {
|
||||
switch v.Key {
|
||||
case "vcs.revision":
|
||||
info.Commit = v.Value
|
||||
case "vcs.modified":
|
||||
info.Dirty = v.Value == "true"
|
||||
case "vcs.time":
|
||||
info.BuildTime = v.Value
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
olcAccess: to dn.subtree="dc=glauth,dc=com"
|
||||
by dn.exact="cn=admin,dc=glauth,dc=com" manage
|
||||
by * break
|
||||
@@ -0,0 +1,7 @@
|
||||
dn: olcDatabase={1}mdb,cn=config
|
||||
changetype: modify
|
||||
delete: olcAccess
|
||||
-
|
||||
add: olcAccess
|
||||
olcAccess: 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: to * by self write by dn="cn=serviceuser,cn=svcaccts,ou=users,dc=glauth,dc=com" write by users read by * none
|
||||
@@ -0,0 +1,4 @@
|
||||
dn: cn=serviceuser,cn=svcaccts,ou=users,dc=glauth,dc=com
|
||||
changetype: modify
|
||||
replace: userPassword
|
||||
userPassword: mysecret
|
||||
@@ -0,0 +1,3 @@
|
||||
TLS_REQCERT never
|
||||
URI ldaps://192.168.1.222
|
||||
BASE dc=test,dc=example,dc=com
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
for arg in "$@"; do
|
||||
if [[ "$arg" == *"="* ]]; then
|
||||
key="${arg%%=*}"
|
||||
value="${arg#*=}"
|
||||
eval "$key"="'$value'"
|
||||
else
|
||||
eval "$arg"=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$ipaddr" == "" ]]; then
|
||||
echo "Please provide ipaddr=<ip address> to create/use" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$ipmask" == "" ]]; then
|
||||
ipmask=24
|
||||
fi
|
||||
|
||||
eipaddr=$(echo "$ipaddr" | sed 's/\./\\./g')
|
||||
ifname=$(ip -brief addr show | awk "/$eipaddr\// {print \$1}")
|
||||
if [[ "$ifname" == "" ]]; then
|
||||
ping -c 1 $ipaddr &>/dev/null
|
||||
if [[ $? -eq 0 ]]; then
|
||||
echo "It looks like you specified an already allocated ip address." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$ifname" == "" ]]; then
|
||||
passed=0
|
||||
if [[ "$ipmask" == "" ]]; then
|
||||
passed=1
|
||||
fi
|
||||
if [[ "$iface" == "" ]]; then
|
||||
passed=2
|
||||
fi
|
||||
if [[ $passed -ne 0 ]]; then
|
||||
echo "To create an interface, specify ipaddr=<ip address> ipmask=<mask> iface=<interface name>" >&2
|
||||
echo "Example: ipaddr=192.168.1.222 ipmask=24 iface=en0" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Creating $ipaddr/$ipmask on interface $iface"
|
||||
sudo ip addr add $ipaddr/$ipmask dev $iface
|
||||
fi
|
||||
|
||||
if [[ "$domain" == "" ]]; then
|
||||
domain="TEST.EXAMPLE.COM"
|
||||
fi
|
||||
if [[ "$defaultdns" == "" ]]; then
|
||||
defaultdns="192.168.1.1"
|
||||
fi
|
||||
|
||||
echo "Running Samba AD, listening on $ipaddr for domain $domain"
|
||||
docker run -d
|
||||
--name ad \
|
||||
--hostname DC1 \
|
||||
--restart unless-stopped \
|
||||
-e REALM='TEST.LOCAL' \
|
||||
-e DOMAIN='TEST' \
|
||||
-e ADMIN_PASS='MySecret2026!' \
|
||||
-e DNS_FORWARDER='8.8.8.8' \
|
||||
-v dc_etc:/usr/local/samba/etc \
|
||||
-v dc_private:/usr/local/samba/private \
|
||||
-v dc_var:/usr/local/samba/var \
|
||||
-p $ipaddr:53:53 \
|
||||
-p $ipaddr:53:53/udp \
|
||||
-p $ipaddr:88:88 \
|
||||
-p $ipaddr:88:88/udp \
|
||||
-p $ipaddr:135:135 \
|
||||
-p $ipaddr:137-138:137-138/udp \
|
||||
-p $ipaddr:139:139 \
|
||||
-p $ipaddr:389:389 \
|
||||
-p $ipaddr:389:389/udp \
|
||||
-p $ipaddr:445:445 \
|
||||
-p $ipaddr:464:464 \
|
||||
-p $ipaddr:464:464/udp \
|
||||
-p $ipaddr:636:636 \
|
||||
-p $ipaddr:1024-1044:1024-1044 \
|
||||
-p $ipaddr:3268-3269:3268-3269 \
|
||||
diegogslomp/samba-ad-dc
|
||||
@@ -0,0 +1,33 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFtDCCA5ygAwIBAgIEhCJsaTANBgkqhkiG9w0BAQsFADB6MR0wGwYDVQQKExRT
|
||||
YW1iYSBBZG1pbmlzdHJhdGlvbjE3MDUGA1UECxMuU2FtYmEgLSB0ZW1wb3Jhcnkg
|
||||
YXV0b2dlbmVyYXRlZCBDQSBjZXJ0aWZpY2F0ZTEgMB4GA1UEAxMXVEVTVERDLnRl
|
||||
c3QuZXhhbXBsZS5jb20wHhcNMjYwMTE4MDAwMDA0WhcNMjcxMjE5MDAwMDA0WjB6
|
||||
MR0wGwYDVQQKExRTYW1iYSBBZG1pbmlzdHJhdGlvbjE3MDUGA1UECxMuU2FtYmEg
|
||||
LSB0ZW1wb3JhcnkgYXV0b2dlbmVyYXRlZCBDQSBjZXJ0aWZpY2F0ZTEgMB4GA1UE
|
||||
AxMXVEVTVERDLnRlc3QuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4IC
|
||||
DwAwggIKAoICAQDuYBt2jNruSR4xL0wI8Nx2uq+jQKpiViWzDPw4eivNrLXc8Bdr
|
||||
o9GqCo4uokPZiFS6jGez3holnMKF1bxU8uES2ma228zRIJVAHo0qU2O8zGoMtAuL
|
||||
Z+F9gZAVSQKUMp0Vl2yyNERY453Ne4B2Oz92G87YtNuDyNYzuK4778FiE5iXjvan
|
||||
CsAENXrspfxvn8QD5IloHPFoieFkfeaigDA6Zjtr2be+LhDbc2wn6YY7t8Z+5wxG
|
||||
UPUy8oYKQ5lyHSn6I7aezAJpJJHSN4eSg76smTMjUOAnLqJJHPurGF9zzq+maRg+
|
||||
M2runjqRcnZzQtkdZJayK8GQ39kEFpHnl1PueexFEIA5PxHaJEbapAiL7nP65bAy
|
||||
Ey6wiOVvuLgztf8CU6hT/Not3qVG5lCGiK1VC9W684X5vQcVnQXTAIY3Edt91u8C
|
||||
/wt4i5j58orVbcghAZMsU3FKjjqyvQMMSD2Pwgf6+m2mVQIAnZOrkErsQrltmFxO
|
||||
CPBbRY+5Yg8v2ZucMWfKNy4IEzm8LyddrZKAENbW73/bZtPCw7cJqeb6ZaWVzrqW
|
||||
BAgMiyrBYq/oGnXP6rHnTpB02LWTx8dvPPmEgvPhjecCRKroesYeKX38KWywGalA
|
||||
xXbs+Eo9ApGst7qhkenAzM3E7Fy56OoELUAKGQW9gtVEnn+8BofLhezUyQIDAQAB
|
||||
o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU
|
||||
ShGTEQzxmK0IrRMPiPYbRxfntYQwDQYJKoZIhvcNAQELBQADggIBAFxD6ShgnqwK
|
||||
6oMzXKZOXoVfVu8yFKhL9Z3ui+6Q/duckzLf873sMlxz9msLTjH1O2Pk9gaND9/l
|
||||
25t37BvVLuNTJKHvTfte53l8fQUrqCgOq2feARoXyCAA77bTUh+uSGsjZYGbSJx/
|
||||
M93fOtabElD9T3o3B0L2EJyL7LHZ3in9MOyVBBcCnOMqi+1eoQEnaoj5TRnE8eEu
|
||||
RuTs7Vu3lceZuCvH1mTRPis2D8bPCWa3aAI6w+LZoJNP3B1izo6Arjje7TBHBMGJ
|
||||
A65o9LP1+OeZ5EA+J3nIkU5rSHu+Pk5biBNEThsnkGjEhSTXZPCVgZRQJOgQyvnf
|
||||
LPWUOMdMLsnsO2oW3/BG6zTmbZw4CMzE8yZUtNZxph7+N1ukzYWZbSwDADxxi+dQ
|
||||
vFQk6tguKpxBCko+pWajgNcxG97MFQMMCfGfva7xdaqBSB4R8sdmQm+wknAYQrlm
|
||||
Jc6csvkJgoIHvyBn9yfSKYNvbjh8RUGA2TZMWrbBMdt51ivw6e1NM16OneGtELh+
|
||||
zGlgv0EfJPaf9IG+RWqvMuhGFF1DgqBi8l816XFy4g3SEFC2F0D6VAv9mQZdq8q1
|
||||
9y7B9vpcBQbNiHqJahIEwgp1JlHdoj281Ms2KixBf88O0y7zSMbyejxHtGv8IUVD
|
||||
+Nth1G9FpENenG73njJIh1dLLYyHvxnp
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,30 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFETCCAvmgAwIBAgIUHfUrLa/sMlGeT6VIZdkj73UjZfkwDQYJKoZIhvcNAQEL
|
||||
BQAwGDEWMBQGA1UEAwwNU2FtYmEgVGVzdCBDQTAeFw0yNjAxMTgwMTEzMzVaFw0z
|
||||
NjAxMTYwMTEzMzVaMBgxFjAUBgNVBAMMDVNhbWJhIFRlc3QgQ0EwggIiMA0GCSqG
|
||||
SIb3DQEBAQUAA4ICDwAwggIKAoICAQC464FD54YJ1rgi/5Z9DQrIbEzED8NhMjo/
|
||||
ndqD+0sfqkiYjQEqAh7Wpj+6r9D9AZ1wnTdF2Ee767OTwkSA2lG5aQAF9HDFzgIJ
|
||||
8yVbeqJIABOzKotLTl1wCNeRx+YeY69xcytzsQ6iNFyOVV/CbkbxtyJvDfxD9rC5
|
||||
Pwf1A7yBLz0H8U+kXBKC0SCDSW4+SAgmJQu3NQ7wd2eg+EBH/cPVhw0F97K3Yfb0
|
||||
c8wekQdqAMrvfo/jq81/JADUEmzi1lyQ34zX2ToBIeu42/oTBmJ8ZX2ZAFgUu0y1
|
||||
Kh+rmmYLhszPLfwB/28j1wizd/iVdpN0+ssJj3y+rEgKeXFfo1x7KSWprHUCJzZk
|
||||
+T7l59tEg7ZiSuRexgkc3Rf+4DYNq7JzCFnVnBvhC++fym29JRrriX0DOUYMGAS3
|
||||
dFBiLJT65kdLCwrgkCAZTbsNKTVkEQx3QYn58dUYMTmQXndg4AEG0VUa4s+oqOFu
|
||||
jokyeqVqp37CUm0MU9cKJVnkMqWkqI9JRTBe90xHSy5xrmB0N1zM/FbUTBvMCh6Z
|
||||
t7ZAiB3gB1LTXaDRWafJ+opYJtPkVhvAlmFjEwuYCYdUM8lz+i/MKK8hrAoztUvr
|
||||
C2n5qoDUX5Tm9VygefWVxzCvyzTy+FKEq+4aU8ye0zohCJ+UP1SMi3j98aGdVjBJ
|
||||
sI9KhOQNmQIDAQABo1MwUTAdBgNVHQ4EFgQUgFOZGLtmwudA8tVaKeLvowVQLMYw
|
||||
HwYDVR0jBBgwFoAUgFOZGLtmwudA8tVaKeLvowVQLMYwDwYDVR0TAQH/BAUwAwEB
|
||||
/zANBgkqhkiG9w0BAQsFAAOCAgEAPNmj5RCacwRbySYERx8XBlcV50DRFnFS51G0
|
||||
EEKObiOBxt/xboAt/bPw+6dVeqS0JILrgMJhHn7dufRTdnGK/z9VkRDrTh6FudAk
|
||||
16qdhglXzPrPzETGl1VHJxQEkqm+qkUeD4r9waUP5ZIXdsUrvGdnCaXgQRA3v2RD
|
||||
NDvNldmgCTScjTPM4ox0B2iL0P0Uo4pRzQTGuPmubCMv2B8EkBOPIQx/TO2svL6M
|
||||
oal/25v06+xUEyVAxWQPD4xolL8ksEjdqa7l5h/1YkpouWq+jaEDpWGzVqt/fLYI
|
||||
cdGUxSNcaueEmHB2QOklrTdDVvvczAE98ytDZ/zswj8cKKLhe2d3Z3ie40xx+4/x
|
||||
NprbosIGWrHnz0NbDlzXRvd8yk7ZWk4QFRycJb7Ve3Za3hPOlDeKgm5iCjafCS4G
|
||||
C1D8r+SwGjCjgsaL7J/zY4IHyxurRVBU+jZSRgy2WQlR/BQ2gSdVt3BhxsEPgbmu
|
||||
jKOiPltNkqPmUHoXFnosJyAblF8yjYkBPBpbM5luAUiKMYBkdkSA3oSJYK3WuIDl
|
||||
UNcKwcC9sJPu63ciPDtEZlgV5blvMr80PPyJZDofo+KHxWweKxoyXxQoIKCrP53D
|
||||
Av2KGBhIZlJUzNrj4LWMGbf+8BAGL+i1oyM77D/N8L589Y0x1mQMa/quQW+TJ8kH
|
||||
g2wgxQw=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQC464FD54YJ1rgi
|
||||
/5Z9DQrIbEzED8NhMjo/ndqD+0sfqkiYjQEqAh7Wpj+6r9D9AZ1wnTdF2Ee767OT
|
||||
wkSA2lG5aQAF9HDFzgIJ8yVbeqJIABOzKotLTl1wCNeRx+YeY69xcytzsQ6iNFyO
|
||||
VV/CbkbxtyJvDfxD9rC5Pwf1A7yBLz0H8U+kXBKC0SCDSW4+SAgmJQu3NQ7wd2eg
|
||||
+EBH/cPVhw0F97K3Yfb0c8wekQdqAMrvfo/jq81/JADUEmzi1lyQ34zX2ToBIeu4
|
||||
2/oTBmJ8ZX2ZAFgUu0y1Kh+rmmYLhszPLfwB/28j1wizd/iVdpN0+ssJj3y+rEgK
|
||||
eXFfo1x7KSWprHUCJzZk+T7l59tEg7ZiSuRexgkc3Rf+4DYNq7JzCFnVnBvhC++f
|
||||
ym29JRrriX0DOUYMGAS3dFBiLJT65kdLCwrgkCAZTbsNKTVkEQx3QYn58dUYMTmQ
|
||||
Xndg4AEG0VUa4s+oqOFujokyeqVqp37CUm0MU9cKJVnkMqWkqI9JRTBe90xHSy5x
|
||||
rmB0N1zM/FbUTBvMCh6Zt7ZAiB3gB1LTXaDRWafJ+opYJtPkVhvAlmFjEwuYCYdU
|
||||
M8lz+i/MKK8hrAoztUvrC2n5qoDUX5Tm9VygefWVxzCvyzTy+FKEq+4aU8ye0zoh
|
||||
CJ+UP1SMi3j98aGdVjBJsI9KhOQNmQIDAQABAoICADiu8APZn2u6pt0LjZytd8Wx
|
||||
9OIQlupfVB2WDJpyWDfPocspRdcTqVJQya43II+9g/42IvBE+NAw0u4TGZ6JzY1R
|
||||
E1G/+SWZHWThZG0jLUV2yJMeEm62CcxC2Zl1NiPQmMHk9MfU2VB9TnXO07isxxBm
|
||||
+6YhGlfW8ZsHKDBBwfGzgjjCDpCqLH5vDlFQG/v5Zo4SwwGYwQiTAXAFSOcIcZQt
|
||||
fp96qwyted669DHWcPhcX9iD6iV2z1oOtjZLo5bw8JT0AZ9UlgQs2q3iuEhGp7/L
|
||||
+HEC+vz34O6Th/u5K1iTLIrBZMNUAkRxMNiaiNd/7nloOv6mPi3Qb5DOYyNW1wzg
|
||||
TlF5fERqz2OeuFqjnEfoE/uFjvBN0bclenuFTCWDeSxEZqni9qjLhnqK4Iot7/D2
|
||||
P148t4UIT8h5l82Wiod05cenlnPfpWbRRdzVXy0NZe1BTf2U8LXRW1ANdJyfVSC6
|
||||
802KPP6noWnaFYfkhIIdD10ZlTd9vTwdcHEYfoJNNbXcwwI/OslGwPB1a+tp+rSd
|
||||
Vr2l8RoF0/28cjsRnDi7hoaMkNIqszXq1WA+d5zOvmOsfuy/32pfnm+rw8HafYNj
|
||||
SNK0t5sh0PCsHrRrCRfTaOIQbFveABcf0uxbM68xSMAIKH8rpTrp4lcEEO5Yrcrj
|
||||
qDFQ7rw+VnRR0Nu4eX+VAoIBAQDbWhvWkZssTNHR8Xs9OtaAiHktEgJbreaRPvCe
|
||||
GgfyuZ+aYj6M9ivY/z99AJqH4pueXWbZuyBHmPp39zvvN44WP6Yc+4EDyDpiRIxi
|
||||
CjofYzbOIMxnMqgXePPWHXv+UA1zaSu1HGF1kMMeU+cT4knQpkQNrbmioYIetOnU
|
||||
BzdKJt/LTK2KRvHn4VN6Tt49ZnIfrAo8UIOZPdpT6qumX/RENw0/66AU220bLtaP
|
||||
xxZNqipjf56wikkQiHbmP85ZW9dPTR2aUtJZkS7nmJhTuH782ES96soPjYREH/fZ
|
||||
K5TdWn7Se6NcdHgoG5W2voqP3GFUyvKxRNLSK/tjN1UGYKx/AoIBAQDX0LSDjqKX
|
||||
nn+kZBHVXmMtEWVu44CijdDvBIkri5rjO5QdEtgk15gxYXwakRiXgmpsDk/fyBEQ
|
||||
n7iIlao697vNh3cWbe9xuBBhccLcmkrt1qKSO9a1qjbh4lZ10F4SLsZrDz1dTWwf
|
||||
Xvlofl1oNbh9a39xDT9Pa6qt2tZLThianqjHXssMsaEQPNrWduJmM8um/OT+CHoJ
|
||||
6XTzX9SZrMOcevPdAWARdqgBohcfMD2LhMkY0HIkZGdP1/FYdAbHqjK+81pvtPYE
|
||||
JNRQLCgWMWqfUnnay6lMwN/0U7u0AHE4futYMqrn8gGl0JuGLZ6o1BDxpf2Thkfv
|
||||
KpDLDcyFMBnnAoIBAQDGfXvDZm8sXzde603B0oRYOyasCifoHi0OMg46gwO+OQqc
|
||||
AVro47o52n/w9aLgY8a+P3baf+Jt55h/BlJN3c/Ngjsm7s+IRJ/QXIdMEEUYMgdd
|
||||
74AHwmcpFmJP1s5lCruhPAI5GT13zU9DGll4PMTe8hWS6ymYaN3+O2d8XitCBnts
|
||||
1C4Veq1cTyHjA4tHtHSBBIUenxz2hmoSlcBOBXZcZBSxSt7JAOZyOqgd4uHj1aAV
|
||||
+V34EqqbM2GkaQipA9qKABZRl7CRLDdti0AMQl9nHazWzqeZ+008+FCnTDChPUqV
|
||||
3RuuhhZU9OE9RJ7OH3ZT7HJAxy6e0P3igieGZeSxAoIBAQCho2ogAmuavZaCTITi
|
||||
1w/WyGm9NN26YhczfaUzHRuDx7K2V8/VQDh3+7iUuUm+6GnGxq1iQRku50Gx4s6d
|
||||
3cAmPqid/vbQqHh0U10MFu+51QDsH68/Xt4+LmRtVwOuAKSRuNdQE1XA5URN1B8n
|
||||
lty36xSHWtGpLU2navkHa9WXsO9ea4wO1Nd6LuoEqQQGYhLM3iqwbS7iT51NTuAP
|
||||
vUrA34HYbFhEnCdXGczRijL+VRS85honUZ/jOCxGAJi3YK0jQWbhtZpxHhh6d6gK
|
||||
fzaJX59ulQ0XNHpIJ5K1TYN/OegQpdpFYIeFZomlZHOppTOPmbAjlvY3lhzeSFZ/
|
||||
BrjPAoIBAQCsJqoFNpJpJFT/gGL6xWS4O158E5GxYXl3raN/dfBMGZnbTr0nHrbC
|
||||
4vyrWaQFVsovea7SZnzfLdyesLuJ1FIUhsal166o/g6Vf5wgDn7LHZ202tt2DokV
|
||||
ECETQTA+VBTXysc7/dwsTohbee7Jg6xZ/qHSdP6nUan9xiC3pOx0G/vT5FaH9kBJ
|
||||
dJ2mQ93n/l1A6q9Hn5AFyoCqniOszVsQ357N9uxakQiX48JKKWG7+BneKtGzN2bu
|
||||
9ArlPSNscNDb4yF2fqksTHvIYbGuJ3icbObjWUKOCQbtpva4Rkrnoqqg5nHM0L+f
|
||||
3tGhl1yrO4/YIpQrp0OgMV1HDYXEOwwI
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
59634A59EBDC029E7D479619E12C1868317CE811
|
||||
@@ -0,0 +1,17 @@
|
||||
[req]
|
||||
default_bits = 2048
|
||||
prompt = no
|
||||
default_md = sha256
|
||||
distinguished_name = dn
|
||||
req_extensions = req_ext
|
||||
|
||||
[dn]
|
||||
CN = TESTDC.test.example.com
|
||||
|
||||
[req_ext]
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = TESTDC.test.example.com
|
||||
DNS.2 = localdc.test.example.com
|
||||
IP.1 = 192.168.1.222
|
||||
@@ -0,0 +1,26 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEUDCCAjigAwIBAgIUWWNKWevcAp59R5YZ4SwYaDF86BEwDQYJKoZIhvcNAQEL
|
||||
BQAwGDEWMBQGA1UEAwwNU2FtYmEgVGVzdCBDQTAeFw0yNjAxMTgwMTE0MDRaFw0y
|
||||
ODA0MjIwMTE0MDRaMCIxIDAeBgNVBAMMF1RFU1REQy50ZXN0LmV4YW1wbGUuY29t
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA04ZWa1/UrP6eAkwvc2oL
|
||||
XCewax+5GWWhIj0XVgglblPNcPbK0dLTienfGnfEYxgrCl4o+H7c+SFYcJ/Sg5jw
|
||||
W4/POs+NWoo3ixj71TYWwK+7bUPt2RbNadCAxo4SejdB0zOjpTRn4dDIMZ0+wrg5
|
||||
Ah3jkTnPmwWjpvt280KkojUz+7mhdKFXBBmyjr/ftWU8C+1nyJt5/tVl7PE0VcOL
|
||||
MBok9djjHlj2aKLCw8Hj0CyL+yYFM4keWvYAguXYjVayMC2w8VffvjjO3Wd7/LWi
|
||||
AkDIAhnKyc1oJ8X4h9XK1irEw8lgWH93jyRx54eG32/C3HuNRQA9oQgJrfaK5YCJ
|
||||
kQIDAQABo4GHMIGEMEIGA1UdEQQ7MDmCF1RFU1REQy50ZXN0LmV4YW1wbGUuY29t
|
||||
ghhsb2NhbGRjLnRlc3QuZXhhbXBsZS5jb22HBMCoAd4wHQYDVR0OBBYEFJDd+Fmx
|
||||
vsrKEvBMXBwtBeFxjVjWMB8GA1UdIwQYMBaAFIBTmRi7ZsLnQPLVWini76MFUCzG
|
||||
MA0GCSqGSIb3DQEBCwUAA4ICAQBwS8/1iAFnopOui1V/qdRBsBTWcXdQS7x1PqVU
|
||||
nJkd+T5AYtLu3tJ+PBO9rfgDNVWVz37GB8CTgr4TpGOt7ruaVSZvw9uMRBxYeeit
|
||||
Kf11gssI2eTZESv8yqsa1DtLheQS0AQ7IVjq9wZSw+i3zQ8lQDvFoCWa8nv+C+YZ
|
||||
cfFSnhfXNRQIM+lxn6vrtZa/GgJh6MTF+vCfLvStveCsTvT7i/w0EzO8NFqGBrsX
|
||||
ldombU/WNketQXQEaLAU6NQVvJCjkC9Wgc+KcOQOV77U7CHi3lsc2TAoGlMuJ1lP
|
||||
zyQWJqHKkuWGODeBSd9FfjoZv81+OGPaCAh7bkOHwqAUjgEIL8SV1fX8i13HTkG5
|
||||
nhbN6XoiezjKixfH/EAQHiKhi6/7nzYltz6bJFHpIfBa097l7Yhx/o/HIdKDWV2S
|
||||
VnLIJMJpf/Qn80led1HQPeRWR1EC+fWhBzOg2mCJJpYkpqaOlHVujZ2c5JLckhxH
|
||||
WuIkicvR+LE07IjgZFWUwCXW/PkDDHtiJ0MD6RAiFzFbnuGGMadv3lUBJBUeKL3P
|
||||
PaDC/pue9AAgbWN9ndg8CTorDR2HHjjOyVTkum5bPL2vChuG3/mx+KHcgwDyEWt8
|
||||
cJTxXNaid928rR0aSPsSd4WNlCTDwXhUpClaVRaip/rcfrhJaaFUe7xWpQe8dkvO
|
||||
hiuYEQ==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,17 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICvDCCAaQCAQAwIjEgMB4GA1UEAwwXVEVTVERDLnRlc3QuZXhhbXBsZS5jb20w
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDThlZrX9Ss/p4CTC9zagtc
|
||||
J7BrH7kZZaEiPRdWCCVuU81w9srR0tOJ6d8ad8RjGCsKXij4ftz5IVhwn9KDmPBb
|
||||
j886z41aijeLGPvVNhbAr7ttQ+3ZFs1p0IDGjhJ6N0HTM6OlNGfh0MgxnT7CuDkC
|
||||
HeOROc+bBaOm+3bzQqSiNTP7uaF0oVcEGbKOv9+1ZTwL7WfIm3n+1WXs8TRVw4sw
|
||||
GiT12OMeWPZoosLDwePQLIv7JgUziR5a9gCC5diNVrIwLbDxV9++OM7dZ3v8taIC
|
||||
QMgCGcrJzWgnxfiH1crWKsTDyWBYf3ePJHHnh4bfb8Lce41FAD2hCAmt9orlgImR
|
||||
AgMBAAGgVTBTBgkqhkiG9w0BCQ4xRjBEMEIGA1UdEQQ7MDmCF1RFU1REQy50ZXN0
|
||||
LmV4YW1wbGUuY29tghhsb2NhbGRjLnRlc3QuZXhhbXBsZS5jb22HBMCoAd4wDQYJ
|
||||
KoZIhvcNAQELBQADggEBAAQGVIMVIWPbCG3blO8iNHetydfz8XWC3w9zYI03QQBv
|
||||
QdiAYZ9sI+9fxZdsBOOt+vSNyqlwuuQYmmMtMJbUezeOQ8Z4zhkWEzTI4ohhOlzV
|
||||
sq+7aE+PNKw8CYvOkL4ssVIDU99Hm4ppJbo6sRa9YAXPDU3z0UJhMiGZL7o11048
|
||||
W8VcWOzyNmEHYL6/+DXwXGiaLiJmMOJiwMPu8TZVnli4Q0k/j7KGdeh8s2MXLwi8
|
||||
NT0ppSxm+MUIm014OEMA/LOPwRVttHca1VkGR15MJQElakWSaaix533p7lpH3Q3Q
|
||||
tKZc7RIWeoXs5wyo7Tx7Tx7QdmNJIzA2gh4tRiQvm1o=
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDThlZrX9Ss/p4C
|
||||
TC9zagtcJ7BrH7kZZaEiPRdWCCVuU81w9srR0tOJ6d8ad8RjGCsKXij4ftz5IVhw
|
||||
n9KDmPBbj886z41aijeLGPvVNhbAr7ttQ+3ZFs1p0IDGjhJ6N0HTM6OlNGfh0Mgx
|
||||
nT7CuDkCHeOROc+bBaOm+3bzQqSiNTP7uaF0oVcEGbKOv9+1ZTwL7WfIm3n+1WXs
|
||||
8TRVw4swGiT12OMeWPZoosLDwePQLIv7JgUziR5a9gCC5diNVrIwLbDxV9++OM7d
|
||||
Z3v8taICQMgCGcrJzWgnxfiH1crWKsTDyWBYf3ePJHHnh4bfb8Lce41FAD2hCAmt
|
||||
9orlgImRAgMBAAECggEAFTygZ5EMWr6zjC/W9xj3Vq4bbciWaRYwNS5PWKa3ugR1
|
||||
lLkKcuzeVTM4YEFpuQ3qSv+vewd4VcDtzrI/zKY/F4pFjYuHZHGKaGTpP9D5kX3z
|
||||
QjdMOtFUbU0Zk2schXhlG6MhyR/iVXnZ55/4dvdPrfE58aY4EZc22jiBYE9ZanmL
|
||||
Nxqby63ntT/8ZqQpi5iu458TrWTAhOqF+0g5Aj8bWnk6xIcauvFuLORfAcg2kig6
|
||||
+/oUZiQ4U65gvBODckADjTtKmVUlBFFc22xsR5pSm08dI06Fc+1ZWSa3smyA/a+d
|
||||
iOx2Bb+wD8zQMq20pm3R7Kx/yaTLy+AubBzc7OYQMwKBgQDxrgQnMzjLpLEBe+kp
|
||||
8DKSUpy06G176Zoz65NP5BiY1S12YSqu9ydapalIKKH32yu6Gzw0JsTDa0z8glDH
|
||||
Z9VeB9ZM3q+jsPrg7pUUQKC+Yn/Msba5a4BLmdO61qAk1/zPsc/bEIxC6HBXuR5k
|
||||
7bYRivARdGtYRqkkvVKnn5uWqwKBgQDgDuhC4u9ZBx2QN+bDyT+Ppqd+nLLZemTF
|
||||
bJRRbfXsMRpgAvQl5f4mTxu/KqtKm/kKGY7F66B1XuSmZqAEsvVw0oM+0+rCDfdk
|
||||
Iir87ZsdpfoRA5x775zaa2GrW7/hhpvBrenD9vNwo6/G927EjMAhhPaOelxpf+dS
|
||||
fXBrTOKQswKBgQDC+pp9KbTzjo4DXX2SCSgzppSo2y+hHaj7pF33wVUa0DnaXK/7
|
||||
8of8eNRm0+lANsFDZz+d730lD8dPnK8hASNBtl7tK2dWT61HlRQO/8jvcDYc5W8+
|
||||
jn02AQexoAAZWWusAI9p9CLExBQGoeHkmWcfJhFic6M6198UNe2eMIVs+wKBgQCR
|
||||
D1W0KDSJ0R+ExYxz6ZFrMZxzToREZQdlAh0scyKqnkfTLVZgSv02VDVEmR15Gm4r
|
||||
6cwTu6+RDbS/PuYO5MYfG0u5m3370QsAG1b1lee9VD4IUew4GTPV/Xuoc6hRHNMu
|
||||
m6gjJXkKzg75pCxkuDOT//+Y0wONki9zqp7j+U0eHQKBgCkyJ/E+hNFlzqFHugaR
|
||||
ZC8ccttxs2ibTK4J861EvjA7zFkbnAHy2NfsrNPKm6t+TJOkeJ4XrST1vfS3aNJk
|
||||
swHRQz8tWwtnJCsvTF5nCXU7dDQPsyxwRHB39Eq9KtXp7F+PD7kaMk8DbYIzcQ55
|
||||
pe3uxsvKvS6R++L2eg3TfdSB
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -3,5 +3,6 @@ package assets
|
||||
import "embed"
|
||||
|
||||
// Content is the embedded assets.
|
||||
//
|
||||
//go:embed *.tgz js/* css/* *.html
|
||||
var Content embed.FS
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type API struct {
|
||||
fileServer http.Handler
|
||||
|
||||
logger zerolog.Logger
|
||||
}
|
||||
|
||||
func (a *API) RegisterEndpoints(router *http.ServeMux) {
|
||||
router.HandleFunc("/", a.assets)
|
||||
router.Handle("/assets/", http.StripPrefix("/assets/", a.fileServer))
|
||||
}
|
||||
|
||||
func (a *API) assets(w http.ResponseWriter, r *http.Request) {
|
||||
a.logger.Info().Str("path", r.URL.Path).Msg("Web")
|
||||
|
||||
if r.URL.Path != "/" {
|
||||
log.Info().Msg("Web 404")
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
a.fileServer.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewAPI(logger zerolog.Logger) *API {
|
||||
a := new(API)
|
||||
|
||||
a.logger = logger
|
||||
a.fileServer = http.FileServer(http.FS(Content))
|
||||
return a
|
||||
}
|
||||
@@ -1,21 +1,20 @@
|
||||
package frontend
|
||||
package assets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"github.com/rs/zerolog"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/glauth/glauth/v2/pkg/assets"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
func TestAPIAssets(t *testing.T) {
|
||||
tt := []struct {
|
||||
Name string
|
||||
Path string
|
||||
@@ -26,7 +25,7 @@ func TestAPI(t *testing.T) {
|
||||
Path: "/",
|
||||
Check: func(t testing.TB, res *http.Response) {
|
||||
if res.StatusCode != http.StatusOK {
|
||||
t.Errorf("unexpected response: %v", res.Status)
|
||||
t.Fatalf("unexpected response: %v", res.Status)
|
||||
return
|
||||
}
|
||||
h := sha256.New()
|
||||
@@ -34,7 +33,7 @@ func TestAPI(t *testing.T) {
|
||||
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)
|
||||
t.Fatalf("bad digest: got: %x, want %x", got, want)
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -42,48 +41,47 @@ func TestAPI(t *testing.T) {
|
||||
Name: "NotFound",
|
||||
Path: "/glauth.js",
|
||||
Check: func(t testing.TB, res *http.Response) {
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return
|
||||
if res.StatusCode != http.StatusNotFound {
|
||||
t.Fatalf("unexpected response: %v", res.Status)
|
||||
}
|
||||
t.Errorf("unexpected response: %v", res.Status)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Content-Type",
|
||||
Path: "/assets/glauth.js",
|
||||
Path: "/assets/js/glauth.js",
|
||||
Check: func(t testing.TB, res *http.Response) {
|
||||
if res.StatusCode != http.StatusOK {
|
||||
t.Errorf("unexpected response: %v", res.Status)
|
||||
t.Fatalf("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)
|
||||
if got, want := res.Header.Get("Content-Type"), `text/javascript; charset=utf-8`; got != want {
|
||||
t.Fatalf("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)
|
||||
if got, want := h.Sum(nil), mkDigest(t, "js/glauth.js"); !bytes.Equal(got, want) {
|
||||
t.Fatalf("bad digest: got: %x, want %x", got, want)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Content-Type",
|
||||
Path: "/assets/glauth.css",
|
||||
Path: "/assets/css/glauth.css",
|
||||
Check: func(t testing.TB, res *http.Response) {
|
||||
if res.StatusCode != http.StatusOK {
|
||||
t.Errorf("unexpected response: %v", res.Status)
|
||||
t.Fatalf("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)
|
||||
t.Fatalf("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)
|
||||
if got, want := h.Sum(nil), mkDigest(t, "css/glauth.css"); !bytes.Equal(got, want) {
|
||||
t.Fatalf("bad digest: got: %x, want %x", got, want)
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -91,7 +89,7 @@ func TestAPI(t *testing.T) {
|
||||
|
||||
mux := http.NewServeMux()
|
||||
log := zerolog.New(os.Stdout).Level(zerolog.InfoLevel)
|
||||
register(mux, log, true, "0.0.0.0:5555")
|
||||
NewAPI(log).RegisterEndpoints(mux)
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
for _, tc := range tt {
|
||||
@@ -112,7 +110,7 @@ func TestAPI(t *testing.T) {
|
||||
}
|
||||
|
||||
func mkDigest(t testing.TB, path string) []byte {
|
||||
f, err := assets.Content.Open(path)
|
||||
f, err := Content.Open(path)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
+148
-115
@@ -3,119 +3,152 @@ 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
|
||||
}
|
||||
type Capability struct {
|
||||
Action string
|
||||
Object string
|
||||
}
|
||||
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 // e.g. cn, ou, uid, or a comma separated list of them
|
||||
NameFormatAsArray []string // we will explode NameFormat on commas
|
||||
GroupFormat string // e.g. cn, ou, gid, or a comma separated list of them
|
||||
GroupFormatAsArray []string // we will explode GroupFormat on commas
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
Frontend struct {
|
||||
AllowedBaseDNs []string // For LDAP backend only
|
||||
Listen string
|
||||
Cert string
|
||||
Key string
|
||||
TLS bool
|
||||
}
|
||||
|
||||
LDAP struct {
|
||||
Enabled bool
|
||||
Listen string
|
||||
// StartTLS parameters
|
||||
TLS bool
|
||||
TLSCert string
|
||||
TLSKey string
|
||||
TLSCertPath string
|
||||
TLSKeyPath string
|
||||
LegacyTLS bool
|
||||
}
|
||||
|
||||
LDAPS struct {
|
||||
Enabled bool
|
||||
Listen string
|
||||
// LDAPS TLS parameters
|
||||
Cert string
|
||||
Key string
|
||||
CertPath string
|
||||
KeyPath string
|
||||
LegacyTLS bool
|
||||
}
|
||||
|
||||
API struct {
|
||||
Cert string
|
||||
Enabled bool
|
||||
Internals bool
|
||||
Key string
|
||||
Listen string
|
||||
SecretToken string
|
||||
TLS bool
|
||||
}
|
||||
|
||||
Behaviors struct {
|
||||
IgnoreCapabilities bool
|
||||
LimitFailedBinds bool
|
||||
NumberOfFailedBinds int
|
||||
PeriodOfFailedBinds time.Duration
|
||||
BlockFailedBindsFor time.Duration
|
||||
PruneSourceTableEvery time.Duration
|
||||
PruneSourcesOlderThan time.Duration
|
||||
LegacyVersion int
|
||||
}
|
||||
|
||||
Capability struct {
|
||||
Action string
|
||||
Object string
|
||||
}
|
||||
|
||||
// UserAuthenticator authenticates a user via custom auth from a backend
|
||||
UserAuthenticator func(user *User, pw string) error
|
||||
User struct {
|
||||
Name string
|
||||
OtherGroups []int
|
||||
PassSHA256 string
|
||||
PassBcrypt string
|
||||
PassAppSHA256 []string
|
||||
PassAppBcrypt []string
|
||||
PassAppCustom UserAuthenticator `toml:"-"`
|
||||
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{}
|
||||
}
|
||||
|
||||
Group struct {
|
||||
Name string
|
||||
UnixID int // TODO: remove after deprecating UnixID on User and Group
|
||||
GIDNumber int
|
||||
Capabilities []Capability
|
||||
IncludeGroups []int
|
||||
}
|
||||
|
||||
Tracing struct {
|
||||
Enabled bool
|
||||
GRPCEndpoint string
|
||||
HTTPEndpoint string
|
||||
}
|
||||
|
||||
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
|
||||
Tracing Tracing
|
||||
ConfigFile string
|
||||
AwsAccessKeyId string
|
||||
AwsSecretAccessKey string
|
||||
AwsRegion string
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ package frontend
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
|
||||
+14
-156
@@ -1,17 +1,10 @@
|
||||
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/internal/monitoring"
|
||||
"github.com/glauth/glauth/v2/pkg/assets"
|
||||
)
|
||||
|
||||
@@ -21,162 +14,27 @@ func RunAPI(opts ...Option) {
|
||||
log := options.Logger
|
||||
cfg := options.Config
|
||||
|
||||
register(http.DefaultServeMux, log, cfg.TLS, cfg.Listen)
|
||||
router := http.DefaultServeMux
|
||||
|
||||
assets.NewAPI(log).RegisterEndpoints(router)
|
||||
monitoring.NewAPI(log).RegisterEndpoints(router)
|
||||
|
||||
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 {
|
||||
|
||||
monitoring.NewCollector(fmt.Sprintf("https://%s/debug/vars", cfg.Listen))
|
||||
if err := http.ListenAndServeTLS(cfg.Listen, cfg.Cert, cfg.Key, nil); 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
|
||||
log.Info().Str("address", cfg.Listen).Msg("Starting HTTP server")
|
||||
monitoring.NewCollector(fmt.Sprintf("http://%s/debug/vars", cfg.Listen))
|
||||
|
||||
if err := http.ListenAndServe(cfg.Listen, nil); err != nil {
|
||||
log.Error().Err(err).Msg("error starting HTTP server")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
+124
-30
@@ -1,17 +1,22 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"net"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/v2/internal/monitoring"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/glauth/v2/pkg/stats"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/glauth/ldap"
|
||||
)
|
||||
|
||||
type configHandler struct {
|
||||
@@ -21,6 +26,9 @@ type configHandler struct {
|
||||
yubikeyAuth *yubigo.YubiAuth
|
||||
ldohelper LDAPOpsHelper
|
||||
attmatcher *regexp.Regexp
|
||||
|
||||
monitor monitoring.MonitorInterface
|
||||
tracer trace.Tracer
|
||||
}
|
||||
|
||||
// NewConfigHandler creates a new config backed handler
|
||||
@@ -34,6 +42,8 @@ func NewConfigHandler(opts ...Option) Handler {
|
||||
yubikeyAuth: options.YubiAuth,
|
||||
ldohelper: options.LDAPHelper,
|
||||
attmatcher: configattributematcher,
|
||||
monitor: options.Monitor,
|
||||
tracer: options.Tracer,
|
||||
}
|
||||
return handler
|
||||
}
|
||||
@@ -52,31 +62,83 @@ func (h configHandler) GetYubikeyAuth() *yubigo.YubiAuth {
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
|
||||
ctx, span := h.tracer.Start(context.Background(), "handler.configHandler.Bind")
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "bind", "status": fmt.Sprintf("%v", result)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
return h.ldohelper.Bind(ctx, 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)
|
||||
ctx, span := h.tracer.Start(context.Background(), "handler.configHandler.Search")
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "search", "status": fmt.Sprintf("%v", result.ResultCode)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
return h.ldohelper.Search(ctx, 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) {
|
||||
_, span := h.tracer.Start(context.Background(), "handler.configHandler.Add")
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "add", "status": fmt.Sprintf("%v", result)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
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) {
|
||||
_, span := h.tracer.Start(context.Background(), "handler.configHandler.Modify")
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "modify", "status": fmt.Sprintf("%v", result)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
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) {
|
||||
_, span := h.tracer.Start(context.Background(), "handler.configHandler.Delete")
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "delete", "status": fmt.Sprintf("%v", result)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||
}
|
||||
|
||||
func (h configHandler) FindUser(userName string, searchByUPN bool) (f bool, u config.User, err error) {
|
||||
func (h configHandler) FindUser(ctx context.Context, userName string, searchByUPN bool) (f bool, u config.User, err error) {
|
||||
ctx, span := h.tracer.Start(ctx, "handler.configHandler.FindUser")
|
||||
defer span.End()
|
||||
user := config.User{}
|
||||
found := false
|
||||
|
||||
@@ -97,7 +159,9 @@ func (h configHandler) FindUser(userName string, searchByUPN bool) (f bool, u co
|
||||
return found, user, nil
|
||||
}
|
||||
|
||||
func (h configHandler) FindGroup(groupName string) (f bool, g config.Group, err error) {
|
||||
func (h configHandler) FindGroup(ctx context.Context, groupName string) (f bool, g config.Group, err error) {
|
||||
ctx, span := h.tracer.Start(ctx, "handler.configHandler.FindGroup")
|
||||
defer span.End()
|
||||
// TODO Does g get erased, and above does u get erased?
|
||||
// TODO and what about f?
|
||||
group := config.Group{}
|
||||
@@ -111,13 +175,17 @@ func (h configHandler) FindGroup(groupName string) (f bool, g config.Group, err
|
||||
return found, group, nil
|
||||
}
|
||||
|
||||
func (h configHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.Entry, err error) {
|
||||
func (h configHandler) FindPosixAccounts(ctx context.Context, hierarchy string) (entrylist []*ldap.Entry, err error) {
|
||||
ctx, span := h.tracer.Start(ctx, "handler.configHandler.FindPosixAccounts")
|
||||
defer span.End()
|
||||
|
||||
entries := []*ldap.Entry{}
|
||||
|
||||
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}})
|
||||
for _, nameAttr := range h.backend.NameFormatAsArray {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: nameAttr, Values: []string{u.Name}})
|
||||
}
|
||||
|
||||
if len(u.GivenName) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "givenName", Values: []string{u.GivenName}})
|
||||
@@ -127,7 +195,7 @@ func (h configHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.En
|
||||
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: "ou", Values: []string{h.getGroupName(ctx, u.PrimaryGroup)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uidNumber", Values: []string{fmt.Sprintf("%d", u.UIDNumber)}})
|
||||
|
||||
if u.Disabled {
|
||||
@@ -158,7 +226,7 @@ func (h configHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.En
|
||||
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: "memberOf", Values: h.getGroupDNs(ctx, 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"}})
|
||||
@@ -193,9 +261,9 @@ func (h configHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.En
|
||||
}
|
||||
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)
|
||||
dn = fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormatAsArray[0], u.Name, h.backend.GroupFormatAsArray[0], h.getGroupName(ctx, 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)
|
||||
dn = fmt.Sprintf("%s=%s,%s=%s,%s,%s", h.backend.NameFormatAsArray[0], u.Name, h.backend.GroupFormatAsArray[0], h.getGroupName(ctx, u.PrimaryGroup), hierarchy, h.backend.BaseDN)
|
||||
}
|
||||
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
|
||||
}
|
||||
@@ -203,25 +271,30 @@ func (h configHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.En
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (h configHandler) FindPosixGroups(hierarchy string) (entrylist []*ldap.Entry, err error) {
|
||||
func (h configHandler) FindPosixGroups(ctx context.Context, hierarchy string) (entrylist []*ldap.Entry, err error) {
|
||||
ctx, span := h.tracer.Start(ctx, "handler.configHandler.FindPosixGroups")
|
||||
defer span.End()
|
||||
|
||||
asGroupOfUniqueNames := hierarchy == "ou=groups"
|
||||
|
||||
entries := []*ldap.Entry{}
|
||||
|
||||
for _, g := range h.cfg.Groups {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{g.Name}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{g.Name}})
|
||||
for _, groupAttr := range h.backend.GroupFormatAsArray {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: groupAttr, 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)})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uniqueMember", Values: h.getGroupMemberDNs(ctx, 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: "memberUid", Values: h.getGroupMemberIDs(ctx, 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)
|
||||
dn := fmt.Sprintf("%s=%s,%s,%s", h.backend.GroupFormatAsArray[0], g.Name, hierarchy, h.backend.BaseDN)
|
||||
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
|
||||
}
|
||||
|
||||
@@ -230,20 +303,32 @@ func (h configHandler) FindPosixGroups(hierarchy string) (entrylist []*ldap.Entr
|
||||
|
||||
// Close does not actually close anything, because the config data is kept in memory
|
||||
func (h configHandler) Close(boundDn string, conn net.Conn) error {
|
||||
_, span := h.tracer.Start(context.Background(), "handler.configHandler.Close")
|
||||
defer span.End()
|
||||
|
||||
stats.Frontend.Add("closes", 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h configHandler) getGroupMemberDNs(gid int) []string {
|
||||
func (h configHandler) getGroupMemberDNs(ctx context.Context, gid int) []string {
|
||||
ctx, span := h.tracer.Start(ctx, "handler.configHandler.getGroupMemberDNs")
|
||||
defer span.End()
|
||||
|
||||
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", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormatAsArray[0], u.Name, h.backend.GroupFormatAsArray[0], h.getGroupName(ctx, 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", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormatAsArray[0], u.Name, h.backend.GroupFormatAsArray[0], h.getGroupName(ctx, u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
|
||||
members[dn] = true
|
||||
}
|
||||
}
|
||||
@@ -254,7 +339,7 @@ func (h configHandler) getGroupMemberDNs(gid int) []string {
|
||||
if gid == g.GIDNumber {
|
||||
for _, includegroupid := range g.IncludeGroups {
|
||||
if includegroupid != gid {
|
||||
includegroupmembers := h.getGroupMemberDNs(includegroupid)
|
||||
includegroupmembers := h.getGroupMemberDNs(ctx, includegroupid)
|
||||
|
||||
for _, includegroupmember := range includegroupmembers {
|
||||
members[includegroupmember] = true
|
||||
@@ -274,7 +359,10 @@ func (h configHandler) getGroupMemberDNs(gid int) []string {
|
||||
return m
|
||||
}
|
||||
|
||||
func (h configHandler) getGroupMemberIDs(gid int) []string {
|
||||
func (h configHandler) getGroupMemberIDs(ctx context.Context, gid int) []string {
|
||||
ctx, span := h.tracer.Start(ctx, "handler.configHandler.getGroupMemberIDs")
|
||||
defer span.End()
|
||||
|
||||
members := make(map[string]bool)
|
||||
for _, u := range h.cfg.Users {
|
||||
if u.PrimaryGroup == gid {
|
||||
@@ -294,7 +382,7 @@ func (h configHandler) getGroupMemberIDs(gid int) []string {
|
||||
if includegroupid == gid {
|
||||
h.log.Warn().Int("groupid", includegroupid).Msg("Ignoring myself as included group")
|
||||
} else {
|
||||
includegroupmemberids := h.getGroupMemberIDs(includegroupid)
|
||||
includegroupmemberids := h.getGroupMemberIDs(ctx, includegroupid)
|
||||
|
||||
for _, includegroupmemberid := range includegroupmemberids {
|
||||
members[includegroupmemberid] = true
|
||||
@@ -315,18 +403,21 @@ func (h configHandler) getGroupMemberIDs(gid int) []string {
|
||||
}
|
||||
|
||||
// Converts an array of GUIDs into an array of DNs
|
||||
func (h configHandler) getGroupDNs(gids []int) []string {
|
||||
func (h configHandler) getGroupDNs(ctx context.Context, gids []int) []string {
|
||||
ctx, span := h.tracer.Start(ctx, "handler.configHandler.getGroupDNs")
|
||||
defer span.End()
|
||||
|
||||
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)
|
||||
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormatAsArray[0], 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})
|
||||
includegroupdns := h.getGroupDNs(ctx, []int{g.GIDNumber})
|
||||
|
||||
for _, includegroupdn := range includegroupdns {
|
||||
groups[includegroupdn] = true
|
||||
@@ -346,7 +437,10 @@ func (h configHandler) getGroupDNs(gids []int) []string {
|
||||
return g
|
||||
}
|
||||
|
||||
func (h configHandler) getGroupName(gid int) string {
|
||||
func (h configHandler) getGroupName(ctx context.Context, gid int) string {
|
||||
ctx, span := h.tracer.Start(ctx, "handler.configHandler.getGroupName")
|
||||
defer span.End()
|
||||
|
||||
for _, g := range h.cfg.Groups {
|
||||
if g.GIDNumber == gid {
|
||||
return g.Name
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/ldap"
|
||||
)
|
||||
|
||||
type disabledBindTestMonitor struct{}
|
||||
|
||||
func (disabledBindTestMonitor) SetResponseTimeMetric(map[string]string, float64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (disabledBindTestMonitor) SetLDAPMetric(map[string]string, float64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestConfigBackendDisabledUserCannotBind(t *testing.T) {
|
||||
pw := "correct horse"
|
||||
raw, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.MinCost)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hexHash := hex.EncodeToString(raw)
|
||||
|
||||
cfg := &config.Config{}
|
||||
cfg.Users = []config.User{
|
||||
{Name: "activeuser", PrimaryGroup: 5501, PassBcrypt: hexHash, Disabled: false},
|
||||
{Name: "disableduser", PrimaryGroup: 5501, PassBcrypt: hexHash, Disabled: true},
|
||||
}
|
||||
|
||||
logger := zerolog.Nop()
|
||||
tracer := noop.NewTracerProvider().Tracer("f001")
|
||||
helper := NewLDAPOpsHelper(tracer)
|
||||
h := configHandler{
|
||||
backend: config.Backend{
|
||||
BaseDN: "dc=example,dc=com",
|
||||
NameFormatAsArray: []string{"cn"},
|
||||
GroupFormatAsArray: []string{"ou"},
|
||||
},
|
||||
log: &logger,
|
||||
cfg: cfg,
|
||||
ldohelper: helper,
|
||||
monitor: disabledBindTestMonitor{},
|
||||
tracer: tracer,
|
||||
}
|
||||
_, server := net.Pipe()
|
||||
defer server.Close()
|
||||
|
||||
bind := func(dn, pass string) ldap.LDAPResultCode {
|
||||
code, _ := h.Bind(dn, pass, server)
|
||||
return code
|
||||
}
|
||||
|
||||
if code := bind("cn=activeuser,dc=example,dc=com", pw); code != ldap.LDAPResultSuccess {
|
||||
t.Fatalf("active user with correct pw should bind, got %d", code)
|
||||
}
|
||||
if code := bind("cn=disableduser,dc=example,dc=com", pw); code != ldap.LDAPResultInvalidCredentials {
|
||||
t.Errorf("disabled user must be rejected; got code %d, want InvalidCredentials(49)", code)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/glauth/ldap"
|
||||
)
|
||||
|
||||
type HelperMaker interface {
|
||||
FindUser(userName string, searchByUPN bool) (bool, config.User, error)
|
||||
FindGroup(groupName string) (bool, config.Group, error)
|
||||
FindUser(ctx context.Context, userName string, searchByUPN bool) (bool, config.User, error)
|
||||
FindGroup(ctx context.Context, groupName string) (bool, config.Group, error)
|
||||
}
|
||||
|
||||
// Handler is the common interface for all datastores
|
||||
|
||||
+108
-18
@@ -1,11 +1,11 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -15,9 +15,13 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/glauth/glauth/v2/internal/monitoring"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/glauth/v2/pkg/stats"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/glauth/ldap"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
@@ -34,6 +38,9 @@ type ldapHandler struct {
|
||||
servers []ldapBackend
|
||||
helper Handler
|
||||
attm *regexp.Regexp
|
||||
|
||||
monitor monitoring.MonitorInterface
|
||||
tracer trace.Tracer
|
||||
}
|
||||
|
||||
// global lock for ldapHandler sessions & servers manipulation
|
||||
@@ -71,6 +78,8 @@ func NewLdapHandler(opts ...Option) Handler {
|
||||
helper: options.Helper,
|
||||
lock: &ldaplock,
|
||||
attm: ldapattributematcher,
|
||||
monitor: options.Monitor,
|
||||
tracer: options.Tracer,
|
||||
}
|
||||
// parse LDAP URLs
|
||||
for _, ldapurl := range handler.backend.Servers {
|
||||
@@ -88,8 +97,18 @@ func NewLdapHandler(opts ...Option) Handler {
|
||||
return handler
|
||||
}
|
||||
|
||||
//
|
||||
func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
||||
func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
|
||||
ctx, span := h.tracer.Start(context.Background(), "handler.ldapHandler.Bind")
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "bind", "status": fmt.Sprintf("%v", result)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
|
||||
h.log.Debug().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind request")
|
||||
|
||||
// if h.helper != nil {
|
||||
@@ -98,17 +117,19 @@ func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCod
|
||||
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+"=")
|
||||
userName := strings.TrimPrefix(parts[0], h.backend.NameFormatAsArray[0]+"=")
|
||||
|
||||
validotp := false
|
||||
|
||||
// Local records augment proxy authentication with OTP/YubiKey checks;
|
||||
// password authentication remains delegated to the upstream LDAP server.
|
||||
// Find the user
|
||||
// We are going to go through all backends and ask
|
||||
// until we find our user or die of boredom.
|
||||
user := config.User{}
|
||||
found := false
|
||||
for i, handler := range h.handlers.Handlers {
|
||||
found, user, _ = handler.FindUser(userName, false)
|
||||
found, user, _ = handler.FindUser(ctx, userName, false)
|
||||
if found {
|
||||
break
|
||||
}
|
||||
@@ -154,8 +175,18 @@ func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCod
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
//
|
||||
func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
||||
ctx, span := h.tracer.Start(context.Background(), "handler.ldapHandler.Search")
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "search", "status": fmt.Sprintf("%v", result.ResultCode)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
|
||||
wantAttributes := true
|
||||
wantTypesOnly := false
|
||||
|
||||
@@ -181,6 +212,20 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
|
||||
stats.Frontend.Add("search_ldapSession_errors", 1)
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, nil
|
||||
}
|
||||
/*
|
||||
delete_idx := -1
|
||||
pagingSize := uint32(0)
|
||||
for idx, control := range searchReq.Controls {
|
||||
if control.GetControlType() == ldap.ControlTypePaging {
|
||||
fmt.Println(control.GetControlType())
|
||||
pagingSize = control.(*ldap.ControlPaging).PagingSize
|
||||
delete_idx = idx
|
||||
}
|
||||
}
|
||||
if delete_idx >= 0 {
|
||||
searchReq.Controls = append(searchReq.Controls[:delete_idx], searchReq.Controls[delete_idx+1:]...)
|
||||
}
|
||||
*/
|
||||
search := ldap.NewSearchRequest(
|
||||
searchReq.BaseDN,
|
||||
searchReq.Scope,
|
||||
@@ -194,6 +239,15 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
|
||||
)
|
||||
|
||||
h.log.Debug().Interface("request", search).Msg("Search request to backend")
|
||||
/*
|
||||
var sr *ldap.SearchResult
|
||||
if pagingSize > 0 {
|
||||
fmt.Printf("Searching with page size == %d\n", pagingSize)
|
||||
sr, err = s.ldap.SearchWithPaging(search, pagingSize)
|
||||
} else {
|
||||
sr, err = s.ldap.Search(search)
|
||||
}
|
||||
*/
|
||||
sr, err := s.ldap.Search(search)
|
||||
h.log.Debug().Interface("result", sr).Msg("Backend Search result")
|
||||
|
||||
@@ -235,7 +289,7 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
|
||||
h.log.Debug().Str("type", "Root search detected").Msg("AP: Search Info")
|
||||
}
|
||||
|
||||
filters := h.buildReqAttributesList(searchReq.Filter, []string{})
|
||||
filters := h.buildReqAttributesList(ctx, searchReq.Filter, []string{})
|
||||
|
||||
for _, filter := range filters {
|
||||
attbits := h.attm.FindStringSubmatch(filter)
|
||||
@@ -257,9 +311,10 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
|
||||
}
|
||||
|
||||
ssr := ldap.ServerSearchResult{
|
||||
Entries: sr.Entries,
|
||||
Referrals: sr.Referrals,
|
||||
Controls: sr.Controls,
|
||||
Entries: sr.Entries,
|
||||
Referrals: sr.Referrals,
|
||||
Controls: sr.Controls,
|
||||
ResultCode: ldap.LDAPResultSuccess,
|
||||
}
|
||||
h.log.Debug().Interface("result", ssr).Msg("Frontend Search result")
|
||||
if err != nil {
|
||||
@@ -274,7 +329,10 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
|
||||
return ssr, nil
|
||||
}
|
||||
|
||||
func (h ldapHandler) buildReqAttributesList(filter string, filters []string) []string {
|
||||
func (h ldapHandler) buildReqAttributesList(ctx context.Context, filter string, filters []string) []string {
|
||||
ctx, span := h.tracer.Start(ctx, "handler.ldapHandler.buildReqAttributesList")
|
||||
defer span.End()
|
||||
|
||||
maxp := len(filter)
|
||||
start := -1
|
||||
descended := false
|
||||
@@ -286,7 +344,7 @@ func (h ldapHandler) buildReqAttributesList(filter string, filters []string) []s
|
||||
} else if c == ')' {
|
||||
if start > -1 {
|
||||
descended = true
|
||||
filters = h.buildReqAttributesList(filter[start:p], filters)
|
||||
filters = h.buildReqAttributesList(ctx, filter[start:p], filters)
|
||||
}
|
||||
start = -1
|
||||
}
|
||||
@@ -299,24 +357,59 @@ func (h ldapHandler) buildReqAttributesList(filter string, filters []string) []s
|
||||
|
||||
// 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) {
|
||||
_, span := h.tracer.Start(context.Background(), "handler.ldapHandler.Add")
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "add", "status": fmt.Sprintf("%v", result)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
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) {
|
||||
_, span := h.tracer.Start(context.Background(), "handler.ldapHandler.Modify")
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "modify", "status": fmt.Sprintf("%v", result)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
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) {
|
||||
_, span := h.tracer.Start(context.Background(), "handler.ldapHandler.Delete")
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "delete", "status": fmt.Sprintf("%v", result)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||
}
|
||||
|
||||
func (h ldapHandler) FindUser(userName string, searchByUPN bool) (found bool, user config.User, err error) {
|
||||
func (h ldapHandler) FindUser(ctx context.Context, userName string, searchByUPN bool) (found bool, user config.User, err error) {
|
||||
ctx, span := h.tracer.Start(ctx, "handler.ldapHandler.FindUser")
|
||||
defer span.End()
|
||||
return false, config.User{}, nil
|
||||
}
|
||||
|
||||
func (h ldapHandler) FindGroup(groupName string) (found bool, group config.Group, err error) {
|
||||
func (h ldapHandler) FindGroup(ctx context.Context, groupName string) (found bool, group config.Group, err error) {
|
||||
ctx, span := h.tracer.Start(ctx, "handler.ldapHandler.FindGroup")
|
||||
defer span.End()
|
||||
|
||||
return false, config.Group{}, nil
|
||||
}
|
||||
|
||||
@@ -362,7 +455,6 @@ func (h *ldapHandler) monitorServers() {
|
||||
}()
|
||||
}
|
||||
|
||||
//
|
||||
func (h ldapHandler) getSession(conn net.Conn) (ldapSession, error) {
|
||||
id := connID(conn)
|
||||
h.lock.Lock()
|
||||
@@ -399,7 +491,6 @@ func (h ldapHandler) getSession(conn net.Conn) (ldapSession, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
//
|
||||
func (h ldapHandler) ping() error {
|
||||
healthy := false
|
||||
for k, s := range h.servers {
|
||||
@@ -442,7 +533,6 @@ func (h ldapHandler) ping() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
func (h ldapHandler) getBestServer() (ldapBackend, error) {
|
||||
favorite := ldapBackend{}
|
||||
forever, err := time.ParseDuration("30m")
|
||||
|
||||
+157
-71
@@ -2,11 +2,10 @@ package handler
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -14,10 +13,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/glauth/v2/pkg/stats"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/glauth/ldap"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
@@ -31,10 +33,10 @@ type LDAPOpsHandler interface {
|
||||
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)
|
||||
FindUser(ctx context.Context, userName string, searchByUPN bool) (f bool, u config.User, err error)
|
||||
FindGroup(ctx context.Context, groupName string) (f bool, g config.Group, err error)
|
||||
FindPosixAccounts(ctx context.Context, hierarchy string) (entrylist []*ldap.Entry, err error)
|
||||
FindPosixGroups(ctx context.Context, hierarchy string) (entrylist []*ldap.Entry, err error)
|
||||
}
|
||||
|
||||
type failedBind struct {
|
||||
@@ -50,18 +52,28 @@ type sourceInfo struct {
|
||||
type LDAPOpsHelper struct {
|
||||
sources map[string]*sourceInfo
|
||||
nextPruning time.Time
|
||||
|
||||
tracer trace.Tracer
|
||||
}
|
||||
|
||||
func NewLDAPOpsHelper() LDAPOpsHelper {
|
||||
func NewLDAPOpsHelper(tracer trace.Tracer) LDAPOpsHelper {
|
||||
helper := LDAPOpsHelper{
|
||||
sources: make(map[string]*sourceInfo),
|
||||
nextPruning: time.Now(),
|
||||
tracer: tracer,
|
||||
}
|
||||
return helper
|
||||
}
|
||||
|
||||
func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
||||
if l.isInTimeout(h, conn) {
|
||||
func isUnauthenticatedSimpleBind(bindDN, bindSimplePw string) bool {
|
||||
return bindDN != "" && bindSimplePw == ""
|
||||
}
|
||||
|
||||
func (l LDAPOpsHelper) Bind(ctx context.Context, h LDAPOpsHandler, bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.Bind")
|
||||
defer span.End()
|
||||
|
||||
if l.isInTimeout(ctx, h, conn) {
|
||||
return ldap.LDAPResultUnwillingToPerform, nil
|
||||
}
|
||||
|
||||
@@ -78,11 +90,24 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
user, ldapcode := l.findUser(h, bindDN, true /* checkGroup */)
|
||||
// A non-empty DN with an empty password is LDAP's unauthenticated bind
|
||||
// mechanism. It must not establish the named user's authorization state.
|
||||
if isUnauthenticatedSimpleBind(bindDN, bindSimplePw) {
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("unauthenticated bind rejected")
|
||||
return ldap.LDAPResultUnwillingToPerform, nil
|
||||
}
|
||||
|
||||
user, ldapcode := l.findUser(ctx, h, bindDN, true /* checkGroup */)
|
||||
if ldapcode != ldap.LDAPResultSuccess {
|
||||
return ldapcode, nil
|
||||
}
|
||||
|
||||
// a disabled account must never authenticate, on any backend
|
||||
if user.Disabled {
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind attempt on disabled account")
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
validotp := false
|
||||
|
||||
if len(user.Yubikey) == 0 && len(user.OTPSecret) == 0 {
|
||||
@@ -167,6 +192,15 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
// Reaching this point without a configured primary password verifier must
|
||||
// not be treated as successful authentication. App-password matches and
|
||||
// custom authentication return above when they succeed.
|
||||
if user.PassBcrypt == "" && user.PassSHA256 == "" {
|
||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("invalid credentials")
|
||||
l.maybePutInTimeout(ctx, h, conn, true)
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
// Now, check the pasword hash
|
||||
if user.PassBcrypt != "" {
|
||||
decoded, err := hex.DecodeString(user.PassBcrypt)
|
||||
@@ -176,7 +210,7 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
|
||||
}
|
||||
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)
|
||||
l.maybePutInTimeout(ctx, h, conn, true)
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
}
|
||||
@@ -185,7 +219,7 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
|
||||
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)
|
||||
l.maybePutInTimeout(ctx, h, conn, true)
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
}
|
||||
@@ -210,8 +244,11 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
|
||||
* 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) {
|
||||
func (l LDAPOpsHelper) Search(ctx context.Context, h LDAPOpsHandler, bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.Search")
|
||||
defer span.End()
|
||||
|
||||
if l.isInTimeout(ctx, h, conn) {
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultUnwillingToPerform}, fmt.Errorf("Source is in a timeout")
|
||||
}
|
||||
|
||||
@@ -225,7 +262,7 @@ func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.Se
|
||||
var ldapcode ldap.LDAPResultCode
|
||||
|
||||
if !anonymous {
|
||||
if bindDN, boundUser, ldapcode = l.searchCheckBindDN(h, baseDN, bindDN, anonymous); ldapcode != ldap.LDAPResultSuccess {
|
||||
if bindDN, boundUser, ldapcode = l.searchCheckBindDN(ctx, h, baseDN, bindDN, anonymous); ldapcode != ldap.LDAPResultSuccess {
|
||||
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Search Error: Potential bypass of BindDN %s", bindDN)
|
||||
}
|
||||
}
|
||||
@@ -233,7 +270,7 @@ func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.Se
|
||||
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 {
|
||||
switch entries, ldapcode := l.searchMaybeRootDSEQuery(ctx, h, baseDN, searchBaseDN, searchReq, anonymous); ldapcode {
|
||||
case ldap.LDAPResultUnwillingToPerform:
|
||||
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Search Error: No BaseDN provided")
|
||||
case ldap.LDAPResultInsufficientAccessRights:
|
||||
@@ -247,9 +284,9 @@ func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.Se
|
||||
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 {
|
||||
switch entries, ldapcode, attributename := l.searchMaybeSchemaQuery(ctx, h, baseDN, searchBaseDN, searchReq, anonymous); ldapcode {
|
||||
case ldap.LDAPResultOperationsError:
|
||||
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Schema Error: attribute %s cannot be read", attributename)
|
||||
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
|
||||
}
|
||||
@@ -264,21 +301,21 @@ func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.Se
|
||||
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}) {
|
||||
if !h.GetCfg().Behaviors.IgnoreCapabilities && !l.checkCapability(ctx, *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 {
|
||||
switch entries, ldapcode := l.searchMaybeTopLevelNodes(ctx, 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 {
|
||||
switch entries, ldapcode := l.searchMaybeTopLevelGroupsNode(ctx, 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 {
|
||||
switch entries, ldapcode := l.searchMaybeTopLevelUsersNode(ctx, h, baseDN, searchBaseDN, searchReq); ldapcode {
|
||||
case ldap.LDAPResultSuccess:
|
||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
||||
}
|
||||
@@ -288,12 +325,12 @@ func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.Se
|
||||
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 {
|
||||
switch entries, ldapcode := l.searchMaybePosixGroups(ctx, 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 {
|
||||
switch entries, ldapcode := l.searchMaybePosixAccounts(ctx, 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")
|
||||
@@ -306,8 +343,11 @@ func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.Se
|
||||
}
|
||||
|
||||
// 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 */)
|
||||
func (l LDAPOpsHelper) searchCheckBindDN(ctx context.Context, h LDAPOpsHandler, baseDN string, bindDN string, anonymous bool) (newBindDN string, boundUser *config.User, ldapresultcode ldap.LDAPResultCode) {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.searchCheckBindDN")
|
||||
defer span.End()
|
||||
|
||||
boundUser, ldapcode := l.findUser(ctx, h, bindDN, false /* checkGroup */)
|
||||
if ldapcode != ldap.LDAPResultSuccess {
|
||||
return "", nil, ldapcode
|
||||
}
|
||||
@@ -323,7 +363,10 @@ func (l LDAPOpsHelper) searchCheckBindDN(h LDAPOpsHandler, baseDN string, bindDN
|
||||
|
||||
// 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) {
|
||||
func (l LDAPOpsHelper) searchMaybeRootDSEQuery(ctx context.Context, h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest, anonymous bool) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.searchMaybeRootDSEQuery")
|
||||
defer span.End()
|
||||
|
||||
if searchBaseDN != "" {
|
||||
return nil, ldap.LDAPResultOther // OK
|
||||
}
|
||||
@@ -342,6 +385,7 @@ func (l LDAPOpsHelper) searchMaybeRootDSEQuery(h LDAPOpsHandler, baseDN string,
|
||||
// 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: "supportedSASLMechanisms", Values: []string{"GSSAPI", "PLAIN", "EXTERNAL"}})
|
||||
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{}})
|
||||
@@ -349,7 +393,7 @@ func (l LDAPOpsHelper) searchMaybeRootDSEQuery(h LDAPOpsHandler, baseDN string,
|
||||
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)
|
||||
attrs = l.collectRequestedAttributesBack(ctx, 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")
|
||||
@@ -358,7 +402,10 @@ func (l LDAPOpsHelper) searchMaybeRootDSEQuery(h LDAPOpsHandler, baseDN string,
|
||||
|
||||
// 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) {
|
||||
func (l LDAPOpsHelper) searchMaybeSchemaQuery(ctx context.Context, h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest, anonymous bool) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode, attributename *string) {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.searchMaybeSchemaQuery")
|
||||
defer span.End()
|
||||
|
||||
if searchBaseDN != "cn=schema" {
|
||||
return nil, ldap.LDAPResultOther, nil // OK
|
||||
}
|
||||
@@ -371,7 +418,7 @@ func (l LDAPOpsHelper) searchMaybeSchemaQuery(h LDAPOpsHandler, baseDN string, s
|
||||
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")
|
||||
filenames, _ := os.ReadDir("schema")
|
||||
for _, filename := range filenames {
|
||||
attributename := new(string)
|
||||
*attributename = filename.Name()
|
||||
@@ -388,7 +435,7 @@ func (l LDAPOpsHelper) searchMaybeSchemaQuery(h LDAPOpsHandler, baseDN string, s
|
||||
}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: filename.Name(), Values: values})
|
||||
}
|
||||
attrs = l.collectRequestedAttributesBack(attrs, searchReq)
|
||||
attrs = l.collectRequestedAttributesBack(ctx, 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")
|
||||
@@ -397,25 +444,28 @@ func (l LDAPOpsHelper) searchMaybeSchemaQuery(h LDAPOpsHandler, baseDN string, s
|
||||
|
||||
// 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) {
|
||||
func (l LDAPOpsHelper) searchMaybeTopLevelNodes(ctx context.Context, h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.searchMaybeTopLevelNodes")
|
||||
defer span.End()
|
||||
|
||||
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.topLevelRootNode(ctx, searchBaseDN))
|
||||
}
|
||||
entries = append(entries, l.topLevelGroupsNode(searchBaseDN, "groups"))
|
||||
entries = append(entries, l.topLevelUsersNode(searchBaseDN))
|
||||
entries = append(entries, l.topLevelGroupsNode(ctx, searchBaseDN, "groups"))
|
||||
entries = append(entries, l.topLevelUsersNode(ctx, searchBaseDN))
|
||||
if searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
groupentries, err := h.FindPosixGroups("ou=users")
|
||||
groupentries, err := h.FindPosixGroups(ctx, "ou=users")
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
entries = append(entries, groupentries...)
|
||||
|
||||
userentries, err := h.FindPosixAccounts("ou=users")
|
||||
userentries, err := h.FindPosixAccounts(ctx, "ou=users")
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
@@ -428,17 +478,20 @@ func (l LDAPOpsHelper) searchMaybeTopLevelNodes(h LDAPOpsHandler, baseDN string,
|
||||
|
||||
// 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) {
|
||||
func (l LDAPOpsHelper) searchMaybeTopLevelGroupsNode(ctx context.Context, h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.searchMaybeTopLevelGroupsNode")
|
||||
defer span.End()
|
||||
|
||||
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"))
|
||||
entries = append(entries, l.topLevelGroupsNode(ctx, searchBaseDN, "groups"))
|
||||
}
|
||||
if searchReq.Scope == ldap.ScopeSingleLevel || searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
groupentries, err := h.FindPosixGroups("ou=groups")
|
||||
groupentries, err := h.FindPosixGroups(ctx, "ou=groups")
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
@@ -451,24 +504,27 @@ func (l LDAPOpsHelper) searchMaybeTopLevelGroupsNode(h LDAPOpsHandler, baseDN st
|
||||
|
||||
// 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) {
|
||||
func (l LDAPOpsHelper) searchMaybeTopLevelUsersNode(ctx context.Context, h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.searchMaybeTopLevelUsersNode")
|
||||
defer span.End()
|
||||
|
||||
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))
|
||||
entries = append(entries, l.topLevelUsersNode(ctx, searchBaseDN))
|
||||
}
|
||||
if searchReq.Scope == ldap.ScopeSingleLevel || searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
groupentries, err := h.FindPosixGroups("ou=users")
|
||||
groupentries, err := h.FindPosixGroups(ctx, "ou=users")
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
entries = append(entries, groupentries...)
|
||||
}
|
||||
if searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
userentries, err := h.FindPosixAccounts("ou=users")
|
||||
userentries, err := h.FindPosixAccounts(ctx, "ou=users")
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
@@ -481,7 +537,10 @@ func (l LDAPOpsHelper) searchMaybeTopLevelUsersNode(h LDAPOpsHandler, baseDN str
|
||||
|
||||
// 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) {
|
||||
func (l LDAPOpsHelper) searchMaybePosixGroups(ctx context.Context, h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest, filterEntity string) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.searchMaybePosixGroups")
|
||||
defer span.End()
|
||||
|
||||
hierarchy := "ou=groups"
|
||||
if filterEntity != "posixgroup" {
|
||||
bits := strings.Split(strings.Replace(searchBaseDN, baseDN, "", 1), ",")
|
||||
@@ -493,19 +552,19 @@ func (l LDAPOpsHelper) searchMaybePosixGroups(h LDAPOpsHandler, baseDN string, s
|
||||
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)
|
||||
groupentries, err := h.FindPosixGroups(ctx, hierarchy)
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
entries = append(entries, l.preFilterEntries(searchBaseDN, groupentries)...)
|
||||
entries = append(entries, l.preFilterEntries(ctx, searchBaseDN, groupentries)...)
|
||||
}
|
||||
if searchReq.Scope == ldap.ScopeSingleLevel || searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||
if hierarchy == "ou=users" {
|
||||
userentries, err := h.FindPosixAccounts("ou=users")
|
||||
userentries, err := h.FindPosixAccounts(ctx, "ou=users")
|
||||
if err != nil {
|
||||
return nil, ldap.LDAPResultOperationsError
|
||||
}
|
||||
entries = append(entries, l.preFilterEntries(searchBaseDN, userentries)...)
|
||||
entries = append(entries, l.preFilterEntries(ctx, searchBaseDN, userentries)...)
|
||||
}
|
||||
}
|
||||
stats.Frontend.Add("search_successes", 1)
|
||||
@@ -516,7 +575,10 @@ func (l LDAPOpsHelper) searchMaybePosixGroups(h LDAPOpsHandler, baseDN string, s
|
||||
// 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) {
|
||||
func (l LDAPOpsHelper) searchMaybePosixAccounts(ctx context.Context, h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest, filterEntity string) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.searchMaybePosixAccounts")
|
||||
defer span.End()
|
||||
|
||||
switch filterEntity {
|
||||
case "posixaccount", "shadowaccount", "":
|
||||
h.GetLog().Info().Str("default case", filterEntity).Msg("Search request")
|
||||
@@ -530,16 +592,25 @@ func (l LDAPOpsHelper) searchMaybePosixAccounts(h LDAPOpsHandler, baseDN string,
|
||||
hierarchyString = "ou=users"
|
||||
}
|
||||
|
||||
entries, err := h.FindPosixAccounts(hierarchyString)
|
||||
unscopedEntries, err := h.FindPosixAccounts(ctx, 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 {
|
||||
func (l LDAPOpsHelper) topLevelRootNode(ctx context.Context, searchBaseDN string) *ldap.Entry {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
dnBits := strings.Split(searchBaseDN, ",")
|
||||
for _, dnBit := range dnBits {
|
||||
@@ -550,7 +621,7 @@ func (l LDAPOpsHelper) topLevelRootNode(searchBaseDN string) *ldap.Entry {
|
||||
return &ldap.Entry{DN: searchBaseDN, Attributes: attrs}
|
||||
}
|
||||
|
||||
func (l LDAPOpsHelper) topLevelGroupsNode(searchBaseDN string, hierarchy string) *ldap.Entry {
|
||||
func (l LDAPOpsHelper) topLevelGroupsNode(ctx context.Context, 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"}})
|
||||
@@ -562,7 +633,7 @@ func (l LDAPOpsHelper) topLevelGroupsNode(searchBaseDN string, hierarchy string)
|
||||
return &ldap.Entry{DN: dn, Attributes: attrs}
|
||||
}
|
||||
|
||||
func (l LDAPOpsHelper) topLevelUsersNode(searchBaseDN string) *ldap.Entry {
|
||||
func (l LDAPOpsHelper) topLevelUsersNode(ctx context.Context, 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"}})
|
||||
@@ -577,7 +648,7 @@ func (l LDAPOpsHelper) topLevelUsersNode(searchBaseDN string) *ldap.Entry {
|
||||
// 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) {
|
||||
func (l LDAPOpsHelper) preFilterEntries(ctx context.Context, searchBaseDN string, entries []*ldap.Entry) (resultentries []*ldap.Entry) {
|
||||
filteredEntries := []*ldap.Entry{}
|
||||
for _, entry := range entries {
|
||||
if strings.HasSuffix(entry.DN, searchBaseDN) {
|
||||
@@ -587,7 +658,9 @@ func (l LDAPOpsHelper) preFilterEntries(searchBaseDN string, entries []*ldap.Ent
|
||||
return filteredEntries
|
||||
}
|
||||
|
||||
func (l LDAPOpsHelper) findUser(h LDAPOpsHandler, bindDN string, checkGroup bool) (userWhenFound *config.User, resultCode ldap.LDAPResultCode) {
|
||||
func (l LDAPOpsHelper) findUser(ctx context.Context, h LDAPOpsHandler, bindDN string, checkGroup bool) (userWhenFound *config.User, resultCode ldap.LDAPResultCode) {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.findUser")
|
||||
defer span.End()
|
||||
|
||||
var user config.User
|
||||
|
||||
@@ -597,7 +670,7 @@ func (l LDAPOpsHelper) findUser(h LDAPOpsHandler, bindDN string, checkGroup bool
|
||||
// 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)
|
||||
foundUser, user, _ = h.FindUser(ctx, bindDN, true)
|
||||
if !foundUser {
|
||||
h.GetLog().Info().Str("userprincipalname", bindDN).Msg("User not found")
|
||||
return nil, ldap.LDAPResultInvalidCredentials
|
||||
@@ -613,10 +686,10 @@ func (l LDAPOpsHelper) findUser(h LDAPOpsHandler, bindDN string, checkGroup bool
|
||||
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+"=")
|
||||
userName = strings.TrimPrefix(parts[0], h.GetBackend().NameFormatAsArray[0]+"=")
|
||||
} else if len(parts) == 2 || (len(parts) == 3 && parts[2] == "ou=users") {
|
||||
userName = strings.TrimPrefix(parts[0], h.GetBackend().NameFormatAsArray[0]+"=")
|
||||
groupName = strings.TrimPrefix(parts[1], h.GetBackend().GroupFormatAsArray[0]+"=")
|
||||
} else {
|
||||
h.GetLog().Info().Str("binddn", bindDN).Int("numparts", len(parts)).Msg("BindDN should have only one or two parts")
|
||||
for _, part := range parts {
|
||||
@@ -627,7 +700,7 @@ func (l LDAPOpsHelper) findUser(h LDAPOpsHandler, bindDN string, checkGroup bool
|
||||
|
||||
// find the user
|
||||
var foundUser bool // = false
|
||||
foundUser, user, _ = h.FindUser(userName, false)
|
||||
foundUser, user, _ = h.FindUser(ctx, userName, false)
|
||||
if !foundUser {
|
||||
h.GetLog().Info().Str("username", userName).Msg("User not found")
|
||||
return nil, ldap.LDAPResultInvalidCredentials
|
||||
@@ -637,7 +710,7 @@ func (l LDAPOpsHelper) findUser(h LDAPOpsHandler, bindDN string, checkGroup bool
|
||||
var group config.Group // = nil
|
||||
var foundGroup bool // = false
|
||||
if groupName != "" {
|
||||
foundGroup, group, _ = h.FindGroup(groupName)
|
||||
foundGroup, group, _ = h.FindGroup(ctx, groupName)
|
||||
if !foundGroup {
|
||||
h.GetLog().Info().Str("groupname", groupName).Msg("Group not found")
|
||||
return nil, ldap.LDAPResultInvalidCredentials
|
||||
@@ -655,7 +728,8 @@ func (l LDAPOpsHelper) findUser(h LDAPOpsHandler, bindDN string, checkGroup bool
|
||||
return &user, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
func (l LDAPOpsHelper) checkCapability(user config.User, action string, objects []string) bool {
|
||||
func (l LDAPOpsHelper) checkCapability(ctx context.Context, user config.User, action string, objects []string) bool {
|
||||
// User-level?
|
||||
for _, capability := range user.Capabilities {
|
||||
if capability.Action == action {
|
||||
for _, object := range objects {
|
||||
@@ -671,7 +745,10 @@ func (l LDAPOpsHelper) checkCapability(user config.User, action string, objects
|
||||
// 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 {
|
||||
func (l LDAPOpsHelper) collectRequestedAttributesBack(ctx context.Context, attrs []*ldap.EntryAttribute, searchReq ldap.SearchRequest) []*ldap.EntryAttribute {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.collectRequestedAttributesBack")
|
||||
defer span.End()
|
||||
|
||||
attbits := configattributematcher.FindStringSubmatch(searchReq.Filter)
|
||||
if len(attbits) == 3 {
|
||||
foundattname := false
|
||||
@@ -690,13 +767,16 @@ func (l LDAPOpsHelper) collectRequestedAttributesBack(attrs []*ldap.EntryAttribu
|
||||
}
|
||||
|
||||
// return true if we should not process the current operation
|
||||
func (l LDAPOpsHelper) isInTimeout(handler LDAPOpsHandler, conn net.Conn) bool {
|
||||
func (l LDAPOpsHelper) isInTimeout(ctx context.Context, handler LDAPOpsHandler, conn net.Conn) bool {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.isInTimeout")
|
||||
defer span.End()
|
||||
|
||||
cfg := handler.GetCfg()
|
||||
if !cfg.Behaviors.LimitFailedBinds {
|
||||
return false
|
||||
}
|
||||
|
||||
remoteAddr := l.getAddr(conn)
|
||||
remoteAddr := l.getAddr(ctx, conn)
|
||||
now := time.Now()
|
||||
info, ok := l.sources[remoteAddr]
|
||||
if !ok {
|
||||
@@ -716,13 +796,16 @@ func (l LDAPOpsHelper) isInTimeout(handler LDAPOpsHandler, conn net.Conn) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (l LDAPOpsHelper) maybePutInTimeout(handler LDAPOpsHandler, conn net.Conn, noteFailure bool) bool {
|
||||
func (l LDAPOpsHelper) maybePutInTimeout(ctx context.Context, handler LDAPOpsHandler, conn net.Conn, noteFailure bool) bool {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.maybePutInTimeout")
|
||||
defer span.End()
|
||||
|
||||
cfg := handler.GetCfg()
|
||||
if !cfg.Behaviors.LimitFailedBinds {
|
||||
return false
|
||||
}
|
||||
|
||||
remoteAddr := l.getAddr(conn)
|
||||
remoteAddr := l.getAddr(ctx, conn)
|
||||
now := time.Now()
|
||||
info, _ := l.sources[remoteAddr]
|
||||
// if we have a failed bind...
|
||||
@@ -756,7 +839,10 @@ func (l LDAPOpsHelper) maybePutInTimeout(handler LDAPOpsHandler, conn net.Conn,
|
||||
return false
|
||||
}
|
||||
|
||||
func (l LDAPOpsHelper) getAddr(conn net.Conn) string {
|
||||
func (l LDAPOpsHelper) getAddr(ctx context.Context, conn net.Conn) string {
|
||||
ctx, span := l.tracer.Start(ctx, "handler.LDAPOpsHelper.getAddr")
|
||||
defer span.End()
|
||||
|
||||
fullAddr := conn.RemoteAddr().String()
|
||||
sep := strings.LastIndex(fullAddr, ":")
|
||||
if sep == -1 {
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/ldap"
|
||||
)
|
||||
|
||||
type bindTestHandler struct {
|
||||
backend config.Backend
|
||||
cfg config.Config
|
||||
log zerolog.Logger
|
||||
user config.User
|
||||
}
|
||||
|
||||
func (h *bindTestHandler) GetBackend() config.Backend { return h.backend }
|
||||
func (h *bindTestHandler) GetLog() *zerolog.Logger { return &h.log }
|
||||
func (h *bindTestHandler) GetCfg() *config.Config { return &h.cfg }
|
||||
func (h *bindTestHandler) GetYubikeyAuth() *yubigo.YubiAuth {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *bindTestHandler) FindUser(_ context.Context, userName string, _ bool) (bool, config.User, error) {
|
||||
if userName == h.user.Name {
|
||||
return true, h.user, nil
|
||||
}
|
||||
return false, config.User{}, nil
|
||||
}
|
||||
|
||||
func (h *bindTestHandler) FindGroup(context.Context, string) (bool, config.Group, error) {
|
||||
return false, config.Group{}, nil
|
||||
}
|
||||
|
||||
func (h *bindTestHandler) FindPosixAccounts(context.Context, string) ([]*ldap.Entry, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (h *bindTestHandler) FindPosixGroups(context.Context, string) ([]*ldap.Entry, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func sha256Password(password string) string {
|
||||
hash := sha256.Sum256([]byte(password))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func TestLDAPOpsHelperBindRejectsUnauthenticatedAndPasswordlessBinds(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
password string
|
||||
user config.User
|
||||
wantResult ldap.LDAPResultCode
|
||||
}{
|
||||
{
|
||||
name: "named empty password is unauthenticated bind",
|
||||
password: "",
|
||||
wantResult: ldap.LDAPResultUnwillingToPerform,
|
||||
},
|
||||
{
|
||||
name: "arbitrary password without verifier",
|
||||
password: "definitely-wrong",
|
||||
wantResult: ldap.LDAPResultInvalidCredentials,
|
||||
},
|
||||
{
|
||||
name: "failed app password does not fall through",
|
||||
user: config.User{
|
||||
Name: "nopass",
|
||||
PassAppSHA256: []string{sha256Password("expected")},
|
||||
},
|
||||
password: "definitely-wrong",
|
||||
wantResult: ldap.LDAPResultInvalidCredentials,
|
||||
},
|
||||
{
|
||||
name: "matching primary password succeeds",
|
||||
user: config.User{
|
||||
Name: "nopass",
|
||||
PassSHA256: sha256Password("correct"),
|
||||
},
|
||||
password: "correct",
|
||||
wantResult: ldap.LDAPResultSuccess,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
user := tt.user
|
||||
if user.Name == "" {
|
||||
user.Name = "nopass"
|
||||
}
|
||||
h := &bindTestHandler{
|
||||
backend: config.Backend{
|
||||
BaseDN: "dc=glauth,dc=com",
|
||||
NameFormatAsArray: []string{"cn"},
|
||||
GroupFormatAsArray: []string{"ou"},
|
||||
},
|
||||
user: user,
|
||||
log: zerolog.Nop(),
|
||||
}
|
||||
conn, peer := net.Pipe()
|
||||
defer conn.Close()
|
||||
defer peer.Close()
|
||||
|
||||
helper := NewLDAPOpsHelper(trace.NewNoopTracerProvider().Tracer("bind-test"))
|
||||
got, err := helper.Bind(context.Background(), h, "cn=nopass,dc=glauth,dc=com", tt.password, conn)
|
||||
if err != nil {
|
||||
t.Fatalf("Bind returned error: %v", err)
|
||||
}
|
||||
if got != tt.wantResult {
|
||||
t.Fatalf("Bind result = %v, want %v", got, tt.wantResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLDAPOpsHelperBindAllowsAnonymousBind(t *testing.T) {
|
||||
h := &bindTestHandler{
|
||||
backend: config.Backend{BaseDN: "dc=glauth,dc=com"},
|
||||
log: zerolog.Nop(),
|
||||
}
|
||||
conn, peer := net.Pipe()
|
||||
defer conn.Close()
|
||||
defer peer.Close()
|
||||
|
||||
helper := NewLDAPOpsHelper(trace.NewNoopTracerProvider().Tracer("bind-test"))
|
||||
got, err := helper.Bind(context.Background(), h, "", "", conn)
|
||||
if err != nil {
|
||||
t.Fatalf("Bind returned error: %v", err)
|
||||
}
|
||||
if got != ldap.LDAPResultSuccess {
|
||||
t.Fatalf("Bind result = %v, want %v", got, ldap.LDAPResultSuccess)
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,12 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/v2/internal/monitoring"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
)
|
||||
|
||||
@@ -21,6 +24,8 @@ type Options struct {
|
||||
YubiAuth *yubigo.YubiAuth
|
||||
Helper Handler
|
||||
LDAPHelper LDAPOpsHelper
|
||||
Monitor monitoring.MonitorInterface
|
||||
Tracer trace.Tracer
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
@@ -100,3 +105,17 @@ func LDAPHelper(val LDAPOpsHelper) Option {
|
||||
o.LDAPHelper = val
|
||||
}
|
||||
}
|
||||
|
||||
// Monitor provides a function to set the monitor option.
|
||||
func Monitor(val monitoring.MonitorInterface) Option {
|
||||
return func(o *Options) {
|
||||
o.Monitor = val
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer provides a function to set the tracer option.
|
||||
func Tracer(val trace.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = val
|
||||
}
|
||||
}
|
||||
|
||||
+93
-35
@@ -6,16 +6,20 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/glauth/glauth/v2/internal/monitoring"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/glauth/v2/pkg/stats"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/glauth/ldap"
|
||||
msgraph "github.com/yaegashi/msgraph.go/v1.0"
|
||||
)
|
||||
|
||||
@@ -33,12 +37,46 @@ type ownCloudHandler struct {
|
||||
client *http.Client
|
||||
sessions map[string]ownCloudSession
|
||||
lock *sync.Mutex
|
||||
|
||||
monitor monitoring.MonitorInterface
|
||||
tracer trace.Tracer
|
||||
}
|
||||
|
||||
// global lock for ownCloudHandler sessions & servers manipulation
|
||||
var ownCloudLock sync.Mutex
|
||||
|
||||
func (h ownCloudHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.LDAPResultCode, error) {
|
||||
func NewOwnCloudHandler(opts ...Option) Handler {
|
||||
options := newOptions(opts...)
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
monitor: options.Monitor,
|
||||
tracer: options.Tracer,
|
||||
}
|
||||
}
|
||||
|
||||
func (h ownCloudHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "bind", "status": fmt.Sprintf("%v", result)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.backend.BaseDN)
|
||||
|
||||
@@ -83,7 +121,15 @@ func (h ownCloudHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) {
|
||||
func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "search", "status": fmt.Sprintf("%v", result.ResultCode)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.backend.BaseDN)
|
||||
searchBaseDN := strings.ToLower(searchReq.BaseDN)
|
||||
@@ -121,7 +167,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}})
|
||||
for _, groupAttr := range h.backend.GroupFormatAsArray {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: groupAttr, 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.GIDNumber)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixGroup"}})
|
||||
@@ -133,7 +181,7 @@ 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.backend.GroupFormat, h.backend.BaseDN)
|
||||
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormatAsArray[0], *g.ID, h.backend.BaseDN)
|
||||
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
|
||||
}
|
||||
case "posixaccount", "":
|
||||
@@ -151,8 +199,9 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
|
||||
}
|
||||
for _, u := range users {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{*u.ID}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{*u.ID}})
|
||||
for _, nameAttr := range h.backend.NameFormatAsArray {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: nameAttr, Values: []string{*u.ID}})
|
||||
}
|
||||
if u.DisplayName != nil {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "givenName", Values: []string{*u.DisplayName}})
|
||||
}
|
||||
@@ -163,7 +212,7 @@ 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.backend.NameFormat, *u.ID, h.backend.GroupFormat, "users", h.backend.BaseDN)
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormatAsArray[0], *u.ID, h.backend.GroupFormatAsArray[0], "users", h.backend.BaseDN)
|
||||
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
|
||||
}
|
||||
}
|
||||
@@ -174,25 +223,55 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
|
||||
|
||||
// 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) {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "add", "status": fmt.Sprintf("%v", result)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
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) {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "modify", "status": fmt.Sprintf("%v", result)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
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) {
|
||||
_, span := h.tracer.Start(context.Background(), "handler.configHandler.Delete")
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
h.monitor.SetResponseTimeMetric(
|
||||
map[string]string{"operation": "delete", "status": fmt.Sprintf("%v", result)},
|
||||
time.Since(start).Seconds(),
|
||||
)
|
||||
}()
|
||||
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) {
|
||||
func (h ownCloudHandler) FindUser(ctx context.Context, userName string, searchByUPN bool) (found bool, user config.User, err error) {
|
||||
_, span := h.tracer.Start(ctx, "handler.ownCloudHandler.FindUser")
|
||||
defer span.End()
|
||||
|
||||
return false, config.User{}, nil
|
||||
}
|
||||
|
||||
func (h ownCloudHandler) FindGroup(groupName string) (found bool, group config.Group, err error) {
|
||||
func (h ownCloudHandler) FindGroup(ctx context.Context, groupName string) (found bool, group config.Group, err error) {
|
||||
_, span := h.tracer.Start(ctx, "handler.ownCloudHandler.FindGroup")
|
||||
defer span.End()
|
||||
|
||||
return false, config.Group{}, nil
|
||||
}
|
||||
|
||||
@@ -255,7 +334,7 @@ func (s ownCloudSession) getGroups() ([]msgraph.Group, error) {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -326,7 +405,7 @@ func (s ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -352,24 +431,3 @@ func (s ownCloudSession) redirectPolicyFunc(req *http.Request, via []*http.Reque
|
||||
req.SetBasicAuth(s.user, s.password)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewOwnCloudHandler(opts ...Option) Handler {
|
||||
options := newOptions(opts...)
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/syslog"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// We will use this package to wrap log messages coming from libraries who have no interest
|
||||
@@ -17,8 +22,29 @@ var (
|
||||
ldapliblogmatcher = regexp.MustCompile(`^\d{4}\/\d{1,2}\/\d{1,2} \d{1,2}\:\d{1,2}\:\d{1,2} `)
|
||||
)
|
||||
|
||||
func RewireLogging(logr zerolog.Logger, reqstructlog bool) {
|
||||
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 {
|
||||
@@ -1,11 +1,11 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
@@ -13,11 +13,17 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/uptrace/opentelemetry-go-extra/otelsql"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"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"
|
||||
"github.com/glauth/ldap"
|
||||
)
|
||||
|
||||
var configattributematcher = regexp.MustCompile(`(?i)\((?P<attribute>[a-zA-Z0-9]+)\s*=\s*(?P<value>.*)\)`)
|
||||
@@ -28,7 +34,7 @@ type SqlBackend interface {
|
||||
// Create db/schema if necessary
|
||||
CreateSchema(db *sql.DB)
|
||||
// Migrate schema if necessary
|
||||
MigrateSchema(db *sql.DB, checker func(*sql.DB, string) bool)
|
||||
MigrateSchema(db *sql.DB, checker func(*sql.DB, string, string) bool)
|
||||
//
|
||||
GetPrepareSymbol() string
|
||||
}
|
||||
@@ -48,13 +54,21 @@ type databaseHandler struct {
|
||||
MemGroups []config.Group
|
||||
ldohelper handler.LDAPOpsHelper
|
||||
attmatcher *regexp.Regexp
|
||||
|
||||
tracer trace.Tracer
|
||||
}
|
||||
|
||||
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)
|
||||
db, err := otelsql.Open(
|
||||
sqlBackend.GetDriverName(),
|
||||
options.Backend.Database,
|
||||
otelsql.WithAttributes(otlpDriverAttribute(sqlBackend)),
|
||||
otelsql.WithDBName(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)
|
||||
@@ -78,24 +92,36 @@ func NewDatabaseHandler(sqlBackend SqlBackend, opts ...handler.Option) handler.H
|
||||
sqlBackend: sqlBackend,
|
||||
database: dbInfo,
|
||||
ldohelper: options.LDAPHelper,
|
||||
attmatcher: configattributematcher}
|
||||
attmatcher: configattributematcher,
|
||||
tracer: options.Tracer,
|
||||
}
|
||||
|
||||
sqlBackend.CreateSchema(db)
|
||||
sqlBackend.MigrateSchema(db, ColumnExists)
|
||||
|
||||
options.Logger.Info().Msg("Database (" + sqlBackend.GetDriverName() + "::" + options.Backend.Database + ") Plugin: Ready")
|
||||
options.Logger.Debug().Msg("Database (" + sqlBackend.GetDriverName() + "::" + options.Backend.Database + ") Plugin: Ready")
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func ColumnExists(db *sql.DB, columnName string) bool {
|
||||
func ColumnExists(db *sql.DB, tableName string, columnName string) bool {
|
||||
var found string
|
||||
err := db.QueryRow(fmt.Sprintf(`SELECT COUNT(%s) FROM users`, columnName)).Scan(
|
||||
err := db.QueryRowContext(context.Background(), fmt.Sprintf(`SELECT COUNT(%s) FROM %s`, columnName, tableName)).Scan(
|
||||
&found)
|
||||
if err != nil {
|
||||
return false
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func otlpDriverAttribute(backend SqlBackend) attribute.KeyValue {
|
||||
switch backend.GetDriverName() {
|
||||
case "sqlite3":
|
||||
return semconv.DBSystemSqlite
|
||||
case "postgres":
|
||||
return semconv.DBSystemPostgreSQL
|
||||
case "mysql":
|
||||
return semconv.DBSystemMySQL
|
||||
default:
|
||||
return semconv.DBSystemOtherSQL
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (h databaseHandler) GetBackend() config.Backend {
|
||||
@@ -112,29 +138,47 @@ func (h databaseHandler) GetYubikeyAuth() *yubigo.YubiAuth {
|
||||
}
|
||||
|
||||
func (h databaseHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
||||
return h.ldohelper.Bind(h, bindDN, bindSimplePw, conn)
|
||||
ctx, span := h.tracer.Start(context.Background(), "plugins.databaseHandler.Bind")
|
||||
defer span.End()
|
||||
|
||||
return h.ldohelper.Bind(ctx, 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)
|
||||
ctx, span := h.tracer.Start(context.Background(), "plugins.databaseHandler.Search")
|
||||
defer span.End()
|
||||
|
||||
return h.ldohelper.Search(ctx, 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) {
|
||||
_, span := h.tracer.Start(context.Background(), "plugins.databaseHandler.Add")
|
||||
defer span.End()
|
||||
|
||||
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) {
|
||||
_, span := h.tracer.Start(context.Background(), "plugins.databaseHandler.Modify")
|
||||
defer span.End()
|
||||
|
||||
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) {
|
||||
_, span := h.tracer.Start(context.Background(), "plugins.databaseHandler.Delete")
|
||||
defer span.End()
|
||||
|
||||
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||
}
|
||||
|
||||
func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u config.User, err error) {
|
||||
func (h databaseHandler) FindUser(ctx context.Context, userName string, searchByUPN bool) (f bool, u config.User, err error) {
|
||||
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.FindUser")
|
||||
defer span.End()
|
||||
|
||||
var criterion string
|
||||
if searchByUPN {
|
||||
criterion = "lower(u.mail)"
|
||||
@@ -146,11 +190,14 @@ func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u
|
||||
found := false
|
||||
|
||||
var disabled int
|
||||
err = h.database.cnx.QueryRow(fmt.Sprintf(`
|
||||
err = h.database.cnx.QueryRowContext(
|
||||
ctx,
|
||||
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(
|
||||
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)
|
||||
@@ -159,7 +206,7 @@ func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u
|
||||
|
||||
if !h.cfg.Behaviors.IgnoreCapabilities {
|
||||
capability := config.Capability{}
|
||||
rows, err := h.database.cnx.Query(fmt.Sprintf(`
|
||||
rows, err := h.database.cnx.QueryContext(ctx, fmt.Sprintf(`
|
||||
SELECT c.action,c.object
|
||||
FROM capabilities c WHERE userid=%s`,
|
||||
h.sqlBackend.GetPrepareSymbol()), user.UIDNumber)
|
||||
@@ -179,12 +226,17 @@ func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u
|
||||
return found, user, err
|
||||
}
|
||||
|
||||
func (h databaseHandler) FindGroup(groupName string) (f bool, g config.Group, err error) {
|
||||
func (h databaseHandler) FindGroup(ctx context.Context, groupName string) (f bool, g config.Group, err error) {
|
||||
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.FindGroup")
|
||||
defer span.End()
|
||||
|
||||
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(
|
||||
err = h.database.cnx.QueryRowContext(
|
||||
ctx,
|
||||
fmt.Sprintf(`
|
||||
SELECT g.gidnumber FROM ldapgroups g WHERE lower(name)=%s`, h.sqlBackend.GetPrepareSymbol()), groupName).Scan(
|
||||
&group.GIDNumber)
|
||||
if err == nil {
|
||||
found = true
|
||||
@@ -193,15 +245,20 @@ func (h databaseHandler) FindGroup(groupName string) (f bool, g config.Group, er
|
||||
return found, group, err
|
||||
}
|
||||
|
||||
func (h databaseHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.Entry, err error) {
|
||||
func (h databaseHandler) FindPosixAccounts(ctx context.Context, hierarchy string) (entrylist []*ldap.Entry, err error) {
|
||||
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.FindPosixAccounts")
|
||||
defer span.End()
|
||||
|
||||
entries := []*ldap.Entry{}
|
||||
|
||||
h.MemGroups, err = h.memoizeGroups()
|
||||
h.MemGroups, err = h.memoizeGroups(ctx)
|
||||
if err != nil {
|
||||
return entries, err
|
||||
}
|
||||
|
||||
rows, err := h.database.cnx.Query(`
|
||||
rows, err := h.database.cnx.QueryContext(
|
||||
ctx,
|
||||
`
|
||||
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 {
|
||||
@@ -219,11 +276,11 @@ func (h databaseHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.
|
||||
if err != nil {
|
||||
return entries, err
|
||||
}
|
||||
u.OtherGroups = h.commaListToIntTable(otherGroups)
|
||||
u.OtherGroups = h.commaListToIntTable(ctx, otherGroups)
|
||||
u.Disabled = h.intToBool(disabled)
|
||||
u.SSHKeys = h.commaListToStringTable(sshKeys)
|
||||
u.SSHKeys = h.commaListToStringTable(ctx, sshKeys)
|
||||
|
||||
entry := h.getAccount(hierarchy, u)
|
||||
entry := h.getAccount(ctx, hierarchy, u)
|
||||
|
||||
if custattrstr != "{}" {
|
||||
var r map[string]interface{}
|
||||
@@ -256,16 +313,19 @@ func (h databaseHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (h databaseHandler) FindPosixGroups(hierarchy string) (entrylist []*ldap.Entry, err error) {
|
||||
func (h databaseHandler) FindPosixGroups(ctx context.Context, hierarchy string) (entrylist []*ldap.Entry, err error) {
|
||||
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.FindPosixGroups")
|
||||
defer span.End()
|
||||
|
||||
entries := []*ldap.Entry{}
|
||||
|
||||
h.MemGroups, err = h.memoizeGroups()
|
||||
h.MemGroups, err = h.memoizeGroups(ctx)
|
||||
if err != nil {
|
||||
return entries, err
|
||||
}
|
||||
|
||||
for _, g := range h.MemGroups {
|
||||
info := h.getGroup(hierarchy, g)
|
||||
info := h.getGroup(ctx, hierarchy, g)
|
||||
if hierarchy != "groups" {
|
||||
info.DN = strings.Replace(info.DN, ",ou=groups,", fmt.Sprintf(",%s,", hierarchy), 1)
|
||||
}
|
||||
@@ -276,6 +336,9 @@ func (h databaseHandler) FindPosixGroups(hierarchy string) (entrylist []*ldap.En
|
||||
}
|
||||
|
||||
func (h databaseHandler) Close(boundDn string, conn net.Conn) error {
|
||||
_, span := h.tracer.Start(context.Background(), "plugins.databaseHandler.Close")
|
||||
defer span.End()
|
||||
|
||||
stats.Frontend.Add("closes", 1)
|
||||
return nil
|
||||
}
|
||||
@@ -287,7 +350,10 @@ func (h databaseHandler) intToBool(value int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (h databaseHandler) commaListToIntTable(commaList string) []int {
|
||||
func (h databaseHandler) commaListToIntTable(ctx context.Context, commaList string) []int {
|
||||
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.commaListToIntTable")
|
||||
defer span.End()
|
||||
|
||||
if len(commaList) == 0 {
|
||||
return make([]int, 0)
|
||||
}
|
||||
@@ -303,20 +369,28 @@ func (h databaseHandler) commaListToIntTable(commaList string) []int {
|
||||
return rowsAsInts
|
||||
}
|
||||
|
||||
func (h databaseHandler) commaListToStringTable(commaList string) []string {
|
||||
func (h databaseHandler) commaListToStringTable(ctx context.Context, commaList string) []string {
|
||||
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.commaListToStringTable")
|
||||
defer span.End()
|
||||
|
||||
if len(commaList) == 0 {
|
||||
return make([]string, 0)
|
||||
}
|
||||
return strings.Split(commaList, ",")
|
||||
}
|
||||
|
||||
func (h databaseHandler) memoizeGroups() ([]config.Group, error) {
|
||||
func (h databaseHandler) memoizeGroups(ctx context.Context) ([]config.Group, error) {
|
||||
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.memoizeGroups")
|
||||
defer span.End()
|
||||
|
||||
workMemGroups := make([]*config.Group, 0)
|
||||
rows, err := h.database.cnx.Query(`
|
||||
SELECT g1.name,g1.gidnumber,ig.includegroupid
|
||||
FROM groups g1
|
||||
rows, err := h.database.cnx.QueryContext(
|
||||
ctx,
|
||||
`
|
||||
SELECT g1.name,g1.gidnumber,ig.includegroupid
|
||||
FROM ldapgroups g1
|
||||
LEFT JOIN includegroups ig ON g1.gidnumber=ig.parentgroupid
|
||||
LEFT JOIN groups g2 ON ig.includegroupid=g2.gidnumber`)
|
||||
LEFT JOIN ldapgroups g2 ON ig.includegroupid=g2.gidnumber`)
|
||||
if err != nil {
|
||||
return nil, errors.New("Unable to memoize groups list")
|
||||
}
|
||||
@@ -349,10 +423,21 @@ func (h databaseHandler) memoizeGroups() ([]config.Group, error) {
|
||||
return memGroups, nil
|
||||
}
|
||||
|
||||
func (h databaseHandler) getGroupMemberDNs(gid int) []string {
|
||||
func (h databaseHandler) getGroupMemberDNs(ctx context.Context, gid int) []string {
|
||||
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.getGroupMemberDNs")
|
||||
defer span.End()
|
||||
|
||||
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(`
|
||||
rows, err := h.database.cnx.QueryContext(
|
||||
ctx,
|
||||
`
|
||||
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
|
||||
FROM users u`)
|
||||
if err != nil {
|
||||
@@ -369,13 +454,13 @@ func (h databaseHandler) getGroupMemberDNs(gid int) []string {
|
||||
return []string{}
|
||||
}
|
||||
if u.PrimaryGroup == gid {
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormatAsArray[0], u.Name, h.backend.GroupFormatAsArray[0], h.getGroupName(ctx, u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
|
||||
members[dn] = true
|
||||
} else {
|
||||
u.OtherGroups = h.commaListToIntTable(otherGroups)
|
||||
u.OtherGroups = h.commaListToIntTable(ctx, otherGroups)
|
||||
for _, othergid := range u.OtherGroups {
|
||||
if othergid == gid {
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormatAsArray[0], u.Name, h.backend.GroupFormatAsArray[0], h.getGroupName(ctx, u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
|
||||
members[dn] = true
|
||||
}
|
||||
}
|
||||
@@ -386,7 +471,7 @@ func (h databaseHandler) getGroupMemberDNs(gid int) []string {
|
||||
if gid == g.GIDNumber {
|
||||
for _, includegroupid := range g.IncludeGroups {
|
||||
if includegroupid != gid {
|
||||
includegroupmembers := h.getGroupMemberDNs(includegroupid)
|
||||
includegroupmembers := h.getGroupMemberDNs(ctx, includegroupid)
|
||||
|
||||
for _, includegroupmember := range includegroupmembers {
|
||||
members[includegroupmember] = true
|
||||
@@ -407,9 +492,14 @@ func (h databaseHandler) getGroupMemberDNs(gid int) []string {
|
||||
}
|
||||
|
||||
// Used exclusively when looking up Posix Groups
|
||||
func (h databaseHandler) getGroupMemberIDs(gid int) []string {
|
||||
func (h databaseHandler) getGroupMemberIDs(ctx context.Context, gid int) []string {
|
||||
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.getGroupMemberIDs")
|
||||
defer span.End()
|
||||
|
||||
members := make(map[string]bool)
|
||||
rows, err := h.database.cnx.Query(`
|
||||
rows, err := h.database.cnx.QueryContext(
|
||||
ctx,
|
||||
`
|
||||
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
|
||||
FROM users u`)
|
||||
if err != nil {
|
||||
@@ -428,7 +518,7 @@ func (h databaseHandler) getGroupMemberIDs(gid int) []string {
|
||||
if u.PrimaryGroup == gid {
|
||||
members[u.Name] = true
|
||||
} else {
|
||||
u.OtherGroups = h.commaListToIntTable(otherGroups)
|
||||
u.OtherGroups = h.commaListToIntTable(ctx, otherGroups)
|
||||
for _, othergid := range u.OtherGroups {
|
||||
if othergid == gid {
|
||||
members[u.Name] = true
|
||||
@@ -443,7 +533,7 @@ func (h databaseHandler) getGroupMemberIDs(gid int) []string {
|
||||
if includegroupid == gid {
|
||||
h.log.Warn().Msg(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
|
||||
} else {
|
||||
includegroupmemberids := h.getGroupMemberIDs(includegroupid)
|
||||
includegroupmemberids := h.getGroupMemberIDs(ctx, includegroupid)
|
||||
|
||||
for _, includegroupmemberid := range includegroupmemberids {
|
||||
members[includegroupmemberid] = true
|
||||
@@ -464,18 +554,21 @@ func (h databaseHandler) getGroupMemberIDs(gid int) []string {
|
||||
}
|
||||
|
||||
// Invoked for every user being returned from our database
|
||||
func (h databaseHandler) getGroupDNs(gids []int) []string {
|
||||
func (h databaseHandler) getGroupDNs(ctx context.Context, gids []int) []string {
|
||||
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.getGroupDNs")
|
||||
defer span.End()
|
||||
|
||||
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)
|
||||
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormatAsArray[0], 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})
|
||||
includegroupdns := h.getGroupDNs(ctx, []int{g.GIDNumber})
|
||||
|
||||
for _, includegroupdn := range includegroupdns {
|
||||
groups[includegroupdn] = true
|
||||
@@ -496,7 +589,10 @@ func (h databaseHandler) getGroupDNs(gids []int) []string {
|
||||
}
|
||||
|
||||
// Invoked for every user being returned from our database
|
||||
func (h databaseHandler) getGroupName(gid int) string {
|
||||
func (h databaseHandler) getGroupName(ctx context.Context, gid int) string {
|
||||
_, span := h.tracer.Start(ctx, "plugins.databaseHandler.getGroupName")
|
||||
defer span.End()
|
||||
|
||||
for _, g := range h.MemGroups {
|
||||
if g.GIDNumber == gid {
|
||||
return g.Name
|
||||
@@ -506,77 +602,86 @@ func (h databaseHandler) getGroupName(gid int) string {
|
||||
}
|
||||
|
||||
// Toolbox
|
||||
func (h databaseHandler) getGroup(hierarchy string, g config.Group) *ldap.Entry {
|
||||
func (h databaseHandler) getGroup(ctx context.Context, hierarchy string, g config.Group) *ldap.Entry {
|
||||
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.getGroup")
|
||||
defer span.End()
|
||||
|
||||
asGroupOfUniqueNames := hierarchy == "ou=groups"
|
||||
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"cn", []string{g.Name}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"description", []string{fmt.Sprintf("%s via LDAP", g.Name)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{fmt.Sprintf("%d", g.GIDNumber)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"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"}})
|
||||
for _, groupAttr := range h.backend.GroupFormatAsArray {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: groupAttr, Values: []string{g.Name}})
|
||||
}
|
||||
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormat, g.Name, h.backend.BaseDN)
|
||||
return &ldap.Entry{dn, attrs}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s via LDAP", 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(ctx, 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(ctx, g.GIDNumber)})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixGroup", "top"}})
|
||||
}
|
||||
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormatAsArray[0], g.Name, h.backend.BaseDN)
|
||||
return &ldap.Entry{DN: dn, Attributes: attrs}
|
||||
}
|
||||
|
||||
func (h databaseHandler) getAccount(hierarchy string, u config.User) *ldap.Entry {
|
||||
func (h databaseHandler) getAccount(ctx context.Context, hierarchy string, u config.User) *ldap.Entry {
|
||||
ctx, span := h.tracer.Start(ctx, "plugins.databaseHandler.getAccount")
|
||||
defer span.End()
|
||||
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"cn", []string{u.Name}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"uid", []string{u.Name}})
|
||||
for _, nameAttr := range h.backend.NameFormatAsArray {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: nameAttr, Values: []string{u.Name}})
|
||||
}
|
||||
|
||||
if len(u.GivenName) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"givenName", []string{u.GivenName}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "givenName", Values: []string{u.GivenName}})
|
||||
}
|
||||
|
||||
if len(u.SN) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"sn", []string{u.SN}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "sn", Values: []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)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{h.getGroupName(ctx, u.PrimaryGroup)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uidNumber", Values: []string{fmt.Sprintf("%d", u.UIDNumber)}})
|
||||
|
||||
if u.Disabled {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"accountStatus", []string{"inactive"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"accountStatus", []string{"active"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []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{Name: "mail", Values: []string{u.Mail}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "userPrincipalName", Values: []string{u.Mail}})
|
||||
}
|
||||
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixAccount"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixAccount"}})
|
||||
|
||||
if len(u.LoginShell) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"loginShell", []string{u.LoginShell}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "loginShell", Values: []string{u.LoginShell}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"loginShell", []string{"/bin/bash"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "loginShell", Values: []string{"/bin/bash"}})
|
||||
}
|
||||
|
||||
if len(u.Homedir) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"homeDirectory", []string{u.Homedir}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "homeDirectory", Values: []string{u.Homedir}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"homeDirectory", []string{"/home/" + u.Name}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "homeDirectory", Values: []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))})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s via LDAP", u.Name)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "gecos", Values: []string{fmt.Sprintf("%s via LDAP", 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(ctx, append(u.OtherGroups, u.PrimaryGroup))})
|
||||
if len(u.SSHKeys) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"sshPublicKey", u.SSHKeys})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "sshPublicKey", Values: 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)
|
||||
dn = fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormatAsArray[0], u.Name, h.backend.GroupFormatAsArray[0], h.getGroupName(ctx, 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)
|
||||
dn = fmt.Sprintf("%s=%s,%s=%s,%s,%s", h.backend.NameFormatAsArray[0], u.Name, h.backend.GroupFormatAsArray[0], h.getGroupName(ctx, u.PrimaryGroup), hierarchy, h.backend.BaseDN)
|
||||
}
|
||||
return &ldap.Entry{dn, attrs}
|
||||
return &ldap.Entry{DN: dn, Attributes: attrs}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
release-glauth-bogus:
|
||||
@echo Ignoring bogus plugin
|
||||
Submodule
+1
Submodule v2/pkg/plugins/glauth-mysql added at 37dae36d57
Submodule
+1
Submodule v2/pkg/plugins/glauth-pam added at aed21903e1
Submodule
+1
Submodule v2/pkg/plugins/glauth-postgres added at 81d4006ec2
Submodule
+1
Submodule v2/pkg/plugins/glauth-sqlite added at fdfef39435
@@ -0,0 +1,13 @@
|
||||
//go:build !(embedsqlite || embedmysql)
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/glauth/glauth/v2/pkg/handler"
|
||||
)
|
||||
|
||||
func NewEmbed(opts ...handler.Option) (handler.Handler, error) {
|
||||
return nil, errors.New("GLAuth was not built with support for an embedded plugin")
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//go:build embedsqlite
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/glauth/glauth/v2/pkg/embed"
|
||||
"github.com/glauth/glauth/v2/pkg/handler"
|
||||
)
|
||||
|
||||
func NewEmbed(opts ...handler.Option) (handler.Handler, error) {
|
||||
return embed.NewSQLiteHandler(opts...), nil
|
||||
}
|
||||
@@ -2,8 +2,12 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/rs/zerolog"
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/glauth/glauth/v2/internal/monitoring"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
)
|
||||
|
||||
@@ -12,9 +16,13 @@ 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
|
||||
Logger zerolog.Logger
|
||||
Config *config.Config
|
||||
StartTLSConfig *tls.Config
|
||||
LDAPSTLSConfig *tls.Config
|
||||
Monitor monitoring.MonitorInterface
|
||||
Tracer trace.Tracer
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
@@ -48,3 +56,31 @@ func Context(val context.Context) Option {
|
||||
o.Context = val
|
||||
}
|
||||
}
|
||||
|
||||
// TLSConfig provides a function to set the StartTLS config option.
|
||||
func StartTLSConfig(val *tls.Config) Option {
|
||||
return func(o *Options) {
|
||||
o.StartTLSConfig = val
|
||||
}
|
||||
}
|
||||
|
||||
// TLSConfig provides a function to set the LDAPSTls config option.
|
||||
func LDAPSTLSConfig(val *tls.Config) Option {
|
||||
return func(o *Options) {
|
||||
o.LDAPSTLSConfig = val
|
||||
}
|
||||
}
|
||||
|
||||
// Monitor provides a function to set the monitor option.
|
||||
func Monitor(val monitoring.MonitorInterface) Option {
|
||||
return func(o *Options) {
|
||||
o.Monitor = val
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer provides a function to set the tracer option.
|
||||
func Tracer(val trace.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = val
|
||||
}
|
||||
}
|
||||
|
||||
+68
-12
@@ -1,30 +1,42 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"plugin"
|
||||
|
||||
_tls "github.com/glauth/glauth/v2/internal/tls"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/v2/internal/monitoring"
|
||||
"github.com/glauth/glauth/v2/pkg/config"
|
||||
"github.com/glauth/glauth/v2/pkg/handler"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/glauth/ldap"
|
||||
)
|
||||
|
||||
type LdapSvc struct {
|
||||
log zerolog.Logger
|
||||
c *config.Config
|
||||
yubiAuth *yubigo.YubiAuth
|
||||
l *ldap.Server
|
||||
|
||||
ldapstls *tls.Config
|
||||
monitor monitoring.MonitorInterface
|
||||
tracer trace.Tracer
|
||||
log zerolog.Logger
|
||||
}
|
||||
|
||||
func NewServer(opts ...Option) (*LdapSvc, error) {
|
||||
options := newOptions(opts...)
|
||||
|
||||
s := LdapSvc{
|
||||
log: options.Logger,
|
||||
c: options.Config,
|
||||
log: options.Logger,
|
||||
c: options.Config,
|
||||
monitor: options.Monitor,
|
||||
tracer: options.Tracer,
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -39,7 +51,7 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
||||
|
||||
var helper handler.Handler
|
||||
|
||||
loh := handler.NewLDAPOpsHelper()
|
||||
loh := handler.NewLDAPOpsHelper(s.tracer)
|
||||
|
||||
// instantiate the helper, if any
|
||||
if s.c.Helper.Enabled {
|
||||
@@ -50,6 +62,7 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
||||
handler.Config(s.c),
|
||||
handler.YubiAuth(s.yubiAuth),
|
||||
handler.LDAPHelper(loh),
|
||||
handler.Tracer(s.tracer),
|
||||
)
|
||||
case "plugin":
|
||||
plug, err := plugin.Open(s.c.Helper.Plugin)
|
||||
@@ -72,6 +85,7 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
||||
handler.Config(s.c),
|
||||
handler.YubiAuth(s.yubiAuth),
|
||||
handler.LDAPHelper(loh),
|
||||
handler.Tracer(s.tracer),
|
||||
)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported helper %s - must be one of 'config', 'plugin'", s.c.Helper.Datastore)
|
||||
@@ -85,6 +99,25 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
||||
// configure the backends
|
||||
s.l = ldap.NewServer()
|
||||
s.l.EnforceLDAP = true
|
||||
|
||||
if tlsConfig := options.StartTLSConfig; tlsConfig != nil {
|
||||
s.l.TLSConfig = tlsConfig
|
||||
s.log.Info().
|
||||
Str("tls.min_version", tls.VersionName(tlsConfig.MinVersion)).
|
||||
Str("tls.max_version", tls.VersionName(tlsConfig.MaxVersion)).
|
||||
Interface("tls.cipher_suites", _tls.CipherSuiteNames(tlsConfig.CipherSuites)).
|
||||
Msg("enabling LDAP over TLS")
|
||||
}
|
||||
|
||||
if tlsConfig := options.LDAPSTLSConfig; tlsConfig != nil {
|
||||
s.ldapstls = tlsConfig
|
||||
s.log.Info().
|
||||
Str("tls.min_version", tls.VersionName(tlsConfig.MinVersion)).
|
||||
Str("tls.max_version", tls.VersionName(tlsConfig.MaxVersion)).
|
||||
Interface("tls.cipher_suites", _tls.CipherSuiteNames(tlsConfig.CipherSuites)).
|
||||
Msg("enabling LDAPS")
|
||||
}
|
||||
|
||||
for i, backend := range s.c.Backends {
|
||||
var h handler.Handler
|
||||
switch backend.Datastore {
|
||||
@@ -94,11 +127,15 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
||||
handler.Handlers(allHandlers),
|
||||
handler.Logger(&s.log),
|
||||
handler.Helper(helper),
|
||||
handler.Monitor(s.monitor),
|
||||
handler.Tracer(s.tracer),
|
||||
)
|
||||
case "owncloud":
|
||||
h = handler.NewOwnCloudHandler(
|
||||
handler.Backend(backend),
|
||||
handler.Logger(&s.log),
|
||||
handler.Monitor(s.monitor),
|
||||
handler.Tracer(s.tracer),
|
||||
)
|
||||
case "config":
|
||||
h = handler.NewConfigHandler(
|
||||
@@ -107,11 +144,13 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
||||
handler.Config(s.c), // TODO only used to access Users and Groups, move that to dedicated options
|
||||
handler.YubiAuth(s.yubiAuth),
|
||||
handler.LDAPHelper(loh),
|
||||
handler.Monitor(s.monitor),
|
||||
handler.Tracer(s.tracer),
|
||||
)
|
||||
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))
|
||||
return nil, fmt.Errorf("unable to load specified backend plugin: %s", err)
|
||||
}
|
||||
nph, err := plug.Lookup(backend.PluginHandler)
|
||||
if err != nil {
|
||||
@@ -130,7 +169,22 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
||||
handler.Config(s.c),
|
||||
handler.YubiAuth(s.yubiAuth),
|
||||
handler.LDAPHelper(loh),
|
||||
handler.Monitor(s.monitor),
|
||||
handler.Tracer(s.tracer),
|
||||
)
|
||||
case "embed":
|
||||
h, err = NewEmbed(
|
||||
handler.Backend(backend),
|
||||
handler.Logger(&s.log),
|
||||
handler.Config(s.c),
|
||||
handler.YubiAuth(s.yubiAuth),
|
||||
handler.LDAPHelper(loh),
|
||||
handler.Monitor(s.monitor),
|
||||
handler.Tracer(s.tracer),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported backend %s - must be one of 'config', 'ldap','owncloud' or 'plugin'", backend.Datastore)
|
||||
}
|
||||
@@ -148,6 +202,8 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
||||
backendCounter++
|
||||
}
|
||||
|
||||
monitoring.NewLDAPMonitorWatcher(s.l, s.monitor, &s.log)
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
@@ -160,11 +216,11 @@ func (s *LdapSvc) ListenAndServe() error {
|
||||
// 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,
|
||||
)
|
||||
listener, err := tls.Listen("tcp", s.c.LDAPS.Listen, s.ldapstls)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.l.Serve(listener)
|
||||
}
|
||||
|
||||
// Shutdown ends listeners by sending true to the ldap serves quit channel
|
||||
|
||||
@@ -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://192.168.1.222: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"
|
||||
|
||||
+35
-8
@@ -3,7 +3,7 @@
|
||||
|
||||
#################
|
||||
# General configuration.
|
||||
debug = true
|
||||
debug = false
|
||||
# syslog = true
|
||||
# structuredlog = true
|
||||
#
|
||||
@@ -21,23 +21,38 @@ debug = true
|
||||
enabled = true
|
||||
# run on a non privileged port
|
||||
listen = "0.0.0.0:3893"
|
||||
tls = false # enable StartTLS support
|
||||
tlsCertPath = "glauth.crt"
|
||||
tlsKeyPath = "glauth.key"
|
||||
|
||||
[ldaps]
|
||||
# to enable ldaps genrerate a certificate, eg. with:
|
||||
# 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
|
||||
enabled = true
|
||||
listen = "0.0.0.0:3894"
|
||||
cert = "glauth.crt"
|
||||
key = "glauth.key"
|
||||
|
||||
#################
|
||||
# Tracing section controls the tracer configuration
|
||||
[tracing]
|
||||
# if enabled is set to false, a no-op tracer will be used
|
||||
enabled = true
|
||||
# if both grpcEndpoint and httpEndpoint are unset, the default stdout provider will be used
|
||||
# TODO add allowGRPCInsecure: right now grpc otlp is using the WithInsecure flag so traffic
|
||||
# will always go without verifying server certificates
|
||||
# grpcEndpoint = "otlp.monitoring.io:4317"
|
||||
# httpEndpoint = "http://otlp.monitoring.io:4318"
|
||||
#################
|
||||
# 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 = false
|
||||
|
||||
## 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
|
||||
@@ -130,6 +145,11 @@ debug = true
|
||||
# passappbcrypt = "243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
|
||||
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
|
||||
|
||||
[[users]]
|
||||
name = "danger"
|
||||
uidnumber = 5006
|
||||
primarygroup = 5503
|
||||
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
|
||||
|
||||
#################
|
||||
# The groups section contains a hardcoded list of valid users.
|
||||
@@ -142,9 +162,16 @@ debug = true
|
||||
gidnumber = 5502
|
||||
|
||||
[[groups]]
|
||||
name = "vpn"
|
||||
name = "umbrella"
|
||||
gidnumber = 5503
|
||||
includegroups = [ 5501 ]
|
||||
includegroups = [ 5504 ]
|
||||
|
||||
[[groups]]
|
||||
name = "danger"
|
||||
gidnumber = 5504
|
||||
[[groups.capabilities]]
|
||||
action = "search"
|
||||
object = "dc=glauth,dc=com"
|
||||
|
||||
#################
|
||||
# Enable and configure the optional REST API here.
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
dn: ou=superheros,ou=users,dc=glauth,dc=com
|
||||
ou: superheros
|
||||
cn: superheros
|
||||
uid: superheros
|
||||
description: superheros
|
||||
gidNumber: 5501
|
||||
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=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
|
||||
@@ -19,26 +19,26 @@ objectClass: posixGroup
|
||||
objectClass: top
|
||||
|
||||
dn: ou=svcaccts,ou=users,dc=glauth,dc=com
|
||||
ou: svcaccts
|
||||
cn: svcaccts
|
||||
uid: svcaccts
|
||||
description: svcaccts
|
||||
gidNumber: 5502
|
||||
uniqueMember: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
|
||||
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
|
||||
cn: vpnaccess
|
||||
uid: vpnaccess
|
||||
description: vpnaccess
|
||||
gidNumber: 5503
|
||||
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=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
|
||||
@@ -49,17 +49,17 @@ objectClass: posixGroup
|
||||
objectClass: top
|
||||
|
||||
dn: ou=allaccs,ou=users,dc=glauth,dc=com
|
||||
ou: allaccs
|
||||
cn: allaccs
|
||||
uid: allaccs
|
||||
description: allaccs
|
||||
gidNumber: 5504
|
||||
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
|
||||
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
|
||||
@@ -71,14 +71,14 @@ objectClass: posixGroup
|
||||
objectClass: top
|
||||
|
||||
dn: ou=mailadmin,ou=users,dc=glauth,dc=com
|
||||
ou: mailadmin
|
||||
cn: mailadmin
|
||||
uid: mailadmin
|
||||
description: mailadmin
|
||||
gidNumber: 5505
|
||||
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
|
||||
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
|
||||
@@ -87,22 +87,22 @@ objectClass: posixGroup
|
||||
objectClass: top
|
||||
|
||||
dn: ou=webmail,ou=users,dc=glauth,dc=com
|
||||
ou: webmail
|
||||
cn: webmail
|
||||
uid: webmail
|
||||
description: webmail
|
||||
gidNumber: 5506
|
||||
objectClass: posixGroup
|
||||
objectClass: top
|
||||
|
||||
dn: ou=fulltime,ou=users,dc=glauth,dc=com
|
||||
ou: fulltime
|
||||
cn: fulltime
|
||||
uid: fulltime
|
||||
description: fulltime
|
||||
gidNumber: 5507
|
||||
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
|
||||
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
|
||||
|
||||
@@ -24,6 +24,22 @@ echo "Fixing CI_BUILD_DIR"
|
||||
|
||||
# This script requires that "$CI_BUILD_DIR" is set to the repo root
|
||||
|
||||
GOOS="$(go env GOOS)"
|
||||
GOARCH="$(go env GOARCH)"
|
||||
if [[ "$GOARCH" = "arm" ]]; then
|
||||
DIST_ARCH="arm-$(go env GOARM)"
|
||||
else
|
||||
DIST_ARCH="$GOARCH"
|
||||
fi
|
||||
GLAUTH_BINARY="$CI_BUILD_DIR/bin/$GOOS$GOARCH/glauth"
|
||||
if [[ ! -x "$GLAUTH_BINARY" ]]; then
|
||||
GLAUTH_BINARY="$CI_BUILD_DIR/bin/glauth-$GOOS-$DIST_ARCH"
|
||||
fi
|
||||
if [[ ! -x "$GLAUTH_BINARY" ]]; then
|
||||
echo "Unable to find a runnable GLAuth binary: $GLAUTH_BINARY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure ldap utils are installed (for example - when running this outside of CI)
|
||||
if [[ ! `which ldapsearch` ]]; then
|
||||
sudo apt-get -qq update && sudo apt-get -qq install -y ldap-utils || exit 1;
|
||||
@@ -33,14 +49,14 @@ fi
|
||||
echo "";
|
||||
echo ""
|
||||
echo "Version string of tested binary:"
|
||||
"$CI_BUILD_DIR/bin/linuxamd64/glauth" --version
|
||||
"$GLAUTH_BINARY" --version
|
||||
echo ""
|
||||
|
||||
# Start in background, capture PID
|
||||
"$CI_BUILD_DIR/bin/linuxamd64/glauth" -c "$CI_BUILD_DIR/scripts/ci/test-config.cfg" &> /dev/null &
|
||||
"$GLAUTH_BINARY" -c "$CI_BUILD_DIR/scripts/ci/test-config.cfg" &> /dev/null &
|
||||
|
||||
# Use this instead to see glauth logs while running
|
||||
# "$CI_BUILD_DIR/bin/linuxamd64/glauth" -c "$CI_BUILD_DIR/scripts/ci/test-config.cfg" &
|
||||
# "$CI_BUILD_DIR/bin/$(go env GOOS)$(go env GOARCH)/glauth" -c "$CI_BUILD_DIR/scripts/ci/test-config.cfg" &
|
||||
glauthPid="$!"
|
||||
|
||||
echo "Running glauth at PID=$glauthPid"
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
Vendored libraries. Not to be mistaken for libraries included under `vendor/` which is a go convention.
|
||||
|
||||
To upgrade toml:
|
||||
|
||||
1. clone from upstream
|
||||
2. add to `decode_meta.go`:
|
||||
|
||||
```
|
||||
func (md *MetaData) Mappings() map[string]interface{} {
|
||||
return md.mapping
|
||||
}
|
||||
```
|
||||
3. Run `gofmt -w ./` in `vendored/`
|
||||
@@ -1,3 +0,0 @@
|
||||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,19 +0,0 @@
|
||||
install:
|
||||
go install ./...
|
||||
|
||||
test: install
|
||||
go test -v
|
||||
toml-test toml-test-decoder
|
||||
toml-test -encoder toml-test-encoder
|
||||
|
||||
fmt:
|
||||
gofmt -w *.go */*.go
|
||||
colcheck *.go */*.go
|
||||
|
||||
tags:
|
||||
find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS
|
||||
|
||||
push:
|
||||
git push origin master
|
||||
git push github master
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
[](https://travis-ci.org/hydronica/toml)
|
||||
|
||||
This is a modified and opinionated fork of https://github.com/BurntSushi/toml with the following changes
|
||||
|
||||
- time.Duration is always encoded as a string ("10s" or "10ns")
|
||||
- support encoding comments (similar functionality to https://github.com/pelletier/go-toml.v1)
|
||||
- comments can be added using the comment tag `comment:"a well thought-out comment"`
|
||||
- valid toml can be commented out using the commented tag `commented:"true"`
|
||||
- Only guarantees last two years of golang compatibility (1.10 to current)
|
||||
|
||||
## TOML parser and encoder for Go with reflection
|
||||
|
||||
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
|
||||
reflection interface similar to Go's standard library `json` and `xml`
|
||||
packages. This package also supports the `encoding.TextUnmarshaler` and
|
||||
`encoding.TextMarshaler` interfaces so that you can define custom data
|
||||
representations. (There is an example of this below.)
|
||||
|
||||
Spec: https://github.com/toml-lang/toml
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||
|
||||
Documentation: https://godoc.org/github.com/BurntSushi/toml
|
||||
|
||||
Installation:
|
||||
|
||||
```bash
|
||||
go get github.com/hydronica/toml
|
||||
```
|
||||
|
||||
Try the toml validator:
|
||||
|
||||
```bash
|
||||
go get github.com/hydronica/toml/cmd/tomlv
|
||||
tomlv some-toml-file.toml
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
This package passes all tests in
|
||||
[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder
|
||||
and the encoder.
|
||||
|
||||
### Examples
|
||||
|
||||
This package works similarly to how the Go standard library handles `XML`
|
||||
and `JSON`. Namely, data is loaded into Go values via reflection.
|
||||
|
||||
For the simplest example, consider some TOML file as just a list of keys
|
||||
and values:
|
||||
|
||||
```toml
|
||||
Age = 25
|
||||
Cats = [ "Cauchy", "Plato" ]
|
||||
Pi = 3.14
|
||||
Perfection = [ 6, 28, 496, 8128 ]
|
||||
DOB = 1987-07-05T05:45:00Z
|
||||
```
|
||||
|
||||
Which could be defined in Go as:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Age int
|
||||
Cats []string
|
||||
Pi float64
|
||||
Perfection []int
|
||||
DOB time.Time // requires `import time`
|
||||
}
|
||||
```
|
||||
|
||||
And then decoded with:
|
||||
|
||||
```go
|
||||
var conf Config
|
||||
if _, err := toml.Decode(tomlData, &conf); err != nil {
|
||||
// handle error
|
||||
}
|
||||
```
|
||||
|
||||
You can also use struct tags if your struct field name doesn't map to a TOML
|
||||
key value directly:
|
||||
|
||||
```toml
|
||||
some_key_NAME = "wat"
|
||||
```
|
||||
|
||||
```go
|
||||
type TOML struct {
|
||||
ObscureKey string `toml:"some_key_NAME"`
|
||||
}
|
||||
```
|
||||
|
||||
### Using the `encoding.TextUnmarshaler` interface
|
||||
|
||||
Here's an example that automatically parses duration strings into
|
||||
`time.Duration` values:
|
||||
|
||||
```toml
|
||||
[[song]]
|
||||
name = "Thunder Road"
|
||||
duration = "4m49s"
|
||||
|
||||
[[song]]
|
||||
name = "Stairway to Heaven"
|
||||
duration = "8m03s"
|
||||
```
|
||||
|
||||
Which can be decoded with:
|
||||
|
||||
```go
|
||||
type song struct {
|
||||
Name string
|
||||
Duration duration
|
||||
}
|
||||
type songs struct {
|
||||
Song []song
|
||||
}
|
||||
var favorites songs
|
||||
if _, err := toml.Decode(blob, &favorites); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, s := range favorites.Song {
|
||||
fmt.Printf("%s (%s)\n", s.Name, s.Duration)
|
||||
}
|
||||
```
|
||||
|
||||
And you'll also need a `duration` type that satisfies the
|
||||
`encoding.TextUnmarshaler` interface:
|
||||
|
||||
```go
|
||||
type duration struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
func (d *duration) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
d.Duration, err = time.ParseDuration(string(text))
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
### More complex usage
|
||||
|
||||
Here's an example of how to load the example from the official spec page:
|
||||
|
||||
```toml
|
||||
# This is a TOML document. Boom.
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
organization = "GitHub"
|
||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
||||
|
||||
[database]
|
||||
server = "192.168.1.1"
|
||||
ports = [ 8001, 8001, 8002 ]
|
||||
connection_max = 5000
|
||||
enabled = true
|
||||
|
||||
[servers]
|
||||
|
||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
||||
[servers.alpha]
|
||||
ip = "10.0.0.1"
|
||||
dc = "eqdc10"
|
||||
|
||||
[servers.beta]
|
||||
ip = "10.0.0.2"
|
||||
dc = "eqdc10"
|
||||
|
||||
[clients]
|
||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
||||
|
||||
# Line breaks are OK when inside arrays
|
||||
hosts = [
|
||||
"alpha",
|
||||
"omega"
|
||||
]
|
||||
```
|
||||
|
||||
And the corresponding Go types are:
|
||||
|
||||
```go
|
||||
type tomlConfig struct {
|
||||
Title string
|
||||
Owner ownerInfo
|
||||
DB database `toml:"database"`
|
||||
Servers map[string]server
|
||||
Clients clients
|
||||
}
|
||||
|
||||
type ownerInfo struct {
|
||||
Name string
|
||||
Org string `toml:"organization"`
|
||||
Bio string
|
||||
DOB time.Time
|
||||
}
|
||||
|
||||
type database struct {
|
||||
Server string
|
||||
Ports []int
|
||||
ConnMax int `toml:"connection_max"`
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type server struct {
|
||||
IP string
|
||||
DC string
|
||||
}
|
||||
|
||||
type clients struct {
|
||||
Data [][]interface{}
|
||||
Hosts []string
|
||||
}
|
||||
```
|
||||
|
||||
Note that a case insensitive match will be tried if an exact match can't be
|
||||
found.
|
||||
|
||||
A working example of the above can be found in `_examples/example.{go,toml}`.
|
||||
@@ -1,61 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hydronica/toml"
|
||||
)
|
||||
|
||||
type tomlConfig struct {
|
||||
Title string
|
||||
Owner ownerInfo
|
||||
DB database `toml:"database"`
|
||||
Servers map[string]server
|
||||
Clients clients
|
||||
}
|
||||
|
||||
type ownerInfo struct {
|
||||
Name string
|
||||
Org string `toml:"organization"`
|
||||
Bio string
|
||||
DOB time.Time
|
||||
}
|
||||
|
||||
type database struct {
|
||||
Server string
|
||||
Ports []int
|
||||
ConnMax int `toml:"connection_max"`
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type server struct {
|
||||
IP string
|
||||
DC string
|
||||
}
|
||||
|
||||
type clients struct {
|
||||
Data [][]interface{}
|
||||
Hosts []string
|
||||
}
|
||||
|
||||
func main() {
|
||||
var config tomlConfig
|
||||
if _, err := toml.DecodeFile("example.toml", &config); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Title: %s\n", config.Title)
|
||||
fmt.Printf("Owner: %s (%s, %s), Born: %s\n",
|
||||
config.Owner.Name, config.Owner.Org, config.Owner.Bio,
|
||||
config.Owner.DOB)
|
||||
fmt.Printf("Database: %s %v (Max conn. %d), Enabled? %v\n",
|
||||
config.DB.Server, config.DB.Ports, config.DB.ConnMax,
|
||||
config.DB.Enabled)
|
||||
for serverName, server := range config.Servers {
|
||||
fmt.Printf("Server: %s (%s, %s)\n", serverName, server.IP, server.DC)
|
||||
}
|
||||
fmt.Printf("Client data: %v\n", config.Clients.Data)
|
||||
fmt.Printf("Client hosts: %v\n", config.Clients.Hosts)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
# This is a TOML document. Boom.
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
organization = "GitHub"
|
||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
||||
|
||||
[database]
|
||||
server = "192.168.1.1"
|
||||
ports = [ 8001, 8001, 8002 ]
|
||||
connection_max = 5000
|
||||
enabled = true
|
||||
|
||||
[servers]
|
||||
|
||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
||||
[servers.alpha]
|
||||
ip = "10.0.0.1"
|
||||
dc = "eqdc10"
|
||||
|
||||
[servers.beta]
|
||||
ip = "10.0.0.2"
|
||||
dc = "eqdc10"
|
||||
|
||||
[clients]
|
||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
||||
|
||||
# Line breaks are OK when inside arrays
|
||||
hosts = [
|
||||
"alpha",
|
||||
"omega"
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
# Test file for TOML
|
||||
# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
|
||||
# This part you'll really hate
|
||||
|
||||
[the]
|
||||
test_string = "You'll hate me after this - #" # " Annoying, isn't it?
|
||||
|
||||
[the.hard]
|
||||
test_array = [ "] ", " # "] # ] There you go, parse this!
|
||||
test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
|
||||
# You didn't think it'd as easy as chucking out the last #, did you?
|
||||
another_test_string = " Same thing, but with a string #"
|
||||
harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too"
|
||||
# Things will get harder
|
||||
|
||||
[the.hard.bit#]
|
||||
what? = "You don't think some user won't do that?"
|
||||
multi_line_array = [
|
||||
"]",
|
||||
# ] Oh yes I did
|
||||
]
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# [x] you
|
||||
# [x.y] don't
|
||||
# [x.y.z] need these
|
||||
[x.y.z.w] # for this to work
|
||||
@@ -1,6 +0,0 @@
|
||||
# DO NOT WANT
|
||||
[fruit]
|
||||
type = "apple"
|
||||
|
||||
[fruit.type]
|
||||
apple = "yes"
|
||||
@@ -1,35 +0,0 @@
|
||||
# This is an INVALID TOML document. Boom.
|
||||
# Can you spot the error without help?
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
organization = "GitHub"
|
||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||
dob = 1979-05-27T7:32:00Z # First class dates? Why not?
|
||||
|
||||
[database]
|
||||
server = "192.168.1.1"
|
||||
ports = [ 8001, 8001, 8002 ]
|
||||
connection_max = 5000
|
||||
enabled = true
|
||||
|
||||
[servers]
|
||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
||||
[servers.alpha]
|
||||
ip = "10.0.0.1"
|
||||
dc = "eqdc10"
|
||||
|
||||
[servers.beta]
|
||||
ip = "10.0.0.2"
|
||||
dc = "eqdc10"
|
||||
|
||||
[clients]
|
||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
||||
|
||||
# Line breaks are OK when inside arrays
|
||||
hosts = [
|
||||
"alpha",
|
||||
"omega"
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
Age = 25
|
||||
Cats = [ "Cauchy", "Plato" ]
|
||||
Pi = 3.14
|
||||
Perfection = [ 6, 28, 496, 8128 ]
|
||||
DOB = 1987-07-05T05:45:00Z
|
||||
@@ -1 +0,0 @@
|
||||
some_key_NAME = "wat"
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,13 +0,0 @@
|
||||
# Implements the TOML test suite interface
|
||||
|
||||
This is an implementation of the interface expected by
|
||||
[toml-test](https://github.com/BurntSushi/toml-test) for my
|
||||
[toml parser written in Go](https://github.com/BurntSushi/toml).
|
||||
In particular, it maps TOML data on `stdin` to a JSON format on `stdout`.
|
||||
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||
|
||||
Compatible with `toml-test` version
|
||||
[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)
|
||||
@@ -1,90 +0,0 @@
|
||||
// Command toml-test-decoder satisfies the toml-test interface for testing
|
||||
// TOML decoders. Namely, it accepts TOML on stdin and outputs JSON on stdout.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/hydronica/toml"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func usage() {
|
||||
log.Printf("Usage: %s < toml-file\n", path.Base(os.Args[0]))
|
||||
flag.PrintDefaults()
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
}
|
||||
|
||||
var tmp interface{}
|
||||
if _, err := toml.DecodeReader(os.Stdin, &tmp); err != nil {
|
||||
log.Fatalf("Error decoding TOML: %s", err)
|
||||
}
|
||||
|
||||
typedTmp := translate(tmp)
|
||||
if err := json.NewEncoder(os.Stdout).Encode(typedTmp); err != nil {
|
||||
log.Fatalf("Error encoding JSON: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func translate(tomlData interface{}) interface{} {
|
||||
switch orig := tomlData.(type) {
|
||||
case map[string]interface{}:
|
||||
typed := make(map[string]interface{}, len(orig))
|
||||
for k, v := range orig {
|
||||
typed[k] = translate(v)
|
||||
}
|
||||
return typed
|
||||
case []map[string]interface{}:
|
||||
typed := make([]map[string]interface{}, len(orig))
|
||||
for i, v := range orig {
|
||||
typed[i] = translate(v).(map[string]interface{})
|
||||
}
|
||||
return typed
|
||||
case []interface{}:
|
||||
typed := make([]interface{}, len(orig))
|
||||
for i, v := range orig {
|
||||
typed[i] = translate(v)
|
||||
}
|
||||
|
||||
// We don't really need to tag arrays, but let's be future proof.
|
||||
// (If TOML ever supports tuples, we'll need this.)
|
||||
return tag("array", typed)
|
||||
case time.Time:
|
||||
return tag("datetime", orig.Format("2006-01-02T15:04:05Z"))
|
||||
case bool:
|
||||
return tag("bool", fmt.Sprintf("%v", orig))
|
||||
case int64:
|
||||
return tag("integer", fmt.Sprintf("%d", orig))
|
||||
case float64:
|
||||
return tag("float", fmt.Sprintf("%v", orig))
|
||||
case string:
|
||||
return tag("string", orig)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Unknown type: %T", tomlData))
|
||||
}
|
||||
|
||||
func tag(typeName string, data interface{}) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"type": typeName,
|
||||
"value": data,
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,13 +0,0 @@
|
||||
# Implements the TOML test suite interface for TOML encoders
|
||||
|
||||
This is an implementation of the interface expected by
|
||||
[toml-test](https://github.com/BurntSushi/toml-test) for the
|
||||
[TOML encoder](https://github.com/BurntSushi/toml).
|
||||
In particular, it maps JSON data on `stdin` to a TOML format on `stdout`.
|
||||
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||
|
||||
Compatible with `toml-test` version
|
||||
[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)
|
||||
@@ -1,131 +0,0 @@
|
||||
// Command toml-test-encoder satisfies the toml-test interface for testing
|
||||
// TOML encoders. Namely, it accepts JSON on stdin and outputs TOML on stdout.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/hydronica/toml"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func usage() {
|
||||
log.Printf("Usage: %s < json-file\n", path.Base(os.Args[0]))
|
||||
flag.PrintDefaults()
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
}
|
||||
|
||||
var tmp interface{}
|
||||
if err := json.NewDecoder(os.Stdin).Decode(&tmp); err != nil {
|
||||
log.Fatalf("Error decoding JSON: %s", err)
|
||||
}
|
||||
|
||||
tomlData := translate(tmp)
|
||||
if err := toml.NewEncoder(os.Stdout).Encode(tomlData); err != nil {
|
||||
log.Fatalf("Error encoding TOML: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func translate(typedJson interface{}) interface{} {
|
||||
switch v := typedJson.(type) {
|
||||
case map[string]interface{}:
|
||||
if len(v) == 2 && in("type", v) && in("value", v) {
|
||||
return untag(v)
|
||||
}
|
||||
m := make(map[string]interface{}, len(v))
|
||||
for k, v2 := range v {
|
||||
m[k] = translate(v2)
|
||||
}
|
||||
return m
|
||||
case []interface{}:
|
||||
tabArray := make([]map[string]interface{}, len(v))
|
||||
for i := range v {
|
||||
if m, ok := translate(v[i]).(map[string]interface{}); ok {
|
||||
tabArray[i] = m
|
||||
} else {
|
||||
log.Fatalf("JSON arrays may only contain objects. This " +
|
||||
"corresponds to only tables being allowed in " +
|
||||
"TOML table arrays.")
|
||||
}
|
||||
}
|
||||
return tabArray
|
||||
}
|
||||
log.Fatalf("Unrecognized JSON format '%T'.", typedJson)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func untag(typed map[string]interface{}) interface{} {
|
||||
t := typed["type"].(string)
|
||||
v := typed["value"]
|
||||
switch t {
|
||||
case "string":
|
||||
return v.(string)
|
||||
case "integer":
|
||||
v := v.(string)
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse '%s' as integer: %s", v, err)
|
||||
}
|
||||
return n
|
||||
case "float":
|
||||
v := v.(string)
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse '%s' as float64: %s", v, err)
|
||||
}
|
||||
return f
|
||||
case "datetime":
|
||||
v := v.(string)
|
||||
t, err := time.Parse("2006-01-02T15:04:05Z", v)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse '%s' as a datetime: %s", v, err)
|
||||
}
|
||||
return t
|
||||
case "bool":
|
||||
v := v.(string)
|
||||
switch v {
|
||||
case "true":
|
||||
return true
|
||||
case "false":
|
||||
return false
|
||||
}
|
||||
log.Fatalf("Could not parse '%s' as a boolean.", v)
|
||||
case "array":
|
||||
v := v.([]interface{})
|
||||
array := make([]interface{}, len(v))
|
||||
for i := range v {
|
||||
if m, ok := v[i].(map[string]interface{}); ok {
|
||||
array[i] = untag(m)
|
||||
} else {
|
||||
log.Fatalf("Arrays may only contain other arrays or "+
|
||||
"primitive values, but found a '%T'.", m)
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
log.Fatalf("Unrecognized tag type '%s'.", t)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func in(key string, m map[string]interface{}) bool {
|
||||
_, ok := m[key]
|
||||
return ok
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,21 +0,0 @@
|
||||
# TOML Validator
|
||||
|
||||
If Go is installed, it's simple to try it out:
|
||||
|
||||
```bash
|
||||
go get github.com/BurntSushi/toml/cmd/tomlv
|
||||
tomlv some-toml-file.toml
|
||||
```
|
||||
|
||||
You can see the types of every key in a TOML file with:
|
||||
|
||||
```bash
|
||||
tomlv -types some-toml-file.toml
|
||||
```
|
||||
|
||||
At the moment, only one error message is reported at a time. Error messages
|
||||
include line numbers. No output means that the files given are valid TOML, or
|
||||
there is a bug in `tomlv`.
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user