mirror of
https://github.com/glauth/glauth.git
synced 2026-10-27 06:26:59 +00:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d93386123f | |||
| 752333e274 | |||
| 8d1ea390ba | |||
| 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 |
@@ -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:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.19.x, 1.20.x]
|
go-version: [1.25.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
- name: Updating packages database
|
- name: Updating packages database
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Install CLI Dependencies
|
- name: Install CLI Dependencies
|
||||||
run: sudo apt-get install -y ldap-utils oathtool libpam0g-dev
|
run: sudo apt-get install -y ldap-utils oathtool libpam0g-dev
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
- name: Fetch Go dependencies
|
- name: Fetch Go dependencies
|
||||||
working-directory: ./v2
|
working-directory: ./v2
|
||||||
run: go get -t -v ./...
|
run: go get -t -v ./...
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: "CodeQL"
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "master", "dev" ]
|
branches: [ "master" ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "master" ]
|
branches: [ "master" ]
|
||||||
schedule:
|
schedule:
|
||||||
@@ -24,18 +24,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-and-quality
|
queries: +security-and-quality
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
with:
|
with:
|
||||||
category: "/language:${{ matrix.language }}"
|
category: "/language:${{ matrix.language }}"
|
||||||
|
|||||||
@@ -1,27 +1,20 @@
|
|||||||
|
name: release-please
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- master
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
name: release-please
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-please:
|
release-please:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: google-github-actions/release-please-action@v4
|
||||||
- uses: google-github-actions/release-please-action@v3
|
|
||||||
with:
|
with:
|
||||||
release-type: go
|
|
||||||
package-name: GLAuth
|
|
||||||
path: v2
|
|
||||||
default-branch: master
|
|
||||||
pull-request-title-pattern: "ci: release ${version}"
|
|
||||||
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
||||||
extra-files: |
|
config-file: release-please-config.json
|
||||||
v2/internal/version/const.go
|
manifest-file: .release-please-manifest.json
|
||||||
|
|||||||
+36
@@ -3,4 +3,40 @@ v2/certs/
|
|||||||
v2/bin
|
v2/bin
|
||||||
v2/docker/assets/linux
|
v2/docker/assets/linux
|
||||||
v2/gl.db
|
v2/gl.db
|
||||||
|
v2/buildlogs
|
||||||
|
.idea/
|
||||||
.vscode/
|
.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
|
||||||
|
|||||||
+4
-4
@@ -1,12 +1,12 @@
|
|||||||
[submodule "v2/pkg/plugins/glauth-sqlite"]
|
[submodule "v2/pkg/plugins/glauth-sqlite"]
|
||||||
path = v2/pkg/plugins/glauth-sqlite
|
path = v2/pkg/plugins/glauth-sqlite
|
||||||
url = https://github.com/glauth/glauth-sqlite
|
url = git@github.com:glauth/glauth-sqlite
|
||||||
[submodule "v2/pkg/plugins/glauth-mysql"]
|
[submodule "v2/pkg/plugins/glauth-mysql"]
|
||||||
path = v2/pkg/plugins/glauth-mysql
|
path = v2/pkg/plugins/glauth-mysql
|
||||||
url = https://github.com/glauth/glauth-mysql
|
url = git@github.com:glauth/glauth-mysql
|
||||||
[submodule "v2/pkg/plugins/glauth-postgres"]
|
[submodule "v2/pkg/plugins/glauth-postgres"]
|
||||||
path = v2/pkg/plugins/glauth-postgres
|
path = v2/pkg/plugins/glauth-postgres
|
||||||
url = https://github.com/glauth/glauth-postgres
|
url = git@github.com:glauth/glauth-postgres
|
||||||
[submodule "v2/pkg/plugins/glauth-pam"]
|
[submodule "v2/pkg/plugins/glauth-pam"]
|
||||||
path = v2/pkg/plugins/glauth-pam
|
path = v2/pkg/plugins/glauth-pam
|
||||||
url = https://github.com/glauth/glauth-pam
|
url = git@github.com:glauth/glauth-pam
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"v2": "2.5.4"
|
||||||
|
}
|
||||||
@@ -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
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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)
|
# GLAuth: LDAP authentication server for developers
|
||||||
[](hey)
|
[](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 accounts across your infrastructure
|
||||||
* Centrally manage SSH keys, Linux accounts, and passwords for cloud servers.
|
* Centrally manage SSH keys, Linux accounts, and passwords for cloud servers.
|
||||||
@@ -21,7 +21,7 @@ Use it to centralize account management across your Linux servers, your OSX mach
|
|||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
- Please base all Pull Requests on [dev](https://github.com/glauth/glauth/tree/dev), not master.
|
- Please base all Pull Requests on [dev](https://github.com/glauth/glauth/tree/dev), not master.
|
||||||
- Format your code autonmatically using `gofmt -d ./` before committing
|
- Format your code automatically using `gofmt -d ./` before committing
|
||||||
|
|
||||||
### Quickstart
|
### 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!*
|
This quickstart is a great way to try out GLAuth in a non-production environment. *Be warned that you should take the extra steps to setup SSL (TLS) for production use!*
|
||||||
@@ -110,4 +110,3 @@ For advanced users, GLAuth supports pluggable backends. Currently, it can use a
|
|||||||
# Stargazers over time
|
# Stargazers over time
|
||||||
|
|
||||||
[](https://starchart.cc/glauth/glauth)
|
[](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"
|
||||||
|
}
|
||||||
+117
@@ -1,5 +1,122 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [2.5.4](https://github.com/glauth/glauth/compare/GLAuth-v2.5.3...GLAuth-v2.5.4) (2026-09-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* revert pam plugin to msteinert/pam v1 ([#487](https://github.com/glauth/glauth/issues/487)) ([8d1ea39](https://github.com/glauth/glauth/commit/8d1ea390bacc74a54d9b2046e514b7e0336cb08b))
|
||||||
|
|
||||||
|
## [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)
|
## [2.3.0](https://github.com/glauth/glauth/compare/v2.2.0...v2.3.0) (2023-10-02)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+211
-47
@@ -1,6 +1,8 @@
|
|||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
VERSION=$(shell bin/linuxamd64/glauth --version)
|
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 )
|
GIT_COMMIT=$(shell git rev-list -1 HEAD )
|
||||||
BUILD_TIME=$(shell date -u +%Y%m%d_%H%M%SZ)
|
BUILD_TIME=$(shell date -u +%Y%m%d_%H%M%SZ)
|
||||||
@@ -19,10 +21,12 @@ GIT_BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
|
|||||||
BUILD_VARS?=-s -w
|
BUILD_VARS?=-s -w
|
||||||
BUILD_FILES=.
|
BUILD_FILES=.
|
||||||
TRIM_FLAGS>?=-trimpath
|
TRIM_FLAGS>?=-trimpath
|
||||||
|
BUILD_TAGS?=noembed
|
||||||
|
|
||||||
# Targets
|
# Targets
|
||||||
MAIN_TARGETS=linux/amd64,linux/386,linux/arm64,linux/arm-7,darwin/amd64,darwin/arm64,windows/amd64,windows/386
|
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,darwin/amd64,darwin/arm64
|
PLUGIN_TARGETS?=linux/amd64,linux/386,linux/arm64,linux/arm-7,linux/riscv64,darwin/amd64,darwin/arm64
|
||||||
|
EMBED_TARGETS?=windows/amd64
|
||||||
|
|
||||||
# For release process
|
# For release process
|
||||||
GO_RELEASE_V=$(shell go version | { read _ _ v _; echo $${v#go}; })
|
GO_RELEASE_V=$(shell go version | { read _ _ v _; echo $${v#go}; })
|
||||||
@@ -45,17 +49,43 @@ help:
|
|||||||
#run: @ build and run - used for development
|
#run: @ build and run - used for development
|
||||||
run: setup devrun
|
run: setup devrun
|
||||||
|
|
||||||
#test: @ runs the integration test on linuxamd64 (eventually allow the binary to be set)
|
mocks:
|
||||||
test: runtest
|
go install go.uber.org/mock/mockgen@v0.3.0
|
||||||
|
# generate gomocks
|
||||||
|
go generate ./...
|
||||||
|
.PHONY: mocks
|
||||||
|
|
||||||
|
vet:
|
||||||
|
-go vet ./...
|
||||||
|
.PHONY: vet
|
||||||
|
#seedplugindb: @ stage the sqlite plugin and seed its test database
|
||||||
|
seedplugindb:
|
||||||
|
@if [ -f bin/sqlite-linux-amd64.so ]; then \
|
||||||
|
echo "- staging sqlite plugin and seeding gl.db"; \
|
||||||
|
cp -f bin/sqlite-linux-amd64.so bin/sqlite.so; \
|
||||||
|
rm -f gl.db; \
|
||||||
|
glauth=$$(ls bin/$(GOOS)$(GOARCH)/glauth bin/glauth-$(GOOS)-$(GOARCH) 2>/dev/null | head -1); \
|
||||||
|
"$$glauth" -c pkg/plugins/glauth-sqlite/sample-database.cfg >/dev/null 2>&1 & \
|
||||||
|
pid=$$!; sleep 2; kill -TERM $$pid 2>/dev/null; wait $$pid 2>/dev/null || true; \
|
||||||
|
sqlite3 gl.db < scripts/ci/seed-sqlite-plugin.sql; \
|
||||||
|
fi
|
||||||
|
.PHONY: seedplugindb
|
||||||
|
|
||||||
|
#test: @ runs package, root integration, and shell integration tests
|
||||||
|
test: mocks vet seedplugindb
|
||||||
|
go test -v -cover -coverprofile coverage.out ./pkg/... ./internal/... .
|
||||||
|
$(MAKE) runtest
|
||||||
#all: @ run build process for all binaries
|
#all: @ run build process for all binaries
|
||||||
all: setup binaries verify
|
all: setup binaries verify
|
||||||
|
|
||||||
#fast: @ run build process for only linuxamd64
|
#fast: @ run build process for only linuxamd64
|
||||||
fast: setup linuxamd64
|
fast: setup linuxamd64
|
||||||
|
|
||||||
|
#fastdebug: @ run build process for only linuxamd64 in debug mode
|
||||||
|
fastdebug: setup linuxamd64debug
|
||||||
|
|
||||||
#binaries: @ list of binary formats to build
|
#binaries: @ list of binary formats to build
|
||||||
binaries: linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64
|
binaries: linux386 linuxamd64 linuxarm linuxarm64 linuxriscv64 darwinamd64 darwinarm64 win386 winamd64
|
||||||
|
|
||||||
#setup: @ setup commands to always run
|
#setup: @ setup commands to always run
|
||||||
setup: getdeps format
|
setup: getdeps format
|
||||||
@@ -63,6 +93,27 @@ setup: getdeps format
|
|||||||
#####################
|
#####################
|
||||||
# Subcommands
|
# 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
|
# Run integration test
|
||||||
runtest:
|
runtest:
|
||||||
@@ -76,25 +127,66 @@ updatetest:
|
|||||||
./scripts/ci/integration-test.sh
|
./scripts/ci/integration-test.sh
|
||||||
|
|
||||||
format:
|
format:
|
||||||
go fmt
|
go fmt ./...
|
||||||
|
|
||||||
devrun:
|
devrun:
|
||||||
go run ${BUILD_FILES} -c sample-simple.cfg
|
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:
|
mkbindir:
|
||||||
@echo "create directory bin/$(GOOS)$(GOARCH)"
|
@echo "create directory bin/$(GOOS)$(GOARCH)"
|
||||||
@mkdir -p bin/$(GOOS)$(GOARCH)
|
@mkdir -p bin/$(GOOS)$(GOARCH)
|
||||||
.PHONY: mkbindir
|
.PHONY: mkbindir
|
||||||
|
|
||||||
build: mkbindir
|
build: mkbindir
|
||||||
@go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/$(GOOS)$(GOARCH)/glauth -buildvcs=false .
|
@go build ${TRIM_FLAGS} -tags ${BUILD_TAGS} -ldflags "${BUILD_VARS}" -o bin/$(GOOS)$(GOARCH)/glauth -buildvcs .
|
||||||
$(MAKE) sha256
|
$(MAKE) sha256
|
||||||
.PHONY: build
|
.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:
|
sha256:
|
||||||
@sha256sum bin/$(GOOS)$(GOARCH)/glauth > bin/$(GOOS)$(GOARCH)/glauth.sha256
|
@sha256sum bin/$(GOOS)$(GOARCH)/glauth > bin/$(GOOS)$(GOARCH)/glauth.sha256
|
||||||
.PHONY: sha256
|
.PHONY: sha256
|
||||||
|
|
||||||
|
linuxamd64debug:
|
||||||
|
GOOS=linux GOARCH=amd64 $(MAKE) builddebug
|
||||||
|
|
||||||
linux386:
|
linux386:
|
||||||
GOOS=linux GOARCH=386 $(MAKE) build
|
GOOS=linux GOARCH=386 $(MAKE) build
|
||||||
|
|
||||||
@@ -107,6 +199,16 @@ linuxarm:
|
|||||||
linuxarm64:
|
linuxarm64:
|
||||||
GOOS=linux GOARCH=arm64 $(MAKE) build
|
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:
|
darwinamd64:
|
||||||
GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 $(MAKE) build
|
GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 $(MAKE) build
|
||||||
|
|
||||||
@@ -120,70 +222,128 @@ winamd64:
|
|||||||
GOOS=windows GOARCH=amd64 $(MAKE) build
|
GOOS=windows GOARCH=amd64 $(MAKE) build
|
||||||
|
|
||||||
verify:
|
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:
|
pull-plugin-dependencies:
|
||||||
@git submodule add $U $M && \
|
(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)
|
||||||
(for pkg in $$(cat $$M/go.mod | awk '/^require [a-z]/{print $$2} /^\)/{rblock=0} rblock{if($$4!="indirect" && $$1!~"glauth"){print $$1}} /^require \(/{rblock=1}'); do if [ "$$(grep $$pkg go.mod &>/dev/null)" = "" ]; then go get $$pkg; fi; done)
|
|
||||||
|
|
||||||
#pull-base-plugins: @ pull plugins for local building
|
#prepare-plugins-buid: @ pull plugins dependencies for local building
|
||||||
pull-base-plugins:
|
prepare-plugins-build:
|
||||||
@U=https://github.com/glauth/glauth-sqlite M=pkg/plugins/glauth-sqlite make pull-plugin && \
|
@M=pkg/plugins/glauth-sqlite make pull-plugin-dependencies && \
|
||||||
U=https://github.com/glauth/glauth-mysql M=pkg/plugins/glauth-mysql make pull-plugin && \
|
M=pkg/plugins/glauth-mysql make pull-plugin-dependencies && \
|
||||||
U=https://github.com/glauth/glauth-postgres M=pkg/plugins/glauth-postgres make pull-plugin && \
|
M=pkg/plugins/glauth-postgres make pull-plugin-dependencies && \
|
||||||
U=https://github.com/glauth/glauth-pam M=pkg/plugins/glauth-pam make pull-plugin
|
M=pkg/plugins/glauth-pam make pull-plugin-dependencies
|
||||||
|
|
||||||
forget-plugin:
|
forget-plugin:
|
||||||
rm -rf $M ../.git/modules/v2/$M && \
|
rm -rf $M ../.git/modules/v2/$M; \
|
||||||
git config --remove-section submodule.v2/$M && \
|
git config --remove-section submodule.v2/$M; \
|
||||||
git rm --cache $M && \
|
git rm --cache $M; \
|
||||||
go mod tidy
|
go mod tidy; \
|
||||||
|
true
|
||||||
|
|
||||||
#forget-plugin: @ remove plugins, restoring original file structure
|
#forget-plugin: @ remove plugins, restoring original file structure
|
||||||
forget-plugins:
|
forget-plugins:
|
||||||
for pkg in pkg/plugins/*/go.mod; do\
|
for pkg in pkg/plugins/*; do \
|
||||||
P=$$(echo $$pkg | cut -d'/' -f 3) M=$$(dirname $$pkg) make forget-plugin;\
|
if [ -d "$$pkg" ]; then \
|
||||||
done
|
P=$$(echo $$pkg | cut -d'/' -f 3) M=$$pkg make forget-plugin >/dev/null 2>&1;\
|
||||||
|
fi; \
|
||||||
|
done; \
|
||||||
|
rm -rf buildlogs
|
||||||
|
|
||||||
#releasemain: @ build main binaries for distribution
|
remember-plugins:
|
||||||
releasemain:
|
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
|
||||||
|
|
||||||
|
_buildmain:
|
||||||
@xgo -v -ldflags="${BUILD_VARS}" -trimpath -go ${GO_RELEASE_V} -out glauth -dest bin -buildvcs=false --targets="${MAIN_TARGETS}" .
|
@xgo -v -ldflags="${BUILD_VARS}" -trimpath -go ${GO_RELEASE_V} -out glauth -dest bin -buildvcs=false --targets="${MAIN_TARGETS}" .
|
||||||
|
|
||||||
releaseplugin:
|
#buildmain: @ build main binaries for distribution
|
||||||
@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 . && \
|
buildmain: prepbuild _buildmain
|
||||||
(cd bin && for lib in $$(ls $$P-*); do sudo mv $$lib $$lib.so; done)
|
|
||||||
|
|
||||||
#releaseplugins: @ build base plugins for distribution
|
buildsingleembed:
|
||||||
releaseplugins:
|
git checkout -b build_embeds && \
|
||||||
@for pkg in pkg/plugins/*/go.mod; do\
|
mkdir -p pkg/embed && \
|
||||||
M=pkg/plugins/$$(echo $$pkg | cut -d'/' -f 3) && P=$$(echo $$M | grep -oP 'glauth-\K\w+') && M=$$M P=$$P make releaseplugin;\
|
cat pkg/plugins/$P/$M | sed -e 's/func main() {}//' -e 's/^package main/package embed/' > pkg/embed/$M && \
|
||||||
done
|
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
|
||||||
|
|
||||||
releasedockermain:
|
_buildmainembeds:
|
||||||
$(if $(TAG),,$(error Must set TAG))
|
P=glauth-sqlite M=sqlite.go T=embedsqlite make buildsingleembed
|
||||||
$(if $(REPO),,$(error Must set REPO - glauth or other))
|
|
||||||
@mkdir -p docker/assets/linux/amd64 docker/assets/linux/arm64 docker/assets/linux/arm/v7 && \
|
#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-amd64 docker/assets/linux/amd64/glauth && \
|
||||||
cp -f bin/glauth-linux-arm64 docker/assets/linux/arm64/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 && \
|
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:
|
builddockerplugins:
|
||||||
$(if $(TAG),,$(error Must set TAG))
|
$(if $(TAG),,$(error ❌ Must set TAG))
|
||||||
$(if $(REPO),,$(error Must set REPO - glauth or other))
|
$(if $(REPO),,$(error ❌ Must set REPO - glauth or other))
|
||||||
@mkdir -p docker/assets/linux/amd64 docker/assets/linux/arm64 docker/assets/linux/arm/v7 && \
|
@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-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-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-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-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-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-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-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-arm64.so docker/assets/linux/arm64/postgres.so && \
|
||||||
cp -f bin/postgres-linux-arm-7.so docker/assets/linux/arm/v7/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: @ run integration test using docker
|
||||||
testdocker:
|
testdocker:
|
||||||
@@ -225,4 +385,8 @@ testdocker:
|
|||||||
docker rm glauth-test && \
|
docker rm glauth-test && \
|
||||||
echo "==> Testing complete."
|
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
|
# Run Step
|
||||||
#################
|
#################
|
||||||
|
|
||||||
FROM gcr.io/distroless/base-debian11 as run
|
FROM gcr.io/distroless/base-debian13 as run
|
||||||
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
|
|
||||||
|
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
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
# Copy docker specific scripts from build container
|
# Copy docker specific scripts from build container
|
||||||
|
|||||||
@@ -10,8 +10,17 @@ RUN apk add --no-cache git make busybox-static dumb-init
|
|||||||
# Run Step
|
# Run Step
|
||||||
#################
|
#################
|
||||||
|
|
||||||
FROM gcr.io/distroless/base-debian11 as run
|
FROM gcr.io/distroless/base-debian13 as run
|
||||||
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
|
|
||||||
|
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
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
# Copy docker specific scripts from build container
|
# Copy docker specific scripts from build container
|
||||||
|
|||||||
+98
-550
@@ -1,29 +1,32 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/GeertJohan/yubigo"
|
"github.com/GeertJohan/yubigo"
|
||||||
"github.com/arl/statsviz"
|
"github.com/arl/statsviz"
|
||||||
docopt "github.com/docopt/docopt-go"
|
docopt "github.com/docopt/docopt-go"
|
||||||
"github.com/fsnotify/fsnotify"
|
"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/internal/version"
|
||||||
"github.com/glauth/glauth/v2/pkg/config"
|
"github.com/glauth/glauth/v2/pkg/config"
|
||||||
"github.com/glauth/glauth/v2/pkg/frontend"
|
"github.com/glauth/glauth/v2/pkg/frontend"
|
||||||
"github.com/glauth/glauth/v2/pkg/logging"
|
"github.com/glauth/glauth/v2/pkg/logging"
|
||||||
"github.com/glauth/glauth/v2/pkg/server"
|
"github.com/glauth/glauth/v2/pkg/server"
|
||||||
"github.com/glauth/glauth/v2/pkg/stats"
|
"github.com/glauth/glauth/v2/pkg/stats"
|
||||||
"github.com/hydronica/toml"
|
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"gopkg.in/amz.v3/aws"
|
|
||||||
"gopkg.in/amz.v3/s3"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const programName = "glauth"
|
const programName = "glauth"
|
||||||
@@ -59,6 +62,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
if err := parseArgs(); err != nil {
|
if err := parseArgs(); err != nil {
|
||||||
fmt.Println("Could not parse command-line arguments")
|
fmt.Println("Could not parse command-line arguments")
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@@ -70,15 +74,35 @@ func main() {
|
|||||||
checkConfig = true
|
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("Configuration file error")
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if checkConfig {
|
if checkConfig {
|
||||||
fmt.Println("Config file seems ok (but I am not checking much at this time)")
|
fmt.Println("Config file seems ok (but I am not checking much at this time)")
|
||||||
return
|
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")
|
log.Info().Msg("AP start")
|
||||||
|
|
||||||
startService()
|
startService()
|
||||||
@@ -106,27 +130,55 @@ func startService() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
monitor := monitoring.NewMonitor(&log)
|
||||||
|
tracer := tracing.NewTracer(
|
||||||
|
tracing.NewConfig(
|
||||||
|
activeConfig.Tracing.Enabled,
|
||||||
|
activeConfig.Tracing.GRPCEndpoint,
|
||||||
|
activeConfig.Tracing.HTTPEndpoint,
|
||||||
|
&log,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
startConfigWatcher()
|
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(
|
s, err := server.NewServer(
|
||||||
server.Logger(log),
|
server.Logger(log),
|
||||||
server.Config(activeConfig),
|
server.Config(activeConfig),
|
||||||
|
server.StartTLSConfig(starttlsConfig),
|
||||||
|
server.LDAPSTLSConfig(ldapstlsConfig),
|
||||||
|
server.Monitor(monitor),
|
||||||
|
server.Tracer(tracer),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("could not create server")
|
log.Error().Err(err).Msg("could not create server")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if activeConfig.LDAP.Enabled {
|
if activeConfig.LDAP.Enabled {
|
||||||
// Don't block if also starting a LDAPS server afterwards
|
|
||||||
shouldBlock := !activeConfig.LDAPS.Enabled
|
|
||||||
|
|
||||||
if shouldBlock {
|
|
||||||
if err := s.ListenAndServe(); err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not start LDAP server")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := s.ListenAndServe(); err != nil {
|
if err := s.ListenAndServe(); err != nil {
|
||||||
log.Error().Err(err).Msg("could not start LDAP server")
|
log.Error().Err(err).Msg("could not start LDAP server")
|
||||||
@@ -134,18 +186,31 @@ func startService() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if activeConfig.LDAPS.Enabled {
|
if activeConfig.LDAPS.Enabled {
|
||||||
// Always block here
|
go func() {
|
||||||
if err := s.ListenAndServeTLS(); err != nil {
|
if err := s.ListenAndServeTLS(); err != nil {
|
||||||
log.Error().Err(err).Msg("could not start LDAPS server")
|
log.Error().Err(err).Msg("could not start LDAPS server")
|
||||||
os.Exit(1)
|
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")
|
log.Info().Msg("AP exit")
|
||||||
os.Exit(1)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startConfigWatcher() {
|
func startConfigWatcher() {
|
||||||
@@ -171,6 +236,8 @@ func startConfigWatcher() {
|
|||||||
isChanged = true
|
isChanged = true
|
||||||
} else if event.Op&fsnotify.Remove == fsnotify.Remove { // vim edit file with rename/remove
|
} else if event.Op&fsnotify.Remove == fsnotify.Remove { // vim edit file with rename/remove
|
||||||
isChanged, isRemoved = true, true
|
isChanged, isRemoved = true, true
|
||||||
|
} else if event.Op&fsnotify.Create == fsnotify.Create { // only when watching a directory
|
||||||
|
isChanged = true
|
||||||
}
|
}
|
||||||
case err := <-watcher.Errors:
|
case err := <-watcher.Errors:
|
||||||
log.Error().Err(err).Msg("watcher error")
|
log.Error().Err(err).Msg("watcher error")
|
||||||
@@ -184,10 +251,17 @@ func startConfigWatcher() {
|
|||||||
isChanged, isRemoved = true, false
|
isChanged, isRemoved = true, false
|
||||||
}
|
}
|
||||||
if isChanged {
|
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")
|
log.Info().Err(err).Msg("Could not reload config. Holding on to old config")
|
||||||
} else {
|
} else {
|
||||||
log.Info().Msg("Config was reloaded")
|
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
|
isChanged = false
|
||||||
}
|
}
|
||||||
@@ -211,529 +285,3 @@ func parseArgs() error {
|
|||||||
func getConfigLocation() string {
|
func getConfigLocation() string {
|
||||||
return args["--config"].(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://") {
|
|
||||||
region, present := aws.Regions[args["-r"].(string)]
|
|
||||||
if present == false {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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, _ := 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 md, err = toml.Decode(destbuf.String(), &merged); err != nil {
|
|
||||||
return &cfg, err
|
|
||||||
}
|
|
||||||
cfg = merged
|
|
||||||
} else {
|
|
||||||
md, err = toml.DecodeFile(configFileLocation, &cfg)
|
|
||||||
if err != nil {
|
|
||||||
return &cfg, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch users := md.Mappings()["users"].(type) {
|
|
||||||
case []map[string]interface{}:
|
|
||||||
for _, mduser := range users {
|
|
||||||
if mduser["customattributes"] != nil {
|
|
||||||
for idx, cfguser := range cfg.Users {
|
|
||||||
if cfguser.Name == mduser["name"].(string) {
|
|
||||||
switch attributes := mduser["customattributes"].(type) {
|
|
||||||
case []map[string]interface{}:
|
|
||||||
cfg.Users[idx].CustomAttrs = attributes[0]
|
|
||||||
case map[string]interface{}:
|
|
||||||
cfg.Users[idx].CustomAttrs = attributes
|
|
||||||
default:
|
|
||||||
log.Info().Interface("attributes", attributes).Msg("Unknown attribute structure in config file")
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backward Compability
|
|
||||||
if cfg.Backend.Datastore != "" {
|
|
||||||
if cfg.Backends != nil {
|
|
||||||
return &cfg, fmt.Errorf("you cannot specify both [Backend] and [[Backends]] directives in the same configuration ")
|
|
||||||
} else {
|
|
||||||
cfg.Backends = append(cfg.Backends, cfg.Backend)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Patch with default values where not specified
|
|
||||||
for i := range cfg.Backends {
|
|
||||||
if cfg.Backends[i].NameFormat == "" {
|
|
||||||
cfg.Backends[i].NameFormat = "cn"
|
|
||||||
}
|
|
||||||
if cfg.Backends[i].GroupFormat == "" {
|
|
||||||
cfg.Backends[i].GroupFormat = "ou"
|
|
||||||
}
|
|
||||||
if cfg.Backends[i].SSHKeyAttr == "" {
|
|
||||||
cfg.Backends[i].SSHKeyAttr = "sshPublicKey"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//
|
|
||||||
|
|
||||||
return &cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeConfigs(config1 interface{}, config2 interface{}) error {
|
|
||||||
var merger func(int, string, interface{}, interface{}) error
|
|
||||||
merger = func(depth int, keyName string, cfg1 interface{}, cfg2 interface{}) error {
|
|
||||||
//fmt.Println(strings.Repeat(" ", depth), "Handling element: ", keyName, " for: ", cfg2)
|
|
||||||
switch element2 := cfg2.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
//fmt.Println(strings.Repeat(" ", depth), " - A map")
|
|
||||||
element2, ok := cfg2.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("config source: %s is not a map", keyName)
|
|
||||||
}
|
|
||||||
element1, ok := cfg1.(*map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("config dest: %s is not a map", keyName)
|
|
||||||
}
|
|
||||||
for k, _ := range element2 {
|
|
||||||
//fmt.Println(strings.Repeat(" ", depth), " - key: ", k)
|
|
||||||
_, ok := (*element1)[k]
|
|
||||||
if !ok {
|
|
||||||
(*element1)[k] = element2[k]
|
|
||||||
} else {
|
|
||||||
//fmt.Println(strings.Repeat(" ", depth), " - merging: ", element2[k])
|
|
||||||
asanarrayptr, ok := (*element1)[k].([]map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
if err := merger(depth+1, k, &asanarrayptr, element2[k]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
(*element1)[k] = asanarrayptr
|
|
||||||
} else {
|
|
||||||
asamapptr, ok := (*element1)[k].(map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
if err := merger(depth+1, k, &asamapptr, element2[k]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
(*element1)[k] = asamapptr
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("config dest: %s does not make a valid map/array ptr", keyName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []map[string]interface{}:
|
|
||||||
//fmt.Println(strings.Repeat(" ", depth), " - An array")
|
|
||||||
element2, ok := cfg2.([]map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("config source: %s is not a map array", keyName)
|
|
||||||
}
|
|
||||||
//fmt.Println(strings.Repeat(" ", depth), " - element2: ", element2)
|
|
||||||
element1, ok := cfg1.(*[]map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("config dest: %s is not a map array", keyName)
|
|
||||||
}
|
|
||||||
//fmt.Println(strings.Repeat(" ", depth), " - element1: ", element1)
|
|
||||||
for index, _ := range element2 {
|
|
||||||
*element1 = append(*element1, element2[index])
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
//fmt.Println(strings.Repeat(" ", depth), " - A string")
|
|
||||||
element2, ok := cfg2.(string)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("config: %s is not a string", keyName)
|
|
||||||
}
|
|
||||||
case bool:
|
|
||||||
//fmt.Println(strings.Repeat(" ", depth), " - A boolean")
|
|
||||||
element2, ok := cfg2.(bool)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("config: %s is not a boolean value", keyName)
|
|
||||||
}
|
|
||||||
case float64:
|
|
||||||
//fmt.Println(strings.Repeat(" ", depth), " - A float64")
|
|
||||||
element2, ok := cfg2.(float64)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("config: %s is not a float64 value", keyName)
|
|
||||||
}
|
|
||||||
case nil:
|
|
||||||
//fmt.Println(strings.Repeat(" ", depth), " - Nil")
|
|
||||||
default:
|
|
||||||
log.Info().Str("type", reflect.TypeOf(element2).String()).Msg("Unknown element type found in configuration file. Ignoring.")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := merger(0, "TOP", config1, config2)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeConfigsO(config1 interface{}, config2 interface{}) (interface{}, error) {
|
|
||||||
var merger func(int, string, interface{}, interface{}) (interface{}, error)
|
|
||||||
merger = func(depth int, keyName string, cfg1 interface{}, cfg2 interface{}) (interface{}, error) {
|
|
||||||
var returnElement interface{}
|
|
||||||
fmt.Println(strings.Repeat(" ", depth), "Handling element: ", keyName, " for: ", cfg1)
|
|
||||||
switch element1 := cfg1.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
element2, ok := cfg2.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("config: %s is not a map", keyName)
|
|
||||||
}
|
|
||||||
for k, value2 := range element2 {
|
|
||||||
value1, ok := element1[k]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("config: %s not found in at least one config file", k)
|
|
||||||
}
|
|
||||||
merged, err := merger(depth+1, k, value1, value2)
|
|
||||||
if err != nil {
|
|
||||||
return merged, err
|
|
||||||
}
|
|
||||||
element1[k] = merged
|
|
||||||
}
|
|
||||||
returnElement = element1
|
|
||||||
case string:
|
|
||||||
element2, ok := cfg2.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("config: %s is not a string", keyName)
|
|
||||||
}
|
|
||||||
if element2 != "" {
|
|
||||||
element1 = element2
|
|
||||||
}
|
|
||||||
returnElement = element1
|
|
||||||
case bool:
|
|
||||||
element2, ok := cfg2.(bool)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("config: %s is not a boolean value", keyName)
|
|
||||||
}
|
|
||||||
if element2 {
|
|
||||||
element1 = true
|
|
||||||
}
|
|
||||||
returnElement = element1
|
|
||||||
case float64:
|
|
||||||
element2, ok := cfg2.(float64)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("config: %s is not a float64 value", keyName)
|
|
||||||
}
|
|
||||||
if element2 != 0 {
|
|
||||||
element1 = element2
|
|
||||||
}
|
|
||||||
returnElement = element1
|
|
||||||
case nil:
|
|
||||||
if cfg2 == nil {
|
|
||||||
returnElement = nil
|
|
||||||
} else {
|
|
||||||
element2, ok := cfg2.(map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
returnElement = element2
|
|
||||||
} else {
|
|
||||||
element3, ok := cfg2.([]interface{})
|
|
||||||
if ok {
|
|
||||||
returnElement = element3
|
|
||||||
} else {
|
|
||||||
log.Info().Msg("Unexpected interface type for an assignment. Ignoring.")
|
|
||||||
returnElement = element1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.Info().Str("type", reflect.TypeOf(element1).String()).Msg("Unknown element type found in configuration file. Ignoring.")
|
|
||||||
returnElement = element1
|
|
||||||
}
|
|
||||||
fmt.Println(strings.Repeat(" ", depth), "Done with element: ", keyName, " for: ", returnElement)
|
|
||||||
return returnElement, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
flattenedConfig1, _ := json.Marshal(config1)
|
|
||||||
var jsonConfig1 interface{}
|
|
||||||
//json.Unmarshal(flattenedConfig1, &jsonConfig1)
|
|
||||||
d1 := json.NewDecoder(bytes.NewReader(flattenedConfig1))
|
|
||||||
d1.UseNumber()
|
|
||||||
if err := d1.Decode(&jsonConfig1); err != nil {
|
|
||||||
return config1, err
|
|
||||||
}
|
|
||||||
|
|
||||||
flattenedConfig2, _ := json.Marshal(config2)
|
|
||||||
var jsonConfig2 interface{}
|
|
||||||
//json.Unmarshal(flattenedConfig2, &jsonConfig2)
|
|
||||||
//spew.Dump(jsonConfig2)
|
|
||||||
d2 := json.NewDecoder(bytes.NewReader(flattenedConfig2))
|
|
||||||
d2.UseNumber()
|
|
||||||
if err := d2.Decode(&jsonConfig2); err != nil {
|
|
||||||
return config1, err
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
blocks, err := merger(0, "TOP", config1, config2)
|
|
||||||
if err != nil {
|
|
||||||
return config1, err
|
|
||||||
}
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// TODO: return blocks... we will convert back to toml when we are done merging!
|
|
||||||
|
|
||||||
//
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
err = toml.NewEncoder(buf).Encode(blocks)
|
|
||||||
if err != nil {
|
|
||||||
return config1, err
|
|
||||||
}
|
|
||||||
fmt.Println(buf.String())
|
|
||||||
merged := config.Config{}
|
|
||||||
if _, err := toml.Decode(buf.String(), &merged); err != nil {
|
|
||||||
return config1, err
|
|
||||||
}
|
|
||||||
return merged, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleArgs(cfg config.Config) (*config.Config, error) {
|
|
||||||
// LDAP flags
|
|
||||||
if ldap, ok := args["--ldap"].(string); ok && ldap != "" {
|
|
||||||
cfg.LDAP.Enabled = true
|
|
||||||
cfg.LDAP.Listen = ldap
|
|
||||||
}
|
|
||||||
|
|
||||||
// LDAPS flags
|
|
||||||
if ldaps, ok := args["--ldaps"].(string); ok && ldaps != "" {
|
|
||||||
cfg.LDAPS.Enabled = true
|
|
||||||
cfg.LDAPS.Listen = ldaps
|
|
||||||
}
|
|
||||||
if ldapsCert, ok := args["--ldaps-cert"].(string); ok && ldapsCert != "" {
|
|
||||||
cfg.LDAPS.Cert = ldapsCert
|
|
||||||
}
|
|
||||||
if ldapsKey, ok := args["--ldaps-key"].(string); ok && ldapsKey != "" {
|
|
||||||
cfg.LDAPS.Key = ldapsKey
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleLegacyConfig(cfg config.Config) (*config.Config, error) {
|
|
||||||
if len(cfg.Frontend.Listen) > 0 && (len(cfg.LDAP.Listen) > 0 || len(cfg.LDAPS.Listen) > 0) {
|
|
||||||
// Both old server-config and new - dont allow
|
|
||||||
return &cfg, fmt.Errorf("both old and new server-config in use - please remove old format ([frontend]) and migrate to new format ([ldap], [ldaps])")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Frontend.Listen) > 0 {
|
|
||||||
// We're going with old format - parse it into new
|
|
||||||
log.Info().Msg("Config [frontend] is deprecated - please move to [ldap] and [ldaps] as-per documentation")
|
|
||||||
|
|
||||||
cfg.LDAP.Enabled = !cfg.Frontend.TLS
|
|
||||||
cfg.LDAPS.Enabled = cfg.Frontend.TLS
|
|
||||||
|
|
||||||
if cfg.Frontend.TLS {
|
|
||||||
cfg.LDAPS.Listen = cfg.Frontend.Listen
|
|
||||||
} else {
|
|
||||||
cfg.LDAP.Listen = cfg.Frontend.Listen
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Frontend.Cert) > 0 {
|
|
||||||
cfg.LDAPS.Cert = cfg.Frontend.Cert
|
|
||||||
}
|
|
||||||
if len(cfg.Frontend.Key) > 0 {
|
|
||||||
cfg.LDAPS.Key = cfg.Frontend.Key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &cfg, nil
|
|
||||||
}
|
|
||||||
func validateConfig(cfg config.Config) (*config.Config, error) {
|
|
||||||
|
|
||||||
if !cfg.LDAP.Enabled && !cfg.LDAPS.Enabled {
|
|
||||||
return &cfg, fmt.Errorf("no server configuration found: please provide either LDAP or LDAPS configuration")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.LDAPS.Enabled {
|
|
||||||
// LDAPS enabled - verify requirements (cert, key, listen)
|
|
||||||
if len(cfg.LDAPS.Cert) == 0 || len(cfg.LDAPS.Key) == 0 {
|
|
||||||
return &cfg, fmt.Errorf("LDAPS was enabled but no certificate or key were specified: please disable LDAPS or use the 'cert' and 'key' options")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.LDAPS.Listen) == 0 {
|
|
||||||
return &cfg, fmt.Errorf("no LDAPS bind address was specified: please disable LDAPS or use the 'listen' option")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.LDAP.Enabled {
|
|
||||||
// LDAP enabled - verify listen
|
|
||||||
if len(cfg.LDAP.Listen) == 0 {
|
|
||||||
return &cfg, fmt.Errorf("no LDAP bind address was specified: please disable LDAP or use the 'listen' option")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//spew.Dump(cfg)
|
|
||||||
for i := range cfg.Backends {
|
|
||||||
switch cfg.Backends[i].Datastore {
|
|
||||||
case "":
|
|
||||||
cfg.Backends[i].Datastore = "config"
|
|
||||||
case "config":
|
|
||||||
case "ldap":
|
|
||||||
case "owncloud":
|
|
||||||
case "plugin":
|
|
||||||
default:
|
|
||||||
return &cfg, fmt.Errorf("invalid backend %s - must be 'config', 'ldap', 'owncloud' or 'plugin'", cfg.Backends[i].Datastore)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove after deprecating UnixID on User and Group
|
|
||||||
for _, user := range cfg.Users {
|
|
||||||
if user.UnixID != 0 {
|
|
||||||
user.UIDNumber = user.UnixID
|
|
||||||
log.Info().Msg(fmt.Sprintf("User '%s': 'unixid' is deprecated - please move to 'uidnumber' as per documentation", user.Name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, group := range cfg.Groups {
|
|
||||||
if group.UnixID != 0 {
|
|
||||||
group.GIDNumber = group.UnixID
|
|
||||||
log.Info().Msg(fmt.Sprintf("Group '%s': 'unixid' is deprecated - please move to 'gidnumber' as per documentation", group.Name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// doConfig reads the cli flags and config file
|
|
||||||
func doConfig(checkConfig bool) error {
|
|
||||||
// Parse config-file into config{} struct
|
|
||||||
cfg, err := parseConfigFile(getConfigLocation())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle parsed flags
|
|
||||||
cfg, err = handleArgs(*cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle parsing of legacy [frontend] section into [ldap] and/or [ldaps] sections
|
|
||||||
cfg, err = handleLegacyConfig(*cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err = validateConfig(*cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before greenlighting new config entirely, lets make sure the yubiauth works - in case they changed
|
|
||||||
if activeConfig.YubikeyClientID != cfg.YubikeyClientID || activeConfig.YubikeySecret != cfg.YubikeySecret {
|
|
||||||
if len(cfg.YubikeyClientID) > 0 && len(cfg.YubikeySecret) > 0 {
|
|
||||||
_yubiAuth, err := yubigo.NewYubiAuth(cfg.YubikeyClientID, cfg.YubikeySecret)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// No errors, override
|
|
||||||
yubiAuth = _yubiAuth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// All config is validated and alright, copy to activeConfig
|
|
||||||
if err := copier.Copy(activeConfig, cfg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle logging settings for new config
|
|
||||||
// - we do this last to make sure we only respect a fully validated config
|
|
||||||
log = logging.InitLogging(activeConfig.Debug, activeConfig.Syslog, activeConfig.StructuredLog)
|
|
||||||
|
|
||||||
if !checkConfig {
|
|
||||||
if activeConfig.Debug {
|
|
||||||
log.Info().Msg("Debugging enabled")
|
|
||||||
}
|
|
||||||
if activeConfig.Syslog {
|
|
||||||
log.Info().Msg("Syslog enabled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
+364
-150
@@ -1,12 +1,20 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
"github.com/glauth/ldap"
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testEnv struct {
|
type testEnv struct {
|
||||||
@@ -24,13 +32,267 @@ type testEnv struct {
|
|||||||
checkemployeetype string
|
checkemployeetype string
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegerStuff(t *testing.T) {
|
func glauthBinary() string {
|
||||||
|
if binary := os.Getenv("GLAUTH_TEST_BINARY"); binary != "" {
|
||||||
|
return binary
|
||||||
|
}
|
||||||
|
|
||||||
Convey("Testing sample-simple local file-based LDAP server", t, func() {
|
qualified := filepath.Join("bin", runtime.GOOS+runtime.GOARCH, "glauth")
|
||||||
svc := startSvc(SD, "bin/glauth", "-c", "sample-simple.cfg")
|
if _, err := os.Stat(qualified); err == nil {
|
||||||
batteryOfTests(
|
return qualified
|
||||||
t,
|
}
|
||||||
svc, testEnv{
|
|
||||||
|
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 ldapOnlyConfig(t *testing.T, source string) string {
|
||||||
|
t.Helper()
|
||||||
|
data, err := os.ReadFile(source)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contents := string(data)
|
||||||
|
sectionMarker := "\n[ldaps]\n"
|
||||||
|
markerStart := strings.Index(contents, sectionMarker)
|
||||||
|
if markerStart < 0 {
|
||||||
|
t.Fatalf("%s has no [ldaps] section", source)
|
||||||
|
}
|
||||||
|
start := markerStart + 1
|
||||||
|
relEnd := strings.Index(contents[start+len("[ldaps]"):], "\n[")
|
||||||
|
if relEnd < 0 {
|
||||||
|
t.Fatalf("%s has no section after [ldaps]", source)
|
||||||
|
}
|
||||||
|
end := start + len("[ldaps]") + relEnd
|
||||||
|
section := contents[start:end]
|
||||||
|
sectionWithoutLDAPS := strings.Replace(section, "enabled = true", "enabled = false", 1)
|
||||||
|
if section == sectionWithoutLDAPS {
|
||||||
|
t.Fatalf("%s [ldaps] section does not enable LDAPS", source)
|
||||||
|
}
|
||||||
|
|
||||||
|
contents = contents[:start] + sectionWithoutLDAPS + contents[end:]
|
||||||
|
path := filepath.Join(t.TempDir(), filepath.Base(source))
|
||||||
|
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
checkanonymousrootDSE: true,
|
||||||
checkTOTP: true,
|
checkTOTP: true,
|
||||||
checkbindUPN: true,
|
checkbindUPN: true,
|
||||||
@@ -43,17 +305,21 @@ func TestIntegerStuff(t *testing.T) {
|
|||||||
expectedfirstaccount: "dn: cn=hackers,ou=superheros,ou=users,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",
|
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
|
||||||
checkemployeetype: "cn=hackers",
|
checkemployeetype: "cn=hackers",
|
||||||
})
|
}
|
||||||
stopSvc(svc)
|
|
||||||
})
|
|
||||||
|
|
||||||
matchingLibrary := doRunGetFirst(RD, "ls", "bin/sqlite.so")
|
svc := startSvc(t, "127.0.0.1:3893", glauthBinary(), "-c", ldapOnlyConfig(t, "sample-simple.cfg"))
|
||||||
if matchingLibrary == "bin/sqlite.so" {
|
batteryOfTests(t, &env)
|
||||||
Convey("Testing sample-database LDAP server", t, func() {
|
stopSvc(svc)
|
||||||
svc := startSvc(SD, "bin/glauth", "-c", "pkg/plugins/sample-database.cfg")
|
}
|
||||||
batteryOfTests(
|
|
||||||
t,
|
func TestSQLitePlugin(t *testing.T) {
|
||||||
svc, testEnv{
|
_, err := os.Stat("bin/sqlite.so")
|
||||||
|
if err != nil {
|
||||||
|
t.Log("Unable to find sqlite plugin")
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
env := testEnv{
|
||||||
checkanonymousrootDSE: true,
|
checkanonymousrootDSE: true,
|
||||||
checkTOTP: false,
|
checkTOTP: false,
|
||||||
checkbindUPN: true,
|
checkbindUPN: true,
|
||||||
@@ -66,18 +332,26 @@ func TestIntegerStuff(t *testing.T) {
|
|||||||
expectedfirstaccount: "dn: cn=hackers,ou=superheros,ou=users,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",
|
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
|
||||||
checkemployeetype: "",
|
checkemployeetype: "",
|
||||||
})
|
|
||||||
stopSvc(svc)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
matchingContainers := doRunGetFirst(RD, "sh", "-c", "docker ps | grep openldap-service | wc -l")
|
svc := startSvc(t, "127.0.0.1:3893", glauthBinary(), "-c", "pkg/plugins/glauth-sqlite/sample-database.cfg")
|
||||||
if matchingContainers == "1" {
|
batteryOfTests(t, &env)
|
||||||
Convey("Testing sample-simple local LDAP server", t, func() {
|
stopSvc(svc)
|
||||||
svc := startSvc(SD, "bin/glauth", "-c", "sample-ldap-injection.cfg")
|
}
|
||||||
batteryOfTests(
|
|
||||||
t,
|
func TestLdapInjection(t *testing.T) {
|
||||||
svc, testEnv{
|
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,
|
checkanonymousrootDSE: false,
|
||||||
checkTOTP: true,
|
checkTOTP: true,
|
||||||
checkbindUPN: false,
|
checkbindUPN: false,
|
||||||
@@ -86,155 +360,95 @@ func TestIntegerStuff(t *testing.T) {
|
|||||||
svcdnnogroup: "", // ignore
|
svcdnnogroup: "", // ignore
|
||||||
otpdn: "cn=otpuser,cn=superheros,ou=users,dc=glauth,dc=com",
|
otpdn: "cn=otpuser,cn=superheros,ou=users,dc=glauth,dc=com",
|
||||||
expectedaccount: "dn: cn=hackers,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",
|
scopedaccount: "dn: cn=hackers,cn=superheros,ou=users,dc=glauth,dc=com",
|
||||||
expectedfirstaccount: "dn: cn=johndoe,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",
|
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
|
||||||
checkemployeetype: "",
|
checkemployeetype: "",
|
||||||
})
|
}
|
||||||
|
|
||||||
|
svc := startSvc(t, "127.0.0.1:3893", glauthBinary(), "-c", "sample-ldap-injection.cfg")
|
||||||
|
batteryOfTests(t, &env)
|
||||||
stopSvc(svc)
|
stopSvc(svc)
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func batteryOfTests(t *testing.T, svc *exec.Cmd, env testEnv) {
|
|
||||||
Convey("When searching for the 'hackers' user", func() {
|
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
|
||||||
Convey("We should find them in the 'superheros' group", func() {
|
|
||||||
So(out, ShouldEqual, env.expectedaccount)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if env.svcdnnogroup != "" {
|
|
||||||
Convey("When searching for the 'hackers' user without binding with a group", func() {
|
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdnnogroup, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
|
||||||
Convey("We should find them in the 'superheros' group", func() {
|
|
||||||
So(out, ShouldEqual, env.expectedaccount)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if env.checkbindUPN {
|
|
||||||
Convey("When searching for the 'hackers' user after binding using the account's UPN", func() {
|
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", "serviceuser@example.com", "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
|
||||||
Convey("We should find them in the 'superheros' group", func() {
|
|
||||||
So(out, ShouldEqual, env.expectedaccount)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Convey("When querying the root SDE", func() {
|
|
||||||
out := doRunGetSecond(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-s", "base", "(objectclass=*)")
|
|
||||||
Convey("We should get some meta information", func() {
|
|
||||||
So(out, ShouldEqual, env.expectedinfo)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if env.checkanonymousrootDSE {
|
|
||||||
Convey("When querying the root SDE anonymously without authorizing in config file", func() {
|
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-x", "-s", "base", "(objectclass=*)")
|
|
||||||
Convey("We should get error 50", func() {
|
|
||||||
So(out, ShouldEqual, "exit status 50")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Convey("When enumerating posix groups", func() {
|
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(objectclass=posixgroup)")
|
|
||||||
Convey("We should get a list starting with the 'superheros' group", func() {
|
|
||||||
So(out, ShouldEqual, env.expectedgroup)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("When searching for members of the 'superheros' group", func() {
|
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(memberOf=ou=superheros,ou=groups,dc=glauth,dc=com)")
|
|
||||||
Convey("We should get a list starting with the 'hackers' user", func() {
|
|
||||||
So(out, ShouldEqual, env.expectedfirstaccount)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("When performing a complex search for members of 'superheros' group", func() {
|
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(&(objectClass=*)(memberOf=ou=superheros,ou=groups,dc=glauth,dc=com))")
|
|
||||||
Convey("We should get a list starting with the 'hackers' user", func() {
|
|
||||||
So(out, ShouldEqual, env.expectedfirstaccount)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if env.checkTOTP {
|
|
||||||
Convey("When searching for the 'hacker' user using a TOTP-enabled account", func() {
|
|
||||||
otpvalue := doRunGetFirst(RD, "oathtool", "--totp", "-b", "-d", "6", "3hnvnk4ycv44glzigd6s25j4dougs3rk")
|
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret"+otpvalue, "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
|
|
||||||
Convey("We should find them in in the 'superheros' group", func() {
|
|
||||||
So(out, ShouldEqual, env.scopedaccount)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("When searching for the 'hacker' user using a TOTP-enabled account and no value", func() {
|
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret", "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
|
|
||||||
Convey("We should get 'Invalid credentials(49)'", func() {
|
|
||||||
So(out, ShouldEqual, "exit status 49")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("When searching for the 'hacker' user using a TOTP-enabled account and the wrong value", func() {
|
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret123456", "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
|
|
||||||
Convey("We should get 'Invalid credentials(49)'", func() {
|
|
||||||
So(out, ShouldEqual, "exit status 49")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if env.checkemployeetype != "" {
|
|
||||||
Convey("When searching for the 'hacker' user", func() {
|
|
||||||
out := doRunGetSecond(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", env.checkemployeetype, "employeetype")
|
|
||||||
Convey("Employee type should be 'Intern'", func() {
|
|
||||||
So(out, ShouldEqual, "employeetype: Intern")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----=============================================================================----
|
// -----=============================================================================----
|
||||||
|
func waitForPort(host string, timeout time.Duration) error {
|
||||||
const SD = 3 // Start Delay
|
start := time.Now()
|
||||||
const RD = 2 // Response Delay
|
for {
|
||||||
|
conn, err := ldap.Dial("tcp", host)
|
||||||
func startSvc(delay time.Duration, name string, arg ...string) *exec.Cmd {
|
if err == nil {
|
||||||
cmd := exec.Command(name, arg...)
|
err = conn.Bind("_", "_")
|
||||||
cmd.Start()
|
if err == nil {
|
||||||
if delay != 0 {
|
conn.Close()
|
||||||
time.Sleep(time.Second * delay)
|
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
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopSvc(svc *exec.Cmd) {
|
func stopSvc(svc *exec.Cmd) {
|
||||||
svc.Process.Kill()
|
svc.Process.Kill()
|
||||||
|
svc.Process.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func doRunGetFirst(delay time.Duration, name string, arg ...string) string {
|
func getFirst(output string) string {
|
||||||
out := strings.SplitN(doRun(delay, name, arg...), "\n", 2)
|
out := strings.SplitN(output, "\n", 2)
|
||||||
if out == nil || len(out) < 1 {
|
if len(out) < 1 {
|
||||||
return "*fail*"
|
return "*fail*"
|
||||||
}
|
}
|
||||||
return out[0]
|
return out[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func doRunGetSecond(delay time.Duration, name string, arg ...string) string {
|
func getSecond(output string) string {
|
||||||
out := strings.SplitN(doRun(delay, name, arg...), "\n", 3)
|
out := strings.SplitN(output, "\n", 3)
|
||||||
if out == nil || len(out) < 2 {
|
if len(out) < 2 {
|
||||||
return "*fail*"
|
return "*fail*"
|
||||||
}
|
}
|
||||||
return out[1]
|
return out[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func doRun(delay time.Duration, name string, arg ...string) string {
|
func doRun(name string, arg ...string) (string, error) {
|
||||||
out, err := exec.Command(name, arg...).Output()
|
out, err := exec.Command(name, arg...).CombinedOutput()
|
||||||
if err != nil {
|
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()
|
return err.Error()
|
||||||
}
|
}
|
||||||
if delay != 0 {
|
return pick(out)
|
||||||
time.Sleep(time.Second * delay)
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(string(out))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,62 @@
|
|||||||
module github.com/glauth/glauth/v2
|
module github.com/glauth/glauth/v2
|
||||||
|
|
||||||
go 1.19
|
go 1.25.0
|
||||||
|
|
||||||
// Do not mistake /vendored for /vendor!
|
|
||||||
replace github.com/hydronica/toml => ./vendored/toml
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/BurntSushi/toml v1.3.2
|
||||||
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e
|
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e
|
||||||
github.com/arl/statsviz v0.4.0
|
github.com/arl/statsviz v0.6.0
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/hydronica/toml v0.4.2
|
github.com/glauth/ldap v0.0.0-20240419171521-1f14f5c1b4ad
|
||||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
github.com/jinzhu/copier v0.4.0
|
||||||
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/pquerna/otp v1.3.0
|
github.com/prometheus/client_golang v1.18.0
|
||||||
github.com/prometheus/client_golang v1.13.0
|
github.com/prometheus/client_model v0.5.0
|
||||||
github.com/rs/zerolog v1.28.0
|
github.com/rs/zerolog v1.31.0
|
||||||
github.com/smartystreets/goconvey v1.6.4
|
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3
|
||||||
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc
|
github.com/yaegashi/msgraph.go v0.1.4
|
||||||
golang.org/x/crypto v0.1.0
|
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/amz.v3 v3.0.0-20201001071545-24fc1eceb27b
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/boombuler/barcode v1.0.1 // indirect
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||||
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
github.com/go-logr/logr v1.4.4 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||||
golang.org/x/sys v0.6.0 // indirect
|
github.com/prometheus/common v0.45.0 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // 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,534 +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.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
|
||||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
|
||||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
|
||||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
|
||||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
|
||||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
|
||||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
|
||||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
|
||||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
|
||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
|
||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
|
||||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
|
||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e h1:Bqtt5C+uVk+vH/t5dmB47uDCTwxw16EYHqvJnmY2aQc=
|
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e h1:Bqtt5C+uVk+vH/t5dmB47uDCTwxw16EYHqvJnmY2aQc=
|
||||||
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e/go.mod h1:njRCDrl+1RQ/A/+KVU8Ho2EWAxUSkohOWczdW3dzDG0=
|
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/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
|
||||||
github.com/arl/statsviz v0.4.0 h1:pv8DWfti0TC8M2Baold2xGnCnir9jhqv1BO6UNKQmqY=
|
|
||||||
github.com/arl/statsviz v0.4.0/go.mod h1:+5inUy/dxy11x/KSmicG3ZrEEy0Yr81AFm3dn4QC04M=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
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-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
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/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/glauth/ldap v0.0.0-20240419171521-1f14f5c1b4ad h1:2ERGXiofR9ISXg4evamCngs2EeuMhVJRuDwWGa6bS6s=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/glauth/ldap v0.0.0-20240419171521-1f14f5c1b4ad/go.mod h1:5ueZMujvJ5nuYPyBj6uQTPV4R1YshboRa/osaP4Emfs=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
|
||||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/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 v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
|
||||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 h1:D9EvfGQvlkKaDr2CRKN++7HbSXbefUNDrPq60T+g24s=
|
|
||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484/go.mod h1:O1EljZ+oHprtxDDPHiMWVo/5dBT6PlvWX5PSwj80aBA=
|
|
||||||
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba h1:DO8NFYdcRv1dnyAINJIBm6Bw2XibtLvQniNFGzf2W8E=
|
|
||||||
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba/go.mod h1:4S0XndRL8HNOaQBfdViJ2F/GPCgL524xlXRuXFH12/U=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||||
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
github.com/pquerna/otp v1.4.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.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
|
||||||
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
|
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
|
||||||
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/rickb777/date v1.12.4 h1:+6IzcCCS/1t17DrmnEvrznyq7nM8vPwir6/UhlyohKw=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/rickb777/date v1.12.4/go.mod h1:xP0eo/I5qmUt97yRGClHZfyLZ3ikMw6v6SU5MOGZTE0=
|
||||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
github.com/rickb777/plural v1.2.0 h1:5tvEc7UBCZ7l8h/2UeybSkt/uu1DQsZFOFdNevmUhlE=
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/rickb777/plural v1.2.0/go.mod h1:UdpyWFCGbo3mvK3f/PfZOAOrkjzJlYN/sD46XNWJ+Es=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
|
||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
|
||||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
|
||||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
|
||||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
|
||||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
|
||||||
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
|
|
||||||
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
|
||||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc h1:ejaC8rvIvCWmsaFrvmGOxhBuMxxhBB1xRshuM98XQ7M=
|
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3 h1:LNi0Qa7869/loPjz2kmMvp/jwZZnMZ9scMJKhDJ1DIo=
|
||||||
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc/go.mod h1:tso14hwzqX4VbnWTNsxiL0DvMb2OwbGISFA7jDibdWc=
|
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3/go.mod h1:jyigonKik3C5V895QNiAGpKYKEvFuqjw9qAEZks1mUg=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
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.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/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.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opentelemetry.io/contrib/propagators/jaeger v1.21.1 h1:f4beMGDKiVzg9IcX7/VuWVy+oGdjx3dNJ72YehmtY5k=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opentelemetry.io/contrib/propagators/jaeger v1.21.1/go.mod h1:U9jhkEl8d1LL+QXY7q3kneJWJugiN3kZJV2OWz3hkBY=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opentelemetry.io/contrib/propagators/jaeger v1.46.0 h1:uxl0SGcmuBkHj/Adl9oftEAyiawQBPL5RzMAmt/Yvq4=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
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-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-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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
|
||||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.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-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-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-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-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-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-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-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-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-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-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-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-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-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-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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
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.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.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.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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
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-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-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-20200529172331-a64b76657301/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
|
||||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-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-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-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.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/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
|
||||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI=
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI=
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
|
||||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4=
|
||||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
|
||||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y=
|
||||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
|
||||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
|
||||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
|
||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.31.0/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.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 h1:tdSyAzD5FHJmZAZ13d5WexAo7YJkiyGphhzFfInMnDw=
|
||||||
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b/go.mod h1:cE0dCGx2UfBTjLFlzEx4EXJUmoX6BXBoX9GjKOvqha4=
|
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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|
||||||
|
|||||||
@@ -0,0 +1,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
|
||||||
|
}
|
||||||
@@ -2,4 +2,4 @@ package version
|
|||||||
|
|
||||||
// Version is the tag of the latest release, it gets changed by the ci
|
// Version is the tag of the latest release, it gets changed by the ci
|
||||||
// process when a release is happening
|
// process when a release is happening
|
||||||
const Version = "v2.3.0" // x-release-please-version
|
const Version = "v2.5.4" // x-release-please-version
|
||||||
|
|||||||
@@ -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-----
|
||||||
@@ -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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"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
|
// 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.
|
// 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 {
|
tt := []struct {
|
||||||
Name string
|
Name string
|
||||||
Path string
|
Path string
|
||||||
@@ -26,7 +25,7 @@ func TestAPI(t *testing.T) {
|
|||||||
Path: "/",
|
Path: "/",
|
||||||
Check: func(t testing.TB, res *http.Response) {
|
Check: func(t testing.TB, res *http.Response) {
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
t.Errorf("unexpected response: %v", res.Status)
|
t.Fatalf("unexpected response: %v", res.Status)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
@@ -34,7 +33,7 @@ func TestAPI(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if got, want := h.Sum(nil), mkDigest(t, "index.html"); !bytes.Equal(got, want) {
|
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",
|
Name: "NotFound",
|
||||||
Path: "/glauth.js",
|
Path: "/glauth.js",
|
||||||
Check: func(t testing.TB, res *http.Response) {
|
Check: func(t testing.TB, res *http.Response) {
|
||||||
if res.StatusCode == http.StatusNotFound {
|
if res.StatusCode != http.StatusNotFound {
|
||||||
return
|
t.Fatalf("unexpected response: %v", res.Status)
|
||||||
}
|
}
|
||||||
t.Errorf("unexpected response: %v", res.Status)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Content-Type",
|
Name: "Content-Type",
|
||||||
Path: "/assets/glauth.js",
|
Path: "/assets/js/glauth.js",
|
||||||
Check: func(t testing.TB, res *http.Response) {
|
Check: func(t testing.TB, res *http.Response) {
|
||||||
if res.StatusCode != http.StatusOK {
|
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 {
|
if got, want := res.Header.Get("Content-Type"), `text/javascript; 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()
|
h := sha256.New()
|
||||||
if _, err := io.Copy(h, res.Body); err != nil {
|
if _, err := io.Copy(h, res.Body); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if got, want := h.Sum(nil), mkDigest(t, "glauth.js"); !bytes.Equal(got, want) {
|
if got, want := h.Sum(nil), mkDigest(t, "js/glauth.js"); !bytes.Equal(got, want) {
|
||||||
t.Errorf("bad digest: got: %x, want %x", got, want)
|
t.Fatalf("bad digest: got: %x, want %x", got, want)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Content-Type",
|
Name: "Content-Type",
|
||||||
Path: "/assets/glauth.css",
|
Path: "/assets/css/glauth.css",
|
||||||
Check: func(t testing.TB, res *http.Response) {
|
Check: func(t testing.TB, res *http.Response) {
|
||||||
if res.StatusCode != http.StatusOK {
|
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.
|
// 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 {
|
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()
|
h := sha256.New()
|
||||||
if _, err := io.Copy(h, res.Body); err != nil {
|
if _, err := io.Copy(h, res.Body); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if got, want := h.Sum(nil), mkDigest(t, "glauth.css"); !bytes.Equal(got, want) {
|
if got, want := h.Sum(nil), mkDigest(t, "css/glauth.css"); !bytes.Equal(got, want) {
|
||||||
t.Errorf("bad digest: got: %x, want %x", got, want)
|
t.Fatalf("bad digest: got: %x, want %x", got, want)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -91,7 +89,7 @@ func TestAPI(t *testing.T) {
|
|||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
log := zerolog.New(os.Stdout).Level(zerolog.InfoLevel)
|
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)
|
srv := httptest.NewServer(mux)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
@@ -112,7 +110,7 @@ func TestAPI(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mkDigest(t testing.TB, path string) []byte {
|
func mkDigest(t testing.TB, path string) []byte {
|
||||||
f, err := assets.Content.Open(path)
|
f, err := Content.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
+59
-27
@@ -3,13 +3,16 @@ package config
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// config file
|
// config file
|
||||||
type Backend struct {
|
type (
|
||||||
|
Backend struct {
|
||||||
BaseDN string
|
BaseDN string
|
||||||
Datastore string
|
Datastore string
|
||||||
Insecure bool // For LDAP and owncloud backend only
|
Insecure bool // For LDAP and owncloud backend only
|
||||||
Servers []string // For LDAP and owncloud backend only
|
Servers []string // For LDAP and owncloud backend only
|
||||||
NameFormat string
|
NameFormat string // e.g. cn, ou, uid, or a comma separated list of them
|
||||||
GroupFormat string
|
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
|
SSHKeyAttr string
|
||||||
UseGraphAPI bool // For ownCloud backend only
|
UseGraphAPI bool // For ownCloud backend only
|
||||||
Plugin string // Path to plugin library, for plugin backend only
|
Plugin string // Path to plugin library, for plugin backend only
|
||||||
@@ -17,33 +20,49 @@ type Backend struct {
|
|||||||
Database string // For Database backends only
|
Database string // For Database backends only
|
||||||
GroupWithSearchCapability string // For PamLinux backend only
|
GroupWithSearchCapability string // For PamLinux backend only
|
||||||
AnonymousDSE bool // For Config and Database backends only
|
AnonymousDSE bool // For Config and Database backends only
|
||||||
}
|
}
|
||||||
type Helper struct {
|
|
||||||
|
Helper struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
BaseDN string
|
BaseDN string
|
||||||
Datastore string
|
Datastore string
|
||||||
Plugin string // Path to plugin library, for plugin backend only
|
Plugin string // Path to plugin library, for plugin backend only
|
||||||
PluginHandler string // Name of plugin's main handler function
|
PluginHandler string // Name of plugin's main handler function
|
||||||
Database string // For MySQL backend only TODO REname to match plugin
|
Database string // For MySQL backend only TODO REname to match plugin
|
||||||
}
|
}
|
||||||
type Frontend struct {
|
|
||||||
|
Frontend struct {
|
||||||
AllowedBaseDNs []string // For LDAP backend only
|
AllowedBaseDNs []string // For LDAP backend only
|
||||||
Listen string
|
Listen string
|
||||||
Cert string
|
Cert string
|
||||||
Key string
|
Key string
|
||||||
TLS bool
|
TLS bool
|
||||||
}
|
}
|
||||||
type LDAP struct {
|
|
||||||
|
LDAP struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Listen string
|
Listen string
|
||||||
}
|
// StartTLS parameters
|
||||||
type LDAPS struct {
|
TLS bool
|
||||||
|
TLSCert string
|
||||||
|
TLSKey string
|
||||||
|
TLSCertPath string
|
||||||
|
TLSKeyPath string
|
||||||
|
LegacyTLS bool
|
||||||
|
}
|
||||||
|
|
||||||
|
LDAPS struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Listen string
|
Listen string
|
||||||
|
// LDAPS TLS parameters
|
||||||
Cert string
|
Cert string
|
||||||
Key string
|
Key string
|
||||||
}
|
CertPath string
|
||||||
type API struct {
|
KeyPath string
|
||||||
|
LegacyTLS bool
|
||||||
|
}
|
||||||
|
|
||||||
|
API struct {
|
||||||
Cert string
|
Cert string
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Internals bool
|
Internals bool
|
||||||
@@ -51,8 +70,9 @@ type API struct {
|
|||||||
Listen string
|
Listen string
|
||||||
SecretToken string
|
SecretToken string
|
||||||
TLS bool
|
TLS bool
|
||||||
}
|
}
|
||||||
type Behaviors struct {
|
|
||||||
|
Behaviors struct {
|
||||||
IgnoreCapabilities bool
|
IgnoreCapabilities bool
|
||||||
LimitFailedBinds bool
|
LimitFailedBinds bool
|
||||||
NumberOfFailedBinds int
|
NumberOfFailedBinds int
|
||||||
@@ -61,22 +81,23 @@ type Behaviors struct {
|
|||||||
PruneSourceTableEvery time.Duration
|
PruneSourceTableEvery time.Duration
|
||||||
PruneSourcesOlderThan time.Duration
|
PruneSourcesOlderThan time.Duration
|
||||||
LegacyVersion int
|
LegacyVersion int
|
||||||
}
|
}
|
||||||
type Capability struct {
|
|
||||||
|
Capability struct {
|
||||||
Action string
|
Action string
|
||||||
Object string
|
Object string
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserAuthenticator authenticates a user via custom auth from a backend
|
// UserAuthenticator authenticates a user via custom auth from a backend
|
||||||
type UserAuthenticator func(user *User, pw string) error
|
UserAuthenticator func(user *User, pw string) error
|
||||||
type User struct {
|
User struct {
|
||||||
Name string
|
Name string
|
||||||
OtherGroups []int
|
OtherGroups []int
|
||||||
PassSHA256 string
|
PassSHA256 string
|
||||||
PassBcrypt string
|
PassBcrypt string
|
||||||
PassAppSHA256 []string
|
PassAppSHA256 []string
|
||||||
PassAppBcrypt []string
|
PassAppBcrypt []string
|
||||||
PassAppCustom UserAuthenticator
|
PassAppCustom UserAuthenticator `toml:"-"`
|
||||||
PrimaryGroup int
|
PrimaryGroup int
|
||||||
Capabilities []Capability
|
Capabilities []Capability
|
||||||
SSHKeys []string
|
SSHKeys []string
|
||||||
@@ -91,14 +112,23 @@ type User struct {
|
|||||||
SN string
|
SN string
|
||||||
Homedir string
|
Homedir string
|
||||||
CustomAttrs map[string]interface{}
|
CustomAttrs map[string]interface{}
|
||||||
}
|
}
|
||||||
type Group struct {
|
|
||||||
|
Group struct {
|
||||||
Name string
|
Name string
|
||||||
UnixID int // TODO: remove after deprecating UnixID on User and Group
|
UnixID int // TODO: remove after deprecating UnixID on User and Group
|
||||||
GIDNumber int
|
GIDNumber int
|
||||||
|
Capabilities []Capability
|
||||||
IncludeGroups []int
|
IncludeGroups []int
|
||||||
}
|
}
|
||||||
type Config struct {
|
|
||||||
|
Tracing struct {
|
||||||
|
Enabled bool
|
||||||
|
GRPCEndpoint string
|
||||||
|
HTTPEndpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
Config struct {
|
||||||
API API
|
API API
|
||||||
Backend Backend // Deprecated
|
Backend Backend // Deprecated
|
||||||
Backends []Backend
|
Backends []Backend
|
||||||
@@ -115,8 +145,10 @@ type Config struct {
|
|||||||
LDAPS LDAPS
|
LDAPS LDAPS
|
||||||
Groups []Group
|
Groups []Group
|
||||||
Users []User
|
Users []User
|
||||||
|
Tracing Tracing
|
||||||
ConfigFile string
|
ConfigFile string
|
||||||
AwsAccessKeyId string
|
AwsAccessKeyId string
|
||||||
AwsSecretAccessKey string
|
AwsSecretAccessKey string
|
||||||
AwsRegion string
|
AwsRegion string
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package frontend
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
"github.com/glauth/glauth/v2/pkg/config"
|
"github.com/glauth/glauth/v2/pkg/config"
|
||||||
|
|||||||
+16
-158
@@ -1,17 +1,10 @@
|
|||||||
package frontend
|
package frontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"github.com/glauth/glauth/v2/internal/monitoring"
|
||||||
"github.com/glauth/glauth/v2/pkg/assets"
|
"github.com/glauth/glauth/v2/pkg/assets"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,162 +14,27 @@ func RunAPI(opts ...Option) {
|
|||||||
log := options.Logger
|
log := options.Logger
|
||||||
cfg := options.Config
|
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 {
|
if cfg.TLS {
|
||||||
log.Info().Str("address", cfg.Listen).Msg("Starting HTTPS server")
|
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")
|
log.Error().Err(err).Msg("error starting HTTPS server")
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Info().Str("address", cfg.Listen).Msg("Starting HTTP server")
|
log.Info().Str("address", cfg.Listen).Msg("Starting HTTP server")
|
||||||
err := http.ListenAndServe(cfg.Listen, nil)
|
monitoring.NewCollector(fmt.Sprintf("http://%s/debug/vars", cfg.Listen))
|
||||||
if err != nil {
|
|
||||||
|
if err := http.ListenAndServe(cfg.Listen, nil); err != nil {
|
||||||
log.Error().Err(err).Msg("error starting HTTP server")
|
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 := 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 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
+118
-30
@@ -1,17 +1,22 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/GeertJohan/yubigo"
|
"github.com/GeertJohan/yubigo"
|
||||||
|
"github.com/glauth/glauth/v2/internal/monitoring"
|
||||||
"github.com/glauth/glauth/v2/pkg/config"
|
"github.com/glauth/glauth/v2/pkg/config"
|
||||||
"github.com/glauth/glauth/v2/pkg/stats"
|
"github.com/glauth/glauth/v2/pkg/stats"
|
||||||
"github.com/nmcclain/ldap"
|
"github.com/glauth/ldap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type configHandler struct {
|
type configHandler struct {
|
||||||
@@ -21,6 +26,9 @@ type configHandler struct {
|
|||||||
yubikeyAuth *yubigo.YubiAuth
|
yubikeyAuth *yubigo.YubiAuth
|
||||||
ldohelper LDAPOpsHelper
|
ldohelper LDAPOpsHelper
|
||||||
attmatcher *regexp.Regexp
|
attmatcher *regexp.Regexp
|
||||||
|
|
||||||
|
monitor monitoring.MonitorInterface
|
||||||
|
tracer trace.Tracer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfigHandler creates a new config backed handler
|
// NewConfigHandler creates a new config backed handler
|
||||||
@@ -34,6 +42,8 @@ func NewConfigHandler(opts ...Option) Handler {
|
|||||||
yubikeyAuth: options.YubiAuth,
|
yubikeyAuth: options.YubiAuth,
|
||||||
ldohelper: options.LDAPHelper,
|
ldohelper: options.LDAPHelper,
|
||||||
attmatcher: configattributematcher,
|
attmatcher: configattributematcher,
|
||||||
|
monitor: options.Monitor,
|
||||||
|
tracer: options.Tracer,
|
||||||
}
|
}
|
||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
@@ -52,31 +62,83 @@ func (h configHandler) GetYubikeyAuth() *yubigo.YubiAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bind implements a bind request against the config file
|
// Bind implements a bind request against the config file
|
||||||
func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
|
||||||
return h.ldohelper.Bind(h, bindDN, bindSimplePw, conn)
|
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
|
// 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) {
|
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
|
// 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) {
|
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
|
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modify is not supported for a static config file
|
// 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) {
|
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
|
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete is not supported for a static config file
|
// 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) {
|
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
|
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{}
|
user := config.User{}
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
@@ -97,7 +159,9 @@ func (h configHandler) FindUser(userName string, searchByUPN bool) (f bool, u co
|
|||||||
return found, user, nil
|
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 Does g get erased, and above does u get erased?
|
||||||
// TODO and what about f?
|
// TODO and what about f?
|
||||||
group := config.Group{}
|
group := config.Group{}
|
||||||
@@ -111,13 +175,17 @@ func (h configHandler) FindGroup(groupName string) (f bool, g config.Group, err
|
|||||||
return found, group, nil
|
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{}
|
entries := []*ldap.Entry{}
|
||||||
|
|
||||||
for _, u := range h.cfg.Users {
|
for _, u := range h.cfg.Users {
|
||||||
attrs := []*ldap.EntryAttribute{}
|
attrs := []*ldap.EntryAttribute{}
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: h.backend.NameFormat, Values: []string{u.Name}})
|
for _, nameAttr := range h.backend.NameFormatAsArray {
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{u.Name}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: nameAttr, Values: []string{u.Name}})
|
||||||
|
}
|
||||||
|
|
||||||
if len(u.GivenName) > 0 {
|
if len(u.GivenName) > 0 {
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "givenName", Values: []string{u.GivenName}})
|
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: "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)}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "uidNumber", Values: []string{fmt.Sprintf("%d", u.UIDNumber)}})
|
||||||
|
|
||||||
if u.Disabled {
|
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: "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: "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: "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: "shadowExpire", Values: []string{"-1"}})
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowFlag", Values: []string{"134538308"}})
|
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
|
var dn string
|
||||||
if hierarchy == "" {
|
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 {
|
} 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})
|
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
|
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"
|
asGroupOfUniqueNames := hierarchy == "ou=groups"
|
||||||
|
|
||||||
entries := []*ldap.Entry{}
|
entries := []*ldap.Entry{}
|
||||||
|
|
||||||
for _, g := range h.cfg.Groups {
|
for _, g := range h.cfg.Groups {
|
||||||
attrs := []*ldap.EntryAttribute{}
|
attrs := []*ldap.EntryAttribute{}
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: h.backend.GroupFormat, Values: []string{g.Name}})
|
for _, groupAttr := range h.backend.GroupFormatAsArray {
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{g.Name}})
|
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: "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: "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 {
|
if asGroupOfUniqueNames {
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"groupOfUniqueNames", "top"}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"groupOfUniqueNames", "top"}})
|
||||||
} else {
|
} 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"}})
|
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})
|
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,11 +303,17 @@ func (h configHandler) FindPosixGroups(hierarchy string) (entrylist []*ldap.Entr
|
|||||||
|
|
||||||
// Close does not actually close anything, because the config data is kept in memory
|
// Close does not actually close anything, because the config data is kept in memory
|
||||||
func (h configHandler) Close(boundDn string, conn net.Conn) error {
|
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)
|
stats.Frontend.Add("closes", 1)
|
||||||
return nil
|
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
|
var insertOuUsers string
|
||||||
if h.cfg.Behaviors.LegacyVersion > 0 && h.cfg.Behaviors.LegacyVersion <= 20100 {
|
if h.cfg.Behaviors.LegacyVersion > 0 && h.cfg.Behaviors.LegacyVersion <= 20100 {
|
||||||
insertOuUsers = ""
|
insertOuUsers = ""
|
||||||
@@ -244,12 +323,12 @@ func (h configHandler) getGroupMemberDNs(gid int) []string {
|
|||||||
members := make(map[string]bool)
|
members := make(map[string]bool)
|
||||||
for _, u := range h.cfg.Users {
|
for _, u := range h.cfg.Users {
|
||||||
if u.PrimaryGroup == gid {
|
if u.PrimaryGroup == gid {
|
||||||
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
|
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
|
members[dn] = true
|
||||||
} else {
|
} else {
|
||||||
for _, othergid := range u.OtherGroups {
|
for _, othergid := range u.OtherGroups {
|
||||||
if othergid == gid {
|
if othergid == gid {
|
||||||
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
|
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
|
members[dn] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +339,7 @@ func (h configHandler) getGroupMemberDNs(gid int) []string {
|
|||||||
if gid == g.GIDNumber {
|
if gid == g.GIDNumber {
|
||||||
for _, includegroupid := range g.IncludeGroups {
|
for _, includegroupid := range g.IncludeGroups {
|
||||||
if includegroupid != gid {
|
if includegroupid != gid {
|
||||||
includegroupmembers := h.getGroupMemberDNs(includegroupid)
|
includegroupmembers := h.getGroupMemberDNs(ctx, includegroupid)
|
||||||
|
|
||||||
for _, includegroupmember := range includegroupmembers {
|
for _, includegroupmember := range includegroupmembers {
|
||||||
members[includegroupmember] = true
|
members[includegroupmember] = true
|
||||||
@@ -280,7 +359,10 @@ func (h configHandler) getGroupMemberDNs(gid int) []string {
|
|||||||
return m
|
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)
|
members := make(map[string]bool)
|
||||||
for _, u := range h.cfg.Users {
|
for _, u := range h.cfg.Users {
|
||||||
if u.PrimaryGroup == gid {
|
if u.PrimaryGroup == gid {
|
||||||
@@ -300,7 +382,7 @@ func (h configHandler) getGroupMemberIDs(gid int) []string {
|
|||||||
if includegroupid == gid {
|
if includegroupid == gid {
|
||||||
h.log.Warn().Int("groupid", includegroupid).Msg("Ignoring myself as included group")
|
h.log.Warn().Int("groupid", includegroupid).Msg("Ignoring myself as included group")
|
||||||
} else {
|
} else {
|
||||||
includegroupmemberids := h.getGroupMemberIDs(includegroupid)
|
includegroupmemberids := h.getGroupMemberIDs(ctx, includegroupid)
|
||||||
|
|
||||||
for _, includegroupmemberid := range includegroupmemberids {
|
for _, includegroupmemberid := range includegroupmemberids {
|
||||||
members[includegroupmemberid] = true
|
members[includegroupmemberid] = true
|
||||||
@@ -321,18 +403,21 @@ func (h configHandler) getGroupMemberIDs(gid int) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Converts an array of GUIDs into an array of DNs
|
// 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)
|
groups := make(map[string]bool)
|
||||||
for _, gid := range gids {
|
for _, gid := range gids {
|
||||||
for _, g := range h.cfg.Groups {
|
for _, g := range h.cfg.Groups {
|
||||||
if g.GIDNumber == gid {
|
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
|
groups[dn] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, includegroupid := range g.IncludeGroups {
|
for _, includegroupid := range g.IncludeGroups {
|
||||||
if includegroupid == gid && g.GIDNumber != gid {
|
if includegroupid == gid && g.GIDNumber != gid {
|
||||||
includegroupdns := h.getGroupDNs([]int{g.GIDNumber})
|
includegroupdns := h.getGroupDNs(ctx, []int{g.GIDNumber})
|
||||||
|
|
||||||
for _, includegroupdn := range includegroupdns {
|
for _, includegroupdn := range includegroupdns {
|
||||||
groups[includegroupdn] = true
|
groups[includegroupdn] = true
|
||||||
@@ -352,7 +437,10 @@ func (h configHandler) getGroupDNs(gids []int) []string {
|
|||||||
return g
|
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 {
|
for _, g := range h.cfg.Groups {
|
||||||
if g.GIDNumber == gid {
|
if g.GIDNumber == gid {
|
||||||
return g.Name
|
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
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/glauth/glauth/v2/pkg/config"
|
"github.com/glauth/glauth/v2/pkg/config"
|
||||||
"github.com/nmcclain/ldap"
|
"github.com/glauth/ldap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HelperMaker interface {
|
type HelperMaker interface {
|
||||||
FindUser(userName string, searchByUPN bool) (bool, config.User, error)
|
FindUser(ctx context.Context, userName string, searchByUPN bool) (bool, config.User, error)
|
||||||
FindGroup(groupName string) (bool, config.Group, error)
|
FindGroup(ctx context.Context, groupName string) (bool, config.Group, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler is the common interface for all datastores
|
// Handler is the common interface for all datastores
|
||||||
|
|||||||
+105
-10
@@ -1,11 +1,11 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -15,9 +15,13 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"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/config"
|
||||||
"github.com/glauth/glauth/v2/pkg/stats"
|
"github.com/glauth/glauth/v2/pkg/stats"
|
||||||
"github.com/nmcclain/ldap"
|
"github.com/glauth/ldap"
|
||||||
"github.com/pquerna/otp/totp"
|
"github.com/pquerna/otp/totp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,6 +38,9 @@ type ldapHandler struct {
|
|||||||
servers []ldapBackend
|
servers []ldapBackend
|
||||||
helper Handler
|
helper Handler
|
||||||
attm *regexp.Regexp
|
attm *regexp.Regexp
|
||||||
|
|
||||||
|
monitor monitoring.MonitorInterface
|
||||||
|
tracer trace.Tracer
|
||||||
}
|
}
|
||||||
|
|
||||||
// global lock for ldapHandler sessions & servers manipulation
|
// global lock for ldapHandler sessions & servers manipulation
|
||||||
@@ -71,6 +78,8 @@ func NewLdapHandler(opts ...Option) Handler {
|
|||||||
helper: options.Helper,
|
helper: options.Helper,
|
||||||
lock: &ldaplock,
|
lock: &ldaplock,
|
||||||
attm: ldapattributematcher,
|
attm: ldapattributematcher,
|
||||||
|
monitor: options.Monitor,
|
||||||
|
tracer: options.Tracer,
|
||||||
}
|
}
|
||||||
// parse LDAP URLs
|
// parse LDAP URLs
|
||||||
for _, ldapurl := range handler.backend.Servers {
|
for _, ldapurl := range handler.backend.Servers {
|
||||||
@@ -88,7 +97,18 @@ func NewLdapHandler(opts ...Option) Handler {
|
|||||||
return 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")
|
h.log.Debug().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind request")
|
||||||
|
|
||||||
// if h.helper != nil {
|
// if h.helper != nil {
|
||||||
@@ -97,17 +117,19 @@ func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCod
|
|||||||
lowerBindDN := strings.ToLower(bindDN)
|
lowerBindDN := strings.ToLower(bindDN)
|
||||||
baseDN := strings.ToLower("," + h.backend.BaseDN)
|
baseDN := strings.ToLower("," + h.backend.BaseDN)
|
||||||
parts := strings.Split(strings.TrimSuffix(lowerBindDN, 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
|
validotp := false
|
||||||
|
|
||||||
|
// Local records augment proxy authentication with OTP/YubiKey checks;
|
||||||
|
// password authentication remains delegated to the upstream LDAP server.
|
||||||
// Find the user
|
// Find the user
|
||||||
// We are going to go through all backends and ask
|
// We are going to go through all backends and ask
|
||||||
// until we find our user or die of boredom.
|
// until we find our user or die of boredom.
|
||||||
user := config.User{}
|
user := config.User{}
|
||||||
found := false
|
found := false
|
||||||
for i, handler := range h.handlers.Handlers {
|
for i, handler := range h.handlers.Handlers {
|
||||||
found, user, _ = handler.FindUser(userName, false)
|
found, user, _ = handler.FindUser(ctx, userName, false)
|
||||||
if found {
|
if found {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -154,6 +176,17 @@ func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCod
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
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
|
wantAttributes := true
|
||||||
wantTypesOnly := false
|
wantTypesOnly := false
|
||||||
|
|
||||||
@@ -179,6 +212,20 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
|
|||||||
stats.Frontend.Add("search_ldapSession_errors", 1)
|
stats.Frontend.Add("search_ldapSession_errors", 1)
|
||||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, nil
|
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(
|
search := ldap.NewSearchRequest(
|
||||||
searchReq.BaseDN,
|
searchReq.BaseDN,
|
||||||
searchReq.Scope,
|
searchReq.Scope,
|
||||||
@@ -192,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")
|
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)
|
sr, err := s.ldap.Search(search)
|
||||||
h.log.Debug().Interface("result", sr).Msg("Backend Search result")
|
h.log.Debug().Interface("result", sr).Msg("Backend Search result")
|
||||||
|
|
||||||
@@ -233,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")
|
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 {
|
for _, filter := range filters {
|
||||||
attbits := h.attm.FindStringSubmatch(filter)
|
attbits := h.attm.FindStringSubmatch(filter)
|
||||||
@@ -258,6 +314,7 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
|
|||||||
Entries: sr.Entries,
|
Entries: sr.Entries,
|
||||||
Referrals: sr.Referrals,
|
Referrals: sr.Referrals,
|
||||||
Controls: sr.Controls,
|
Controls: sr.Controls,
|
||||||
|
ResultCode: ldap.LDAPResultSuccess,
|
||||||
}
|
}
|
||||||
h.log.Debug().Interface("result", ssr).Msg("Frontend Search result")
|
h.log.Debug().Interface("result", ssr).Msg("Frontend Search result")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -272,7 +329,10 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
|
|||||||
return ssr, nil
|
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)
|
maxp := len(filter)
|
||||||
start := -1
|
start := -1
|
||||||
descended := false
|
descended := false
|
||||||
@@ -284,7 +344,7 @@ func (h ldapHandler) buildReqAttributesList(filter string, filters []string) []s
|
|||||||
} else if c == ')' {
|
} else if c == ')' {
|
||||||
if start > -1 {
|
if start > -1 {
|
||||||
descended = true
|
descended = true
|
||||||
filters = h.buildReqAttributesList(filter[start:p], filters)
|
filters = h.buildReqAttributesList(ctx, filter[start:p], filters)
|
||||||
}
|
}
|
||||||
start = -1
|
start = -1
|
||||||
}
|
}
|
||||||
@@ -297,24 +357,59 @@ func (h ldapHandler) buildReqAttributesList(filter string, filters []string) []s
|
|||||||
|
|
||||||
// Add is not yet supported for the ldap backend
|
// 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) {
|
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
|
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modify is not yet supported for the ldap backend
|
// 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) {
|
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
|
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete is not yet supported for the ldap backend
|
// 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) {
|
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
|
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
|
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
|
return false, config.Group{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+146
-68
@@ -2,10 +2,10 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -13,10 +13,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/GeertJohan/yubigo"
|
"github.com/GeertJohan/yubigo"
|
||||||
"github.com/glauth/glauth/v2/pkg/config"
|
"github.com/glauth/glauth/v2/pkg/config"
|
||||||
"github.com/glauth/glauth/v2/pkg/stats"
|
"github.com/glauth/glauth/v2/pkg/stats"
|
||||||
"github.com/nmcclain/ldap"
|
"github.com/glauth/ldap"
|
||||||
"github.com/pquerna/otp/totp"
|
"github.com/pquerna/otp/totp"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
@@ -30,10 +33,10 @@ type LDAPOpsHandler interface {
|
|||||||
GetCfg() *config.Config
|
GetCfg() *config.Config
|
||||||
GetYubikeyAuth() *yubigo.YubiAuth
|
GetYubikeyAuth() *yubigo.YubiAuth
|
||||||
|
|
||||||
FindUser(userName string, searchByUPN bool) (f bool, u config.User, err error)
|
FindUser(ctx context.Context, userName string, searchByUPN bool) (f bool, u config.User, err error)
|
||||||
FindGroup(groupName string) (f bool, g config.Group, err error)
|
FindGroup(ctx context.Context, groupName string) (f bool, g config.Group, err error)
|
||||||
FindPosixAccounts(hierarchy string) (entrylist []*ldap.Entry, err error)
|
FindPosixAccounts(ctx context.Context, hierarchy string) (entrylist []*ldap.Entry, err error)
|
||||||
FindPosixGroups(hierarchy string) (entrylist []*ldap.Entry, err error)
|
FindPosixGroups(ctx context.Context, hierarchy string) (entrylist []*ldap.Entry, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type failedBind struct {
|
type failedBind struct {
|
||||||
@@ -49,18 +52,28 @@ type sourceInfo struct {
|
|||||||
type LDAPOpsHelper struct {
|
type LDAPOpsHelper struct {
|
||||||
sources map[string]*sourceInfo
|
sources map[string]*sourceInfo
|
||||||
nextPruning time.Time
|
nextPruning time.Time
|
||||||
|
|
||||||
|
tracer trace.Tracer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLDAPOpsHelper() LDAPOpsHelper {
|
func NewLDAPOpsHelper(tracer trace.Tracer) LDAPOpsHelper {
|
||||||
helper := LDAPOpsHelper{
|
helper := LDAPOpsHelper{
|
||||||
sources: make(map[string]*sourceInfo),
|
sources: make(map[string]*sourceInfo),
|
||||||
nextPruning: time.Now(),
|
nextPruning: time.Now(),
|
||||||
|
tracer: tracer,
|
||||||
}
|
}
|
||||||
return helper
|
return helper
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
func isUnauthenticatedSimpleBind(bindDN, bindSimplePw string) bool {
|
||||||
if l.isInTimeout(h, conn) {
|
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
|
return ldap.LDAPResultUnwillingToPerform, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,11 +90,24 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
|
|||||||
return ldap.LDAPResultSuccess, nil
|
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 {
|
if ldapcode != ldap.LDAPResultSuccess {
|
||||||
return ldapcode, nil
|
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
|
validotp := false
|
||||||
|
|
||||||
if len(user.Yubikey) == 0 && len(user.OTPSecret) == 0 {
|
if len(user.Yubikey) == 0 && len(user.OTPSecret) == 0 {
|
||||||
@@ -166,6 +192,15 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
|
|||||||
return ldap.LDAPResultInvalidCredentials, nil
|
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
|
// Now, check the pasword hash
|
||||||
if user.PassBcrypt != "" {
|
if user.PassBcrypt != "" {
|
||||||
decoded, err := hex.DecodeString(user.PassBcrypt)
|
decoded, err := hex.DecodeString(user.PassBcrypt)
|
||||||
@@ -175,7 +210,7 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
|
|||||||
}
|
}
|
||||||
if bcrypt.CompareHashAndPassword(decoded, []byte(bindSimplePw)) != nil {
|
if bcrypt.CompareHashAndPassword(decoded, []byte(bindSimplePw)) != nil {
|
||||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("invalid credentials")
|
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
|
return ldap.LDAPResultInvalidCredentials, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,7 +219,7 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
|
|||||||
hash.Write([]byte(bindSimplePw))
|
hash.Write([]byte(bindSimplePw))
|
||||||
if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
|
if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
|
||||||
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("invalid credentials")
|
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
|
return ldap.LDAPResultInvalidCredentials, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,8 +244,11 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
|
|||||||
* TODO #5:
|
* TODO #5:
|
||||||
* Document roll out of schemas
|
* Document roll out of schemas
|
||||||
*/
|
*/
|
||||||
func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
func (l LDAPOpsHelper) Search(ctx context.Context, h LDAPOpsHandler, bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
||||||
if l.isInTimeout(h, conn) {
|
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")
|
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultUnwillingToPerform}, fmt.Errorf("Source is in a timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +262,7 @@ func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.Se
|
|||||||
var ldapcode ldap.LDAPResultCode
|
var ldapcode ldap.LDAPResultCode
|
||||||
|
|
||||||
if !anonymous {
|
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)
|
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Search Error: Potential bypass of BindDN %s", bindDN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,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")
|
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)
|
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:
|
case ldap.LDAPResultUnwillingToPerform:
|
||||||
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Search Error: No BaseDN provided")
|
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Search Error: No BaseDN provided")
|
||||||
case ldap.LDAPResultInsufficientAccessRights:
|
case ldap.LDAPResultInsufficientAccessRights:
|
||||||
@@ -246,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)
|
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:
|
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:
|
case ldap.LDAPResultSuccess:
|
||||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
||||||
}
|
}
|
||||||
@@ -263,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)
|
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
|
// 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)
|
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:
|
case ldap.LDAPResultSuccess:
|
||||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
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:
|
case ldap.LDAPResultSuccess:
|
||||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
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:
|
case ldap.LDAPResultSuccess:
|
||||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
||||||
}
|
}
|
||||||
@@ -287,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)
|
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:
|
case ldap.LDAPResultSuccess:
|
||||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
|
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:
|
case ldap.LDAPResultSuccess:
|
||||||
stats.Frontend.Add("search_successes", 1)
|
stats.Frontend.Add("search_successes", 1)
|
||||||
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Search OK")
|
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Search OK")
|
||||||
@@ -305,8 +343,11 @@ func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.Se
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns: LDAPResultSuccess or any ldap code returned by findUser
|
// 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) {
|
func (l LDAPOpsHelper) searchCheckBindDN(ctx context.Context, h LDAPOpsHandler, baseDN string, bindDN string, anonymous bool) (newBindDN string, boundUser *config.User, ldapresultcode ldap.LDAPResultCode) {
|
||||||
boundUser, ldapcode := l.findUser(h, bindDN, false /* checkGroup */)
|
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 {
|
if ldapcode != ldap.LDAPResultSuccess {
|
||||||
return "", nil, ldapcode
|
return "", nil, ldapcode
|
||||||
}
|
}
|
||||||
@@ -322,7 +363,10 @@ func (l LDAPOpsHelper) searchCheckBindDN(h LDAPOpsHandler, baseDN string, bindDN
|
|||||||
|
|
||||||
// Search RootDSE and return information on the server
|
// Search RootDSE and return information on the server
|
||||||
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultUnwillingToPerform, LDAPResultInsufficientAccessRights
|
// 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 != "" {
|
if searchBaseDN != "" {
|
||||||
return nil, ldap.LDAPResultOther // OK
|
return nil, ldap.LDAPResultOther // OK
|
||||||
}
|
}
|
||||||
@@ -341,6 +385,7 @@ func (l LDAPOpsHelper) searchMaybeRootDSEQuery(h LDAPOpsHandler, baseDN string,
|
|||||||
// unfortunately, objectClass is not to be included so we will respect that
|
// 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: "objectClass", Values: []string{"*"}})
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedSASLMechanisms", 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: "supportedLDAPVersion", Values: []string{"3"}})
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedControl", Values: []string{}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedControl", Values: []string{}})
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedCapabilities", Values: []string{}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedCapabilities", Values: []string{}})
|
||||||
@@ -348,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: "serverName", Values: []string{"unknown"}})
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "namingContexts", Values: []string{baseDN}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "namingContexts", Values: []string{baseDN}})
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "defaultNamingContext", 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})
|
entries = append(entries, &ldap.Entry{DN: searchBaseDN, Attributes: attrs})
|
||||||
stats.Frontend.Add("search_successes", 1)
|
stats.Frontend.Add("search_successes", 1)
|
||||||
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Root Search OK")
|
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Root Search OK")
|
||||||
@@ -357,7 +402,10 @@ func (l LDAPOpsHelper) searchMaybeRootDSEQuery(h LDAPOpsHandler, baseDN string,
|
|||||||
|
|
||||||
// Search and return the information, after indirection from the RootDSE
|
// Search and return the information, after indirection from the RootDSE
|
||||||
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultOperationsError
|
// 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" {
|
if searchBaseDN != "cn=schema" {
|
||||||
return nil, ldap.LDAPResultOther, nil // OK
|
return nil, ldap.LDAPResultOther, nil // OK
|
||||||
}
|
}
|
||||||
@@ -387,7 +435,7 @@ func (l LDAPOpsHelper) searchMaybeSchemaQuery(h LDAPOpsHandler, baseDN string, s
|
|||||||
}
|
}
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: filename.Name(), Values: values})
|
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})
|
entries = append(entries, &ldap.Entry{DN: searchBaseDN, Attributes: attrs})
|
||||||
stats.Frontend.Add("search_successes", 1)
|
stats.Frontend.Add("search_successes", 1)
|
||||||
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Schema Discovery OK")
|
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Schema Discovery OK")
|
||||||
@@ -396,25 +444,28 @@ func (l LDAPOpsHelper) searchMaybeSchemaQuery(h LDAPOpsHandler, baseDN string, s
|
|||||||
|
|
||||||
// Retrieve the top-levell nodes, i.e. the baseDN, groups, members...
|
// Retrieve the top-levell nodes, i.e. the baseDN, groups, members...
|
||||||
// Returns: LDAPResultSuccess, LDAPResultOther
|
// 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 {
|
if baseDN != searchBaseDN {
|
||||||
return nil, ldap.LDAPResultOther // OK
|
return nil, ldap.LDAPResultOther // OK
|
||||||
}
|
}
|
||||||
h.GetLog().Info().Str("special case", "top-level browse").Msg("Search request")
|
h.GetLog().Info().Str("special case", "top-level browse").Msg("Search request")
|
||||||
entries := []*ldap.Entry{}
|
entries := []*ldap.Entry{}
|
||||||
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
|
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.topLevelGroupsNode(ctx, searchBaseDN, "groups"))
|
||||||
entries = append(entries, l.topLevelUsersNode(searchBaseDN))
|
entries = append(entries, l.topLevelUsersNode(ctx, searchBaseDN))
|
||||||
if searchReq.Scope == ldap.ScopeWholeSubtree {
|
if searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||||
groupentries, err := h.FindPosixGroups("ou=users")
|
groupentries, err := h.FindPosixGroups(ctx, "ou=users")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ldap.LDAPResultOperationsError
|
return nil, ldap.LDAPResultOperationsError
|
||||||
}
|
}
|
||||||
entries = append(entries, groupentries...)
|
entries = append(entries, groupentries...)
|
||||||
|
|
||||||
userentries, err := h.FindPosixAccounts("ou=users")
|
userentries, err := h.FindPosixAccounts(ctx, "ou=users")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ldap.LDAPResultOperationsError
|
return nil, ldap.LDAPResultOperationsError
|
||||||
}
|
}
|
||||||
@@ -427,17 +478,20 @@ func (l LDAPOpsHelper) searchMaybeTopLevelNodes(h LDAPOpsHandler, baseDN string,
|
|||||||
|
|
||||||
// Search starting from and including the ou=groups node
|
// Search starting from and including the ou=groups node
|
||||||
// Returns: LDAPResultSuccess, LDAPResultOther
|
// 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) {
|
if searchBaseDN != fmt.Sprintf("ou=groups,%s", baseDN) {
|
||||||
return nil, ldap.LDAPResultOther // OK
|
return nil, ldap.LDAPResultOther // OK
|
||||||
}
|
}
|
||||||
h.GetLog().Info().Str("special case", "top-level groups node").Msg("Search request")
|
h.GetLog().Info().Str("special case", "top-level groups node").Msg("Search request")
|
||||||
entries := []*ldap.Entry{}
|
entries := []*ldap.Entry{}
|
||||||
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
|
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 {
|
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 {
|
if err != nil {
|
||||||
return nil, ldap.LDAPResultOperationsError
|
return nil, ldap.LDAPResultOperationsError
|
||||||
}
|
}
|
||||||
@@ -450,24 +504,27 @@ func (l LDAPOpsHelper) searchMaybeTopLevelGroupsNode(h LDAPOpsHandler, baseDN st
|
|||||||
|
|
||||||
// Search starting from and including the ou=users node
|
// Search starting from and including the ou=users node
|
||||||
// Returns: LDAPResultSuccess, LDAPResultOther
|
// 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) {
|
if searchBaseDN != fmt.Sprintf("ou=users,%s", baseDN) {
|
||||||
return nil, ldap.LDAPResultOther // OK
|
return nil, ldap.LDAPResultOther // OK
|
||||||
}
|
}
|
||||||
h.GetLog().Info().Str("special case", "top-level users node").Msg("Search request")
|
h.GetLog().Info().Str("special case", "top-level users node").Msg("Search request")
|
||||||
entries := []*ldap.Entry{}
|
entries := []*ldap.Entry{}
|
||||||
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
|
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 {
|
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 {
|
if err != nil {
|
||||||
return nil, ldap.LDAPResultOperationsError
|
return nil, ldap.LDAPResultOperationsError
|
||||||
}
|
}
|
||||||
entries = append(entries, groupentries...)
|
entries = append(entries, groupentries...)
|
||||||
}
|
}
|
||||||
if searchReq.Scope == ldap.ScopeWholeSubtree {
|
if searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||||
userentries, err := h.FindPosixAccounts("ou=users")
|
userentries, err := h.FindPosixAccounts(ctx, "ou=users")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ldap.LDAPResultOperationsError
|
return nil, ldap.LDAPResultOperationsError
|
||||||
}
|
}
|
||||||
@@ -480,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
|
// Look up posixgroup entries, either through objectlass or parent is ou=groups or ou=users
|
||||||
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultOperationsError
|
// 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"
|
hierarchy := "ou=groups"
|
||||||
if filterEntity != "posixgroup" {
|
if filterEntity != "posixgroup" {
|
||||||
bits := strings.Split(strings.Replace(searchBaseDN, baseDN, "", 1), ",")
|
bits := strings.Split(strings.Replace(searchBaseDN, baseDN, "", 1), ",")
|
||||||
@@ -492,19 +552,19 @@ func (l LDAPOpsHelper) searchMaybePosixGroups(h LDAPOpsHandler, baseDN string, s
|
|||||||
h.GetLog().Info().Str("special case", "posix groups").Msg("Search request")
|
h.GetLog().Info().Str("special case", "posix groups").Msg("Search request")
|
||||||
entries := []*ldap.Entry{}
|
entries := []*ldap.Entry{}
|
||||||
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
|
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||||
groupentries, err := h.FindPosixGroups(hierarchy)
|
groupentries, err := h.FindPosixGroups(ctx, hierarchy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ldap.LDAPResultOperationsError
|
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 searchReq.Scope == ldap.ScopeSingleLevel || searchReq.Scope == ldap.ScopeWholeSubtree {
|
||||||
if hierarchy == "ou=users" {
|
if hierarchy == "ou=users" {
|
||||||
userentries, err := h.FindPosixAccounts("ou=users")
|
userentries, err := h.FindPosixAccounts(ctx, "ou=users")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ldap.LDAPResultOperationsError
|
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)
|
stats.Frontend.Add("search_successes", 1)
|
||||||
@@ -515,7 +575,10 @@ func (l LDAPOpsHelper) searchMaybePosixGroups(h LDAPOpsHandler, baseDN string, s
|
|||||||
// Lookup posixaccount entries
|
// Lookup posixaccount entries
|
||||||
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultOperationsError
|
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultOperationsError
|
||||||
// This function ignores scopes... for now
|
// 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 {
|
switch filterEntity {
|
||||||
case "posixaccount", "shadowaccount", "":
|
case "posixaccount", "shadowaccount", "":
|
||||||
h.GetLog().Info().Str("default case", filterEntity).Msg("Search request")
|
h.GetLog().Info().Str("default case", filterEntity).Msg("Search request")
|
||||||
@@ -529,7 +592,7 @@ func (l LDAPOpsHelper) searchMaybePosixAccounts(h LDAPOpsHandler, baseDN string,
|
|||||||
hierarchyString = "ou=users"
|
hierarchyString = "ou=users"
|
||||||
}
|
}
|
||||||
|
|
||||||
unscopedEntries, err := h.FindPosixAccounts(hierarchyString)
|
unscopedEntries, err := h.FindPosixAccounts(ctx, hierarchyString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ldap.LDAPResultOperationsError
|
return nil, ldap.LDAPResultOperationsError
|
||||||
}
|
}
|
||||||
@@ -547,7 +610,7 @@ func (l LDAPOpsHelper) searchMaybePosixAccounts(h LDAPOpsHandler, baseDN string,
|
|||||||
return entries, ldap.LDAPResultSuccess
|
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{}
|
attrs := []*ldap.EntryAttribute{}
|
||||||
dnBits := strings.Split(searchBaseDN, ",")
|
dnBits := strings.Split(searchBaseDN, ",")
|
||||||
for _, dnBit := range dnBits {
|
for _, dnBit := range dnBits {
|
||||||
@@ -558,7 +621,7 @@ func (l LDAPOpsHelper) topLevelRootNode(searchBaseDN string) *ldap.Entry {
|
|||||||
return &ldap.Entry{DN: searchBaseDN, Attributes: attrs}
|
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 := []*ldap.EntryAttribute{}
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{"groups"}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{"groups"}})
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"organizationalUnit", "top"}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"organizationalUnit", "top"}})
|
||||||
@@ -570,7 +633,7 @@ func (l LDAPOpsHelper) topLevelGroupsNode(searchBaseDN string, hierarchy string)
|
|||||||
return &ldap.Entry{DN: dn, Attributes: attrs}
|
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 := []*ldap.EntryAttribute{}
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{"users"}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{"users"}})
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"organizationalUnit", "top"}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"organizationalUnit", "top"}})
|
||||||
@@ -585,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"
|
// 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.
|
// (and only in this scenario!) will defeat the LDAP library's filtering capabilities.
|
||||||
// Some day, hopefully, I'll fix this directly in the library.
|
// 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{}
|
filteredEntries := []*ldap.Entry{}
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if strings.HasSuffix(entry.DN, searchBaseDN) {
|
if strings.HasSuffix(entry.DN, searchBaseDN) {
|
||||||
@@ -595,7 +658,9 @@ func (l LDAPOpsHelper) preFilterEntries(searchBaseDN string, entries []*ldap.Ent
|
|||||||
return filteredEntries
|
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
|
var user config.User
|
||||||
|
|
||||||
@@ -605,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
|
// Not using mail.ParseAddress/1 because we would allow incorrectly formatted UPNs
|
||||||
if emailmatcher.MatchString(bindDN) {
|
if emailmatcher.MatchString(bindDN) {
|
||||||
var foundUser bool // = false
|
var foundUser bool // = false
|
||||||
foundUser, user, _ = h.FindUser(bindDN, true)
|
foundUser, user, _ = h.FindUser(ctx, bindDN, true)
|
||||||
if !foundUser {
|
if !foundUser {
|
||||||
h.GetLog().Info().Str("userprincipalname", bindDN).Msg("User not found")
|
h.GetLog().Info().Str("userprincipalname", bindDN).Msg("User not found")
|
||||||
return nil, ldap.LDAPResultInvalidCredentials
|
return nil, ldap.LDAPResultInvalidCredentials
|
||||||
@@ -621,10 +686,10 @@ func (l LDAPOpsHelper) findUser(h LDAPOpsHandler, bindDN string, checkGroup bool
|
|||||||
groupName := ""
|
groupName := ""
|
||||||
userName := ""
|
userName := ""
|
||||||
if len(parts) == 1 {
|
if len(parts) == 1 {
|
||||||
userName = strings.TrimPrefix(parts[0], h.GetBackend().NameFormat+"=")
|
userName = strings.TrimPrefix(parts[0], h.GetBackend().NameFormatAsArray[0]+"=")
|
||||||
} else if len(parts) == 2 || (len(parts) == 3 && parts[2] == "ou=users") {
|
} else if len(parts) == 2 || (len(parts) == 3 && parts[2] == "ou=users") {
|
||||||
userName = strings.TrimPrefix(parts[0], h.GetBackend().NameFormat+"=")
|
userName = strings.TrimPrefix(parts[0], h.GetBackend().NameFormatAsArray[0]+"=")
|
||||||
groupName = strings.TrimPrefix(parts[1], h.GetBackend().GroupFormat+"=")
|
groupName = strings.TrimPrefix(parts[1], h.GetBackend().GroupFormatAsArray[0]+"=")
|
||||||
} else {
|
} else {
|
||||||
h.GetLog().Info().Str("binddn", bindDN).Int("numparts", len(parts)).Msg("BindDN should have only one or two parts")
|
h.GetLog().Info().Str("binddn", bindDN).Int("numparts", len(parts)).Msg("BindDN should have only one or two parts")
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
@@ -635,7 +700,7 @@ func (l LDAPOpsHelper) findUser(h LDAPOpsHandler, bindDN string, checkGroup bool
|
|||||||
|
|
||||||
// find the user
|
// find the user
|
||||||
var foundUser bool // = false
|
var foundUser bool // = false
|
||||||
foundUser, user, _ = h.FindUser(userName, false)
|
foundUser, user, _ = h.FindUser(ctx, userName, false)
|
||||||
if !foundUser {
|
if !foundUser {
|
||||||
h.GetLog().Info().Str("username", userName).Msg("User not found")
|
h.GetLog().Info().Str("username", userName).Msg("User not found")
|
||||||
return nil, ldap.LDAPResultInvalidCredentials
|
return nil, ldap.LDAPResultInvalidCredentials
|
||||||
@@ -645,7 +710,7 @@ func (l LDAPOpsHelper) findUser(h LDAPOpsHandler, bindDN string, checkGroup bool
|
|||||||
var group config.Group // = nil
|
var group config.Group // = nil
|
||||||
var foundGroup bool // = false
|
var foundGroup bool // = false
|
||||||
if groupName != "" {
|
if groupName != "" {
|
||||||
foundGroup, group, _ = h.FindGroup(groupName)
|
foundGroup, group, _ = h.FindGroup(ctx, groupName)
|
||||||
if !foundGroup {
|
if !foundGroup {
|
||||||
h.GetLog().Info().Str("groupname", groupName).Msg("Group not found")
|
h.GetLog().Info().Str("groupname", groupName).Msg("Group not found")
|
||||||
return nil, ldap.LDAPResultInvalidCredentials
|
return nil, ldap.LDAPResultInvalidCredentials
|
||||||
@@ -663,7 +728,8 @@ func (l LDAPOpsHelper) findUser(h LDAPOpsHandler, bindDN string, checkGroup bool
|
|||||||
return &user, ldap.LDAPResultSuccess
|
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 {
|
for _, capability := range user.Capabilities {
|
||||||
if capability.Action == action {
|
if capability.Action == action {
|
||||||
for _, object := range objects {
|
for _, object := range objects {
|
||||||
@@ -679,7 +745,10 @@ func (l LDAPOpsHelper) checkCapability(user config.User, action string, objects
|
|||||||
// If your query is for, say 'objectClass', then our LDAP
|
// If your query is for, say 'objectClass', then our LDAP
|
||||||
// library will weed out this entry since it does *not* contain an objectclass attribute
|
// 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
|
// 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)
|
attbits := configattributematcher.FindStringSubmatch(searchReq.Filter)
|
||||||
if len(attbits) == 3 {
|
if len(attbits) == 3 {
|
||||||
foundattname := false
|
foundattname := false
|
||||||
@@ -698,13 +767,16 @@ func (l LDAPOpsHelper) collectRequestedAttributesBack(attrs []*ldap.EntryAttribu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return true if we should not process the current operation
|
// 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()
|
cfg := handler.GetCfg()
|
||||||
if !cfg.Behaviors.LimitFailedBinds {
|
if !cfg.Behaviors.LimitFailedBinds {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteAddr := l.getAddr(conn)
|
remoteAddr := l.getAddr(ctx, conn)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
info, ok := l.sources[remoteAddr]
|
info, ok := l.sources[remoteAddr]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -724,13 +796,16 @@ func (l LDAPOpsHelper) isInTimeout(handler LDAPOpsHandler, conn net.Conn) bool {
|
|||||||
return false
|
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()
|
cfg := handler.GetCfg()
|
||||||
if !cfg.Behaviors.LimitFailedBinds {
|
if !cfg.Behaviors.LimitFailedBinds {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteAddr := l.getAddr(conn)
|
remoteAddr := l.getAddr(ctx, conn)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
info, _ := l.sources[remoteAddr]
|
info, _ := l.sources[remoteAddr]
|
||||||
// if we have a failed bind...
|
// if we have a failed bind...
|
||||||
@@ -764,7 +839,10 @@ func (l LDAPOpsHelper) maybePutInTimeout(handler LDAPOpsHandler, conn net.Conn,
|
|||||||
return false
|
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()
|
fullAddr := conn.RemoteAddr().String()
|
||||||
sep := strings.LastIndex(fullAddr, ":")
|
sep := strings.LastIndex(fullAddr, ":")
|
||||||
if sep == -1 {
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/GeertJohan/yubigo"
|
"github.com/GeertJohan/yubigo"
|
||||||
|
"github.com/glauth/glauth/v2/internal/monitoring"
|
||||||
"github.com/glauth/glauth/v2/pkg/config"
|
"github.com/glauth/glauth/v2/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,6 +24,8 @@ type Options struct {
|
|||||||
YubiAuth *yubigo.YubiAuth
|
YubiAuth *yubigo.YubiAuth
|
||||||
Helper Handler
|
Helper Handler
|
||||||
LDAPHelper LDAPOpsHelper
|
LDAPHelper LDAPOpsHelper
|
||||||
|
Monitor monitoring.MonitorInterface
|
||||||
|
Tracer trace.Tracer
|
||||||
}
|
}
|
||||||
|
|
||||||
// newOptions initializes the available default options.
|
// newOptions initializes the available default options.
|
||||||
@@ -100,3 +105,17 @@ func LDAPHelper(val LDAPOpsHelper) Option {
|
|||||||
o.LDAPHelper = val
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+90
-32
@@ -6,16 +6,20 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"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/config"
|
||||||
"github.com/glauth/glauth/v2/pkg/stats"
|
"github.com/glauth/glauth/v2/pkg/stats"
|
||||||
"github.com/nmcclain/ldap"
|
"github.com/glauth/ldap"
|
||||||
msgraph "github.com/yaegashi/msgraph.go/v1.0"
|
msgraph "github.com/yaegashi/msgraph.go/v1.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,12 +37,46 @@ type ownCloudHandler struct {
|
|||||||
client *http.Client
|
client *http.Client
|
||||||
sessions map[string]ownCloudSession
|
sessions map[string]ownCloudSession
|
||||||
lock *sync.Mutex
|
lock *sync.Mutex
|
||||||
|
|
||||||
|
monitor monitoring.MonitorInterface
|
||||||
|
tracer trace.Tracer
|
||||||
}
|
}
|
||||||
|
|
||||||
// global lock for ownCloudHandler sessions & servers manipulation
|
// global lock for ownCloudHandler sessions & servers manipulation
|
||||||
var ownCloudLock sync.Mutex
|
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)
|
bindDN = strings.ToLower(bindDN)
|
||||||
baseDN := strings.ToLower("," + h.backend.BaseDN)
|
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
|
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)
|
bindDN = strings.ToLower(bindDN)
|
||||||
baseDN := strings.ToLower("," + h.backend.BaseDN)
|
baseDN := strings.ToLower("," + h.backend.BaseDN)
|
||||||
searchBaseDN := strings.ToLower(searchReq.BaseDN)
|
searchBaseDN := strings.ToLower(searchReq.BaseDN)
|
||||||
@@ -121,7 +167,9 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
|
|||||||
}
|
}
|
||||||
for _, g := range groups {
|
for _, g := range groups {
|
||||||
attrs := []*ldap.EntryAttribute{}
|
attrs := []*ldap.EntryAttribute{}
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: h.backend.GroupFormat, 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{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{"gidNumber", []string{fmt.Sprintf("%d", g.GIDNumber)}})
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixGroup"}})
|
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})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: members})
|
||||||
}
|
}
|
||||||
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormat, *g.ID, 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})
|
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
|
||||||
}
|
}
|
||||||
case "posixaccount", "":
|
case "posixaccount", "":
|
||||||
@@ -151,8 +199,9 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
|
|||||||
}
|
}
|
||||||
for _, u := range users {
|
for _, u := range users {
|
||||||
attrs := []*ldap.EntryAttribute{}
|
attrs := []*ldap.EntryAttribute{}
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: h.backend.NameFormat, Values: []string{*u.ID}})
|
for _, nameAttr := range h.backend.NameFormatAsArray {
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{*u.ID}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: nameAttr, Values: []string{*u.ID}})
|
||||||
|
}
|
||||||
if u.DisplayName != nil {
|
if u.DisplayName != nil {
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "givenName", Values: []string{*u.DisplayName}})
|
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: "objectClass", Values: []string{"posixAccount"}})
|
||||||
|
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s from ownCloud", *u.ID)}})
|
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})
|
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
|
// 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) {
|
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
|
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modify is not yet supported for the owncloud backend
|
// 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) {
|
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
|
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete is not yet supported for the owncloud backend
|
// 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) {
|
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
|
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindUser with the given username. Called by the ldap backend to authenticate the bind. Optional
|
// 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
|
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
|
return false, config.Group{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,24 +431,3 @@ func (s ownCloudSession) redirectPolicyFunc(req *http.Request, via []*http.Reque
|
|||||||
req.SetBasicAuth(s.user, s.password)
|
req.SetBasicAuth(s.user, s.password)
|
||||||
return nil
|
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package logging
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
@@ -13,6 +12,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// We will use this package to wrap log messages coming from libraries who have no interest
|
// We will use this package to wrap log messages coming from libraries who have no interest
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ package logging
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// We will use this package to wrap log messages coming from libraries who have no interest
|
// We will use this package to wrap log messages coming from libraries who have no interest
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package plugins
|
package plugins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -13,11 +13,17 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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/GeertJohan/yubigo"
|
||||||
"github.com/glauth/glauth/v2/pkg/config"
|
"github.com/glauth/glauth/v2/pkg/config"
|
||||||
"github.com/glauth/glauth/v2/pkg/handler"
|
"github.com/glauth/glauth/v2/pkg/handler"
|
||||||
"github.com/glauth/glauth/v2/pkg/stats"
|
"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>.*)\)`)
|
var configattributematcher = regexp.MustCompile(`(?i)\((?P<attribute>[a-zA-Z0-9]+)\s*=\s*(?P<value>.*)\)`)
|
||||||
@@ -48,13 +54,21 @@ type databaseHandler struct {
|
|||||||
MemGroups []config.Group
|
MemGroups []config.Group
|
||||||
ldohelper handler.LDAPOpsHelper
|
ldohelper handler.LDAPOpsHelper
|
||||||
attmatcher *regexp.Regexp
|
attmatcher *regexp.Regexp
|
||||||
|
|
||||||
|
tracer trace.Tracer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDatabaseHandler(sqlBackend SqlBackend, opts ...handler.Option) handler.Handler {
|
func NewDatabaseHandler(sqlBackend SqlBackend, opts ...handler.Option) handler.Handler {
|
||||||
options := handler.NewOptions(opts...)
|
options := handler.NewOptions(opts...)
|
||||||
|
|
||||||
// Note: we will never terminate this connection pool.
|
// 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 {
|
if err != nil {
|
||||||
options.Logger.Error().Err(err).Msg(fmt.Sprintf("unable to open SQL database named '%s'", options.Backend.Database))
|
options.Logger.Error().Err(err).Msg(fmt.Sprintf("unable to open SQL database named '%s'", options.Backend.Database))
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -78,23 +92,38 @@ func NewDatabaseHandler(sqlBackend SqlBackend, opts ...handler.Option) handler.H
|
|||||||
sqlBackend: sqlBackend,
|
sqlBackend: sqlBackend,
|
||||||
database: dbInfo,
|
database: dbInfo,
|
||||||
ldohelper: options.LDAPHelper,
|
ldohelper: options.LDAPHelper,
|
||||||
attmatcher: configattributematcher}
|
attmatcher: configattributematcher,
|
||||||
|
tracer: options.Tracer,
|
||||||
|
}
|
||||||
|
|
||||||
sqlBackend.CreateSchema(db)
|
sqlBackend.CreateSchema(db)
|
||||||
sqlBackend.MigrateSchema(db, ColumnExists)
|
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
|
return handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func ColumnExists(db *sql.DB, tableName string, columnName string) bool {
|
func ColumnExists(db *sql.DB, tableName string, columnName string) bool {
|
||||||
var found string
|
var found string
|
||||||
err := db.QueryRow(fmt.Sprintf(`SELECT COUNT(%s) FROM %s`, columnName, tableName)).Scan(
|
err := db.QueryRowContext(context.Background(), fmt.Sprintf(`SELECT COUNT(%s) FROM %s`, columnName, tableName)).Scan(
|
||||||
&found)
|
&found)
|
||||||
return err == nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (h databaseHandler) GetBackend() config.Backend {
|
func (h databaseHandler) GetBackend() config.Backend {
|
||||||
return h.backend
|
return h.backend
|
||||||
}
|
}
|
||||||
@@ -109,29 +138,47 @@ func (h databaseHandler) GetYubikeyAuth() *yubigo.YubiAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h databaseHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
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) {
|
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
|
// 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) {
|
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
|
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modify is not yet supported for the sql backend
|
// 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) {
|
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
|
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete is not yet supported for the sql backend
|
// 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) {
|
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
|
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
|
var criterion string
|
||||||
if searchByUPN {
|
if searchByUPN {
|
||||||
criterion = "lower(u.mail)"
|
criterion = "lower(u.mail)"
|
||||||
@@ -143,11 +190,14 @@ func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u
|
|||||||
found := false
|
found := false
|
||||||
|
|
||||||
var disabled int
|
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
|
SELECT u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.disabled
|
||||||
FROM users u WHERE %s=%s`,
|
FROM users u WHERE %s=%s`,
|
||||||
criterion,
|
criterion,
|
||||||
h.sqlBackend.GetPrepareSymbol()), userName).Scan(
|
h.sqlBackend.GetPrepareSymbol(),
|
||||||
|
), userName).Scan(
|
||||||
&user.UIDNumber, &user.PrimaryGroup, &user.PassBcrypt, &user.PassSHA256, &user.OTPSecret, &user.Yubikey, &disabled)
|
&user.UIDNumber, &user.PrimaryGroup, &user.PassBcrypt, &user.PassSHA256, &user.OTPSecret, &user.Yubikey, &disabled)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
user.Disabled = h.intToBool(disabled)
|
user.Disabled = h.intToBool(disabled)
|
||||||
@@ -156,7 +206,7 @@ func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u
|
|||||||
|
|
||||||
if !h.cfg.Behaviors.IgnoreCapabilities {
|
if !h.cfg.Behaviors.IgnoreCapabilities {
|
||||||
capability := config.Capability{}
|
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
|
SELECT c.action,c.object
|
||||||
FROM capabilities c WHERE userid=%s`,
|
FROM capabilities c WHERE userid=%s`,
|
||||||
h.sqlBackend.GetPrepareSymbol()), user.UIDNumber)
|
h.sqlBackend.GetPrepareSymbol()), user.UIDNumber)
|
||||||
@@ -176,11 +226,16 @@ func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u
|
|||||||
return found, user, err
|
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{}
|
group := config.Group{}
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
err = h.database.cnx.QueryRow(fmt.Sprintf(`
|
err = h.database.cnx.QueryRowContext(
|
||||||
|
ctx,
|
||||||
|
fmt.Sprintf(`
|
||||||
SELECT g.gidnumber FROM ldapgroups g WHERE lower(name)=%s`, h.sqlBackend.GetPrepareSymbol()), groupName).Scan(
|
SELECT g.gidnumber FROM ldapgroups g WHERE lower(name)=%s`, h.sqlBackend.GetPrepareSymbol()), groupName).Scan(
|
||||||
&group.GIDNumber)
|
&group.GIDNumber)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -190,15 +245,20 @@ func (h databaseHandler) FindGroup(groupName string) (f bool, g config.Group, er
|
|||||||
return found, group, err
|
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{}
|
entries := []*ldap.Entry{}
|
||||||
|
|
||||||
h.MemGroups, err = h.memoizeGroups()
|
h.MemGroups, err = h.memoizeGroups(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entries, err
|
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
|
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`)
|
FROM users u`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -216,11 +276,11 @@ func (h databaseHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return entries, err
|
return entries, err
|
||||||
}
|
}
|
||||||
u.OtherGroups = h.commaListToIntTable(otherGroups)
|
u.OtherGroups = h.commaListToIntTable(ctx, otherGroups)
|
||||||
u.Disabled = h.intToBool(disabled)
|
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 != "{}" {
|
if custattrstr != "{}" {
|
||||||
var r map[string]interface{}
|
var r map[string]interface{}
|
||||||
@@ -253,16 +313,19 @@ func (h databaseHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.
|
|||||||
return entries, nil
|
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{}
|
entries := []*ldap.Entry{}
|
||||||
|
|
||||||
h.MemGroups, err = h.memoizeGroups()
|
h.MemGroups, err = h.memoizeGroups(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entries, err
|
return entries, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, g := range h.MemGroups {
|
for _, g := range h.MemGroups {
|
||||||
info := h.getGroup(hierarchy, g)
|
info := h.getGroup(ctx, hierarchy, g)
|
||||||
if hierarchy != "groups" {
|
if hierarchy != "groups" {
|
||||||
info.DN = strings.Replace(info.DN, ",ou=groups,", fmt.Sprintf(",%s,", hierarchy), 1)
|
info.DN = strings.Replace(info.DN, ",ou=groups,", fmt.Sprintf(",%s,", hierarchy), 1)
|
||||||
}
|
}
|
||||||
@@ -273,6 +336,9 @@ func (h databaseHandler) FindPosixGroups(hierarchy string) (entrylist []*ldap.En
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h databaseHandler) Close(boundDn string, conn net.Conn) error {
|
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)
|
stats.Frontend.Add("closes", 1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -284,7 +350,10 @@ func (h databaseHandler) intToBool(value int) bool {
|
|||||||
return true
|
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 {
|
if len(commaList) == 0 {
|
||||||
return make([]int, 0)
|
return make([]int, 0)
|
||||||
}
|
}
|
||||||
@@ -300,16 +369,24 @@ func (h databaseHandler) commaListToIntTable(commaList string) []int {
|
|||||||
return rowsAsInts
|
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 {
|
if len(commaList) == 0 {
|
||||||
return make([]string, 0)
|
return make([]string, 0)
|
||||||
}
|
}
|
||||||
return strings.Split(commaList, ",")
|
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)
|
workMemGroups := make([]*config.Group, 0)
|
||||||
rows, err := h.database.cnx.Query(`
|
rows, err := h.database.cnx.QueryContext(
|
||||||
|
ctx,
|
||||||
|
`
|
||||||
SELECT g1.name,g1.gidnumber,ig.includegroupid
|
SELECT g1.name,g1.gidnumber,ig.includegroupid
|
||||||
FROM ldapgroups g1
|
FROM ldapgroups g1
|
||||||
LEFT JOIN includegroups ig ON g1.gidnumber=ig.parentgroupid
|
LEFT JOIN includegroups ig ON g1.gidnumber=ig.parentgroupid
|
||||||
@@ -346,7 +423,10 @@ func (h databaseHandler) memoizeGroups() ([]config.Group, error) {
|
|||||||
return memGroups, nil
|
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
|
var insertOuUsers string
|
||||||
if h.cfg.Behaviors.LegacyVersion > 0 && h.cfg.Behaviors.LegacyVersion <= 20100 {
|
if h.cfg.Behaviors.LegacyVersion > 0 && h.cfg.Behaviors.LegacyVersion <= 20100 {
|
||||||
insertOuUsers = ""
|
insertOuUsers = ""
|
||||||
@@ -355,7 +435,9 @@ func (h databaseHandler) getGroupMemberDNs(gid int) []string {
|
|||||||
}
|
}
|
||||||
members := make(map[string]bool)
|
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
|
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
|
||||||
FROM users u`)
|
FROM users u`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -372,13 +454,13 @@ func (h databaseHandler) getGroupMemberDNs(gid int) []string {
|
|||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
if u.PrimaryGroup == gid {
|
if u.PrimaryGroup == gid {
|
||||||
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
|
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
|
members[dn] = true
|
||||||
} else {
|
} else {
|
||||||
u.OtherGroups = h.commaListToIntTable(otherGroups)
|
u.OtherGroups = h.commaListToIntTable(ctx, otherGroups)
|
||||||
for _, othergid := range u.OtherGroups {
|
for _, othergid := range u.OtherGroups {
|
||||||
if othergid == gid {
|
if othergid == gid {
|
||||||
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
|
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
|
members[dn] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,7 +471,7 @@ func (h databaseHandler) getGroupMemberDNs(gid int) []string {
|
|||||||
if gid == g.GIDNumber {
|
if gid == g.GIDNumber {
|
||||||
for _, includegroupid := range g.IncludeGroups {
|
for _, includegroupid := range g.IncludeGroups {
|
||||||
if includegroupid != gid {
|
if includegroupid != gid {
|
||||||
includegroupmembers := h.getGroupMemberDNs(includegroupid)
|
includegroupmembers := h.getGroupMemberDNs(ctx, includegroupid)
|
||||||
|
|
||||||
for _, includegroupmember := range includegroupmembers {
|
for _, includegroupmember := range includegroupmembers {
|
||||||
members[includegroupmember] = true
|
members[includegroupmember] = true
|
||||||
@@ -410,9 +492,14 @@ func (h databaseHandler) getGroupMemberDNs(gid int) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Used exclusively when looking up Posix Groups
|
// 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)
|
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
|
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
|
||||||
FROM users u`)
|
FROM users u`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -431,7 +518,7 @@ func (h databaseHandler) getGroupMemberIDs(gid int) []string {
|
|||||||
if u.PrimaryGroup == gid {
|
if u.PrimaryGroup == gid {
|
||||||
members[u.Name] = true
|
members[u.Name] = true
|
||||||
} else {
|
} else {
|
||||||
u.OtherGroups = h.commaListToIntTable(otherGroups)
|
u.OtherGroups = h.commaListToIntTable(ctx, otherGroups)
|
||||||
for _, othergid := range u.OtherGroups {
|
for _, othergid := range u.OtherGroups {
|
||||||
if othergid == gid {
|
if othergid == gid {
|
||||||
members[u.Name] = true
|
members[u.Name] = true
|
||||||
@@ -446,7 +533,7 @@ func (h databaseHandler) getGroupMemberIDs(gid int) []string {
|
|||||||
if includegroupid == gid {
|
if includegroupid == gid {
|
||||||
h.log.Warn().Msg(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
|
h.log.Warn().Msg(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
|
||||||
} else {
|
} else {
|
||||||
includegroupmemberids := h.getGroupMemberIDs(includegroupid)
|
includegroupmemberids := h.getGroupMemberIDs(ctx, includegroupid)
|
||||||
|
|
||||||
for _, includegroupmemberid := range includegroupmemberids {
|
for _, includegroupmemberid := range includegroupmemberids {
|
||||||
members[includegroupmemberid] = true
|
members[includegroupmemberid] = true
|
||||||
@@ -467,18 +554,21 @@ func (h databaseHandler) getGroupMemberIDs(gid int) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Invoked for every user being returned from our database
|
// 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)
|
groups := make(map[string]bool)
|
||||||
for _, gid := range gids {
|
for _, gid := range gids {
|
||||||
for _, g := range h.MemGroups {
|
for _, g := range h.MemGroups {
|
||||||
if g.GIDNumber == gid {
|
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
|
groups[dn] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, includegroupid := range g.IncludeGroups {
|
for _, includegroupid := range g.IncludeGroups {
|
||||||
if includegroupid == gid && g.GIDNumber != gid {
|
if includegroupid == gid && g.GIDNumber != gid {
|
||||||
includegroupdns := h.getGroupDNs([]int{g.GIDNumber})
|
includegroupdns := h.getGroupDNs(ctx, []int{g.GIDNumber})
|
||||||
|
|
||||||
for _, includegroupdn := range includegroupdns {
|
for _, includegroupdn := range includegroupdns {
|
||||||
groups[includegroupdn] = true
|
groups[includegroupdn] = true
|
||||||
@@ -499,7 +589,10 @@ func (h databaseHandler) getGroupDNs(gids []int) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Invoked for every user being returned from our database
|
// 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 {
|
for _, g := range h.MemGroups {
|
||||||
if g.GIDNumber == gid {
|
if g.GIDNumber == gid {
|
||||||
return g.Name
|
return g.Name
|
||||||
@@ -509,77 +602,86 @@ func (h databaseHandler) getGroupName(gid int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Toolbox
|
// 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"
|
asGroupOfUniqueNames := hierarchy == "ou=groups"
|
||||||
|
|
||||||
attrs := []*ldap.EntryAttribute{}
|
attrs := []*ldap.EntryAttribute{}
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{h.backend.GroupFormat, []string{g.Name}})
|
for _, groupAttr := range h.backend.GroupFormatAsArray {
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{"description", []string{fmt.Sprintf("%s via LDAP", g.Name)}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: groupAttr, Values: []string{g.Name}})
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{fmt.Sprintf("%d", g.GIDNumber)}})
|
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{"uniqueMember", h.getGroupMemberDNs(g.GIDNumber)})
|
|
||||||
if asGroupOfUniqueNames {
|
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"groupOfUniqueNames", "top"}})
|
|
||||||
} else {
|
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{"memberUid", h.getGroupMemberIDs(g.GIDNumber)})
|
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixGroup", "top"}})
|
|
||||||
}
|
}
|
||||||
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormat, g.Name, h.backend.BaseDN)
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s via LDAP", g.Name)}})
|
||||||
return &ldap.Entry{dn, attrs}
|
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 := []*ldap.EntryAttribute{}
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{h.backend.NameFormat, []string{u.Name}})
|
for _, nameAttr := range h.backend.NameFormatAsArray {
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{"uid", []string{u.Name}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: nameAttr, Values: []string{u.Name}})
|
||||||
|
}
|
||||||
|
|
||||||
if len(u.GivenName) > 0 {
|
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 {
|
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{Name: "ou", Values: []string{h.getGroupName(ctx, u.PrimaryGroup)}})
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{"uidNumber", []string{fmt.Sprintf("%d", u.UIDNumber)}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "uidNumber", Values: []string{fmt.Sprintf("%d", u.UIDNumber)}})
|
||||||
|
|
||||||
if u.Disabled {
|
if u.Disabled {
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{"accountStatus", []string{"inactive"}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}})
|
||||||
} else {
|
} else {
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{"accountStatus", []string{"active"}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(u.Mail) > 0 {
|
if len(u.Mail) > 0 {
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{"mail", []string{u.Mail}})
|
attrs = append(attrs, &ldap.EntryAttribute{Name: "mail", Values: []string{u.Mail}})
|
||||||
attrs = append(attrs, &ldap.EntryAttribute{"userPrincipalName", []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 {
|
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 {
|
} 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 {
|
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 {
|
} 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{Name: "description", Values: []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{Name: "gecos", Values: []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{Name: "gidNumber", Values: []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: "memberOf", Values: h.getGroupDNs(ctx, append(u.OtherGroups, u.PrimaryGroup))})
|
||||||
if len(u.SSHKeys) > 0 {
|
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
|
var dn string
|
||||||
if hierarchy == "" {
|
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 {
|
} 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 (
|
import (
|
||||||
"context"
|
"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"
|
"github.com/glauth/glauth/v2/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,6 +18,10 @@ type Option func(o *Options)
|
|||||||
type Options struct {
|
type Options struct {
|
||||||
Logger zerolog.Logger
|
Logger zerolog.Logger
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
|
StartTLSConfig *tls.Config
|
||||||
|
LDAPSTLSConfig *tls.Config
|
||||||
|
Monitor monitoring.MonitorInterface
|
||||||
|
Tracer trace.Tracer
|
||||||
Context context.Context
|
Context context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,3 +56,31 @@ func Context(val context.Context) Option {
|
|||||||
o.Context = val
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+66
-10
@@ -1,22 +1,32 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"plugin"
|
"plugin"
|
||||||
|
|
||||||
|
_tls "github.com/glauth/glauth/v2/internal/tls"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/GeertJohan/yubigo"
|
"github.com/GeertJohan/yubigo"
|
||||||
|
"github.com/glauth/glauth/v2/internal/monitoring"
|
||||||
"github.com/glauth/glauth/v2/pkg/config"
|
"github.com/glauth/glauth/v2/pkg/config"
|
||||||
"github.com/glauth/glauth/v2/pkg/handler"
|
"github.com/glauth/glauth/v2/pkg/handler"
|
||||||
"github.com/nmcclain/ldap"
|
"github.com/glauth/ldap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LdapSvc struct {
|
type LdapSvc struct {
|
||||||
log zerolog.Logger
|
|
||||||
c *config.Config
|
c *config.Config
|
||||||
yubiAuth *yubigo.YubiAuth
|
yubiAuth *yubigo.YubiAuth
|
||||||
l *ldap.Server
|
l *ldap.Server
|
||||||
|
|
||||||
|
ldapstls *tls.Config
|
||||||
|
monitor monitoring.MonitorInterface
|
||||||
|
tracer trace.Tracer
|
||||||
|
log zerolog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(opts ...Option) (*LdapSvc, error) {
|
func NewServer(opts ...Option) (*LdapSvc, error) {
|
||||||
@@ -25,6 +35,8 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
|||||||
s := LdapSvc{
|
s := LdapSvc{
|
||||||
log: options.Logger,
|
log: options.Logger,
|
||||||
c: options.Config,
|
c: options.Config,
|
||||||
|
monitor: options.Monitor,
|
||||||
|
tracer: options.Tracer,
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -39,7 +51,7 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
|||||||
|
|
||||||
var helper handler.Handler
|
var helper handler.Handler
|
||||||
|
|
||||||
loh := handler.NewLDAPOpsHelper()
|
loh := handler.NewLDAPOpsHelper(s.tracer)
|
||||||
|
|
||||||
// instantiate the helper, if any
|
// instantiate the helper, if any
|
||||||
if s.c.Helper.Enabled {
|
if s.c.Helper.Enabled {
|
||||||
@@ -50,6 +62,7 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
|||||||
handler.Config(s.c),
|
handler.Config(s.c),
|
||||||
handler.YubiAuth(s.yubiAuth),
|
handler.YubiAuth(s.yubiAuth),
|
||||||
handler.LDAPHelper(loh),
|
handler.LDAPHelper(loh),
|
||||||
|
handler.Tracer(s.tracer),
|
||||||
)
|
)
|
||||||
case "plugin":
|
case "plugin":
|
||||||
plug, err := plugin.Open(s.c.Helper.Plugin)
|
plug, err := plugin.Open(s.c.Helper.Plugin)
|
||||||
@@ -72,6 +85,7 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
|||||||
handler.Config(s.c),
|
handler.Config(s.c),
|
||||||
handler.YubiAuth(s.yubiAuth),
|
handler.YubiAuth(s.yubiAuth),
|
||||||
handler.LDAPHelper(loh),
|
handler.LDAPHelper(loh),
|
||||||
|
handler.Tracer(s.tracer),
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported helper %s - must be one of 'config', 'plugin'", s.c.Helper.Datastore)
|
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
|
// configure the backends
|
||||||
s.l = ldap.NewServer()
|
s.l = ldap.NewServer()
|
||||||
s.l.EnforceLDAP = true
|
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 {
|
for i, backend := range s.c.Backends {
|
||||||
var h handler.Handler
|
var h handler.Handler
|
||||||
switch backend.Datastore {
|
switch backend.Datastore {
|
||||||
@@ -94,11 +127,15 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
|||||||
handler.Handlers(allHandlers),
|
handler.Handlers(allHandlers),
|
||||||
handler.Logger(&s.log),
|
handler.Logger(&s.log),
|
||||||
handler.Helper(helper),
|
handler.Helper(helper),
|
||||||
|
handler.Monitor(s.monitor),
|
||||||
|
handler.Tracer(s.tracer),
|
||||||
)
|
)
|
||||||
case "owncloud":
|
case "owncloud":
|
||||||
h = handler.NewOwnCloudHandler(
|
h = handler.NewOwnCloudHandler(
|
||||||
handler.Backend(backend),
|
handler.Backend(backend),
|
||||||
handler.Logger(&s.log),
|
handler.Logger(&s.log),
|
||||||
|
handler.Monitor(s.monitor),
|
||||||
|
handler.Tracer(s.tracer),
|
||||||
)
|
)
|
||||||
case "config":
|
case "config":
|
||||||
h = handler.NewConfigHandler(
|
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.Config(s.c), // TODO only used to access Users and Groups, move that to dedicated options
|
||||||
handler.YubiAuth(s.yubiAuth),
|
handler.YubiAuth(s.yubiAuth),
|
||||||
handler.LDAPHelper(loh),
|
handler.LDAPHelper(loh),
|
||||||
|
handler.Monitor(s.monitor),
|
||||||
|
handler.Tracer(s.tracer),
|
||||||
)
|
)
|
||||||
case "plugin":
|
case "plugin":
|
||||||
plug, err := plugin.Open(backend.Plugin)
|
plug, err := plugin.Open(backend.Plugin)
|
||||||
if err != nil {
|
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)
|
nph, err := plug.Lookup(backend.PluginHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -130,7 +169,22 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
|
|||||||
handler.Config(s.c),
|
handler.Config(s.c),
|
||||||
handler.YubiAuth(s.yubiAuth),
|
handler.YubiAuth(s.yubiAuth),
|
||||||
handler.LDAPHelper(loh),
|
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:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported backend %s - must be one of 'config', 'ldap','owncloud' or 'plugin'", backend.Datastore)
|
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++
|
backendCounter++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
monitoring.NewLDAPMonitorWatcher(s.l, s.monitor, &s.log)
|
||||||
|
|
||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,11 +216,11 @@ func (s *LdapSvc) ListenAndServe() error {
|
|||||||
// ListenAndServeTLS listens on the TCP network address s.c.LDAPS.Listen
|
// ListenAndServeTLS listens on the TCP network address s.c.LDAPS.Listen
|
||||||
func (s *LdapSvc) ListenAndServeTLS() error {
|
func (s *LdapSvc) ListenAndServeTLS() error {
|
||||||
s.log.Info().Str("address", s.c.LDAPS.Listen).Msg("LDAPS server listening")
|
s.log.Info().Str("address", s.c.LDAPS.Listen).Msg("LDAPS server listening")
|
||||||
return s.l.ListenAndServeTLS(
|
listener, err := tls.Listen("tcp", s.c.LDAPS.Listen, s.ldapstls)
|
||||||
s.c.LDAPS.Listen,
|
if err != nil {
|
||||||
s.c.LDAPS.Cert,
|
return err
|
||||||
s.c.LDAPS.Key,
|
}
|
||||||
)
|
return s.l.Serve(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown ends listeners by sending true to the ldap serves quit channel
|
// 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"
|
||||||
|
|
||||||
+30
-7
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#################
|
#################
|
||||||
# General configuration.
|
# General configuration.
|
||||||
debug = true
|
debug = false
|
||||||
# syslog = true
|
# syslog = true
|
||||||
# structuredlog = true
|
# structuredlog = true
|
||||||
#
|
#
|
||||||
@@ -21,26 +21,37 @@ debug = true
|
|||||||
enabled = true
|
enabled = true
|
||||||
# run on a non privileged port
|
# run on a non privileged port
|
||||||
listen = "0.0.0.0:3893"
|
listen = "0.0.0.0:3893"
|
||||||
|
tls = false # enable StartTLS support
|
||||||
|
tlsCertPath = "glauth.crt"
|
||||||
|
tlsKeyPath = "glauth.key"
|
||||||
|
|
||||||
[ldaps]
|
[ldaps]
|
||||||
# to enable ldaps generate 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`'
|
# 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"
|
listen = "0.0.0.0:3894"
|
||||||
cert = "glauth.crt"
|
cert = "glauth.crt"
|
||||||
key = "glauth.key"
|
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.
|
# The backend section controls the data store.
|
||||||
[backend]
|
[backend]
|
||||||
datastore = "config"
|
datastore = "config"
|
||||||
baseDN = "dc=glauth,dc=com"
|
baseDN = "dc=glauth,dc=com"
|
||||||
nameformat = "cn"
|
|
||||||
groupformat = "ou"
|
|
||||||
|
|
||||||
# If you are using a client that requires reading the root DSE first
|
# If you are using a client that requires reading the root DSE first
|
||||||
# such as SSSD
|
# such as SSSD
|
||||||
# anonymousdse = true
|
anonymousdse = false
|
||||||
|
|
||||||
## Configure dn format to use structures like
|
## Configure dn format to use structures like
|
||||||
## "uid=serviceuser,cn=svcaccts,$BASEDN" instead of "cn=serviceuser,ou=svcaccts,$BASEDN"
|
## "uid=serviceuser,cn=svcaccts,$BASEDN" instead of "cn=serviceuser,ou=svcaccts,$BASEDN"
|
||||||
@@ -134,6 +145,11 @@ debug = true
|
|||||||
# passappbcrypt = "243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
|
# passappbcrypt = "243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
|
||||||
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
|
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
|
||||||
|
|
||||||
|
[[users]]
|
||||||
|
name = "danger"
|
||||||
|
uidnumber = 5006
|
||||||
|
primarygroup = 5503
|
||||||
|
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# The groups section contains a hardcoded list of valid users.
|
# The groups section contains a hardcoded list of valid users.
|
||||||
@@ -146,9 +162,16 @@ debug = true
|
|||||||
gidnumber = 5502
|
gidnumber = 5502
|
||||||
|
|
||||||
[[groups]]
|
[[groups]]
|
||||||
name = "vpn"
|
name = "umbrella"
|
||||||
gidnumber = 5503
|
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.
|
# Enable and configure the optional REST API here.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
dn: ou=superheros,ou=users,dc=glauth,dc=com
|
dn: ou=superheros,ou=users,dc=glauth,dc=com
|
||||||
ou: superheros
|
ou: superheros
|
||||||
uid: superheros
|
cn: superheros
|
||||||
description: superheros
|
description: superheros
|
||||||
gidNumber: 5501
|
gidNumber: 5501
|
||||||
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||||
@@ -20,7 +20,7 @@ objectClass: top
|
|||||||
|
|
||||||
dn: ou=svcaccts,ou=users,dc=glauth,dc=com
|
dn: ou=svcaccts,ou=users,dc=glauth,dc=com
|
||||||
ou: svcaccts
|
ou: svcaccts
|
||||||
uid: svcaccts
|
cn: svcaccts
|
||||||
description: svcaccts
|
description: svcaccts
|
||||||
gidNumber: 5502
|
gidNumber: 5502
|
||||||
uniqueMember: cn=serviceuser,ou=svcaccts,ou=users,dc=glauth,dc=com
|
uniqueMember: cn=serviceuser,ou=svcaccts,ou=users,dc=glauth,dc=com
|
||||||
@@ -30,7 +30,7 @@ objectClass: top
|
|||||||
|
|
||||||
dn: ou=vpnaccess,ou=users,dc=glauth,dc=com
|
dn: ou=vpnaccess,ou=users,dc=glauth,dc=com
|
||||||
ou: vpnaccess
|
ou: vpnaccess
|
||||||
uid: vpnaccess
|
cn: vpnaccess
|
||||||
description: vpnaccess
|
description: vpnaccess
|
||||||
gidNumber: 5503
|
gidNumber: 5503
|
||||||
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||||
@@ -50,7 +50,7 @@ objectClass: top
|
|||||||
|
|
||||||
dn: ou=allaccs,ou=users,dc=glauth,dc=com
|
dn: ou=allaccs,ou=users,dc=glauth,dc=com
|
||||||
ou: allaccs
|
ou: allaccs
|
||||||
uid: allaccs
|
cn: allaccs
|
||||||
description: allaccs
|
description: allaccs
|
||||||
gidNumber: 5504
|
gidNumber: 5504
|
||||||
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||||
@@ -72,7 +72,7 @@ objectClass: top
|
|||||||
|
|
||||||
dn: ou=mailadmin,ou=users,dc=glauth,dc=com
|
dn: ou=mailadmin,ou=users,dc=glauth,dc=com
|
||||||
ou: mailadmin
|
ou: mailadmin
|
||||||
uid: mailadmin
|
cn: mailadmin
|
||||||
description: mailadmin
|
description: mailadmin
|
||||||
gidNumber: 5505
|
gidNumber: 5505
|
||||||
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||||
@@ -88,7 +88,7 @@ objectClass: top
|
|||||||
|
|
||||||
dn: ou=webmail,ou=users,dc=glauth,dc=com
|
dn: ou=webmail,ou=users,dc=glauth,dc=com
|
||||||
ou: webmail
|
ou: webmail
|
||||||
uid: webmail
|
cn: webmail
|
||||||
description: webmail
|
description: webmail
|
||||||
gidNumber: 5506
|
gidNumber: 5506
|
||||||
objectClass: posixGroup
|
objectClass: posixGroup
|
||||||
@@ -96,7 +96,7 @@ objectClass: top
|
|||||||
|
|
||||||
dn: ou=fulltime,ou=users,dc=glauth,dc=com
|
dn: ou=fulltime,ou=users,dc=glauth,dc=com
|
||||||
ou: fulltime
|
ou: fulltime
|
||||||
uid: fulltime
|
cn: fulltime
|
||||||
description: fulltime
|
description: fulltime
|
||||||
gidNumber: 5507
|
gidNumber: 5507
|
||||||
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
uniqueMember: cn=alexdoe,ou=superheros,ou=users,dc=glauth,dc=com
|
||||||
|
|||||||
@@ -24,6 +24,22 @@ echo "Fixing CI_BUILD_DIR"
|
|||||||
|
|
||||||
# This script requires that "$CI_BUILD_DIR" is set to the repo root
|
# 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)
|
# Ensure ldap utils are installed (for example - when running this outside of CI)
|
||||||
if [[ ! `which ldapsearch` ]]; then
|
if [[ ! `which ldapsearch` ]]; then
|
||||||
sudo apt-get -qq update && sudo apt-get -qq install -y ldap-utils || exit 1;
|
sudo apt-get -qq update && sudo apt-get -qq install -y ldap-utils || exit 1;
|
||||||
@@ -33,14 +49,14 @@ fi
|
|||||||
echo "";
|
echo "";
|
||||||
echo ""
|
echo ""
|
||||||
echo "Version string of tested binary:"
|
echo "Version string of tested binary:"
|
||||||
"$CI_BUILD_DIR/bin/linuxamd64/glauth" --version
|
"$GLAUTH_BINARY" --version
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Start in background, capture PID
|
# 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
|
# 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="$!"
|
glauthPid="$!"
|
||||||
|
|
||||||
echo "Running glauth at PID=$glauthPid"
|
echo "Running glauth at PID=$glauthPid"
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
-- Seed data for TestSQLitePlugin. The schema itself is created by the plugin's
|
||||||
|
-- CreateSchema() on first start, so this file only inserts rows.
|
||||||
|
|
||||||
|
INSERT INTO ldapgroups(name, gidnumber)
|
||||||
|
VALUES('superheros', 5501);
|
||||||
|
INSERT INTO ldapgroups(name, gidnumber)
|
||||||
|
VALUES('svcaccts', 5502);
|
||||||
|
INSERT INTO ldapgroups(name, gidnumber)
|
||||||
|
VALUES('civilians', 5503);
|
||||||
|
INSERT INTO ldapgroups(name, gidnumber)
|
||||||
|
VALUES('caped', 5504);
|
||||||
|
INSERT INTO ldapgroups(name, gidnumber)
|
||||||
|
VALUES('lovesailing', 5505);
|
||||||
|
INSERT INTO ldapgroups(name, gidnumber)
|
||||||
|
VALUES('smoker', 5506);
|
||||||
|
INSERT INTO includegroups(parentgroupid, includegroupid)
|
||||||
|
VALUES(5503, 5501);
|
||||||
|
INSERT INTO includegroups(parentgroupid, includegroupid)
|
||||||
|
VALUES(5504, 5502);
|
||||||
|
INSERT INTO includegroups(parentgroupid, includegroupid)
|
||||||
|
VALUES(5504, 5501);
|
||||||
|
INSERT INTO users(name, uidnumber, primarygroup, passsha256)
|
||||||
|
VALUES('hackers', 5001, 5501,
|
||||||
|
'6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a');
|
||||||
|
INSERT INTO users(name, uidnumber, primarygroup, passsha256)
|
||||||
|
VALUES('johndoe', 5002, 5502,
|
||||||
|
'6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a');
|
||||||
|
INSERT INTO users(name, mail, uidnumber, primarygroup, passsha256)
|
||||||
|
VALUES('serviceuser', "serviceuser@example.com", 5003, 5502,
|
||||||
|
'652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0');
|
||||||
|
INSERT INTO users(name, uidnumber, primarygroup, passsha256, othergroups)
|
||||||
|
VALUES('user4', 5004, 5504,
|
||||||
|
'652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0',
|
||||||
|
'5505,5506');
|
||||||
|
INSERT INTO capabilities(userid, action, object)
|
||||||
|
VALUES(5001, "search", "ou=superheros,dc=glauth,dc=com");
|
||||||
|
INSERT INTO capabilities(userid, action, object)
|
||||||
|
VALUES(5003, "search", "*");
|
||||||
@@ -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)
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
// Command tomlv validates TOML documents and prints each key's type.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/hydronica/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
flagTypes = false
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
|
|
||||||
flag.BoolVar(&flagTypes, "types", flagTypes,
|
|
||||||
"When set, the types of every defined key will be shown.")
|
|
||||||
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
log.Printf("Usage: %s toml-file [ toml-file ... ]\n",
|
|
||||||
path.Base(os.Args[0]))
|
|
||||||
flag.PrintDefaults()
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if flag.NArg() < 1 {
|
|
||||||
flag.Usage()
|
|
||||||
}
|
|
||||||
for _, f := range flag.Args() {
|
|
||||||
var tmp interface{}
|
|
||||||
md, err := toml.DecodeFile(f, &tmp)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error in '%s': %s", f, err)
|
|
||||||
}
|
|
||||||
if flagTypes {
|
|
||||||
printTypes(md)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTypes(md toml.MetaData) {
|
|
||||||
tabw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
||||||
for _, key := range md.Keys() {
|
|
||||||
fmt.Fprintf(tabw, "%s%s\t%s\n",
|
|
||||||
strings.Repeat(" ", len(key)-1), key, md.Type(key...))
|
|
||||||
}
|
|
||||||
tabw.Flush()
|
|
||||||
}
|
|
||||||
@@ -1,519 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func e(format string, args ...interface{}) error {
|
|
||||||
return fmt.Errorf("toml: "+format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
|
||||||
// TOML description of themselves.
|
|
||||||
type Unmarshaler interface {
|
|
||||||
UnmarshalTOML(interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
|
|
||||||
func Unmarshal(p []byte, v interface{}) error {
|
|
||||||
_, err := Decode(string(p), v)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Primitive is a TOML value that hasn't been decoded into a Go value.
|
|
||||||
// When using the various `Decode*` functions, the type `Primitive` may
|
|
||||||
// be given to any value, and its decoding will be delayed.
|
|
||||||
//
|
|
||||||
// A `Primitive` value can be decoded using the `PrimitiveDecode` function.
|
|
||||||
//
|
|
||||||
// The underlying representation of a `Primitive` value is subject to change.
|
|
||||||
// Do not rely on it.
|
|
||||||
//
|
|
||||||
// N.B. Primitive values are still parsed, so using them will only avoid
|
|
||||||
// the overhead of reflection. They can be useful when you don't know the
|
|
||||||
// exact type of TOML data until run time.
|
|
||||||
type Primitive struct {
|
|
||||||
undecoded interface{}
|
|
||||||
context Key
|
|
||||||
}
|
|
||||||
|
|
||||||
// DEPRECATED!
|
|
||||||
//
|
|
||||||
// Use MetaData.PrimitiveDecode instead.
|
|
||||||
func PrimitiveDecode(primValue Primitive, v interface{}) error {
|
|
||||||
md := MetaData{decoded: make(map[string]bool)}
|
|
||||||
return md.unify(primValue.undecoded, rvalue(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrimitiveDecode is just like the other `Decode*` functions, except it
|
|
||||||
// decodes a TOML value that has already been parsed. Valid primitive values
|
|
||||||
// can *only* be obtained from values filled by the decoder functions,
|
|
||||||
// including this method. (i.e., `v` may contain more `Primitive`
|
|
||||||
// values.)
|
|
||||||
//
|
|
||||||
// Meta data for primitive values is included in the meta data returned by
|
|
||||||
// the `Decode*` functions with one exception: keys returned by the Undecoded
|
|
||||||
// method will only reflect keys that were decoded. Namely, any keys hidden
|
|
||||||
// behind a Primitive will be considered undecoded. Executing this method will
|
|
||||||
// update the undecoded keys in the meta data. (See the example.)
|
|
||||||
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
|
||||||
md.context = primValue.context
|
|
||||||
defer func() { md.context = nil }()
|
|
||||||
return md.unify(primValue.undecoded, rvalue(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode will decode the contents of `data` in TOML format into a pointer
|
|
||||||
// `v`.
|
|
||||||
//
|
|
||||||
// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be
|
|
||||||
// used interchangeably.)
|
|
||||||
//
|
|
||||||
// TOML arrays of tables correspond to either a slice of structs or a slice
|
|
||||||
// of maps.
|
|
||||||
//
|
|
||||||
// TOML datetimes correspond to Go `time.Time` values.
|
|
||||||
//
|
|
||||||
// All other TOML types (float, string, int, bool and array) correspond
|
|
||||||
// to the obvious Go types.
|
|
||||||
//
|
|
||||||
// An exception to the above rules is if a type implements the
|
|
||||||
// encoding.TextUnmarshaler interface. In this case, any primitive TOML value
|
|
||||||
// (floats, strings, integers, booleans and datetimes) will be converted to
|
|
||||||
// a byte string and given to the value's UnmarshalText method. See the
|
|
||||||
// Unmarshaler example for a demonstration with time duration strings.
|
|
||||||
//
|
|
||||||
// # Key mapping
|
|
||||||
//
|
|
||||||
// TOML keys can map to either keys in a Go map or field names in a Go
|
|
||||||
// struct. The special `toml` struct tag may be used to map TOML keys to
|
|
||||||
// struct fields that don't match the key name exactly. (See the example.)
|
|
||||||
// A case insensitive match to struct names will be tried if an exact match
|
|
||||||
// can't be found.
|
|
||||||
//
|
|
||||||
// The mapping between TOML values and Go values is loose. That is, there
|
|
||||||
// may exist TOML values that cannot be placed into your representation, and
|
|
||||||
// there may be parts of your representation that do not correspond to
|
|
||||||
// TOML values. This loose mapping can be made stricter by using the IsDefined
|
|
||||||
// and/or Undecoded methods on the MetaData returned.
|
|
||||||
//
|
|
||||||
// This decoder will not handle cyclic types. If a cyclic type is passed,
|
|
||||||
// `Decode` will not terminate.
|
|
||||||
func Decode(data string, v interface{}) (MetaData, error) {
|
|
||||||
rv := reflect.ValueOf(v)
|
|
||||||
if rv.Kind() != reflect.Ptr {
|
|
||||||
return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
|
|
||||||
}
|
|
||||||
if rv.IsNil() {
|
|
||||||
return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
|
|
||||||
}
|
|
||||||
p, err := parse(data)
|
|
||||||
if err != nil {
|
|
||||||
return MetaData{}, err
|
|
||||||
}
|
|
||||||
md := MetaData{
|
|
||||||
p.mapping, p.types, p.ordered,
|
|
||||||
make(map[string]bool, len(p.ordered)), nil,
|
|
||||||
}
|
|
||||||
return md, md.unify(p.mapping, indirect(rv))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeFile is just like Decode, except it will automatically read the
|
|
||||||
// contents of the file at `fpath` and decode it for you.
|
|
||||||
func DecodeFile(fpath string, v interface{}) (MetaData, error) {
|
|
||||||
bs, err := ioutil.ReadFile(fpath)
|
|
||||||
if err != nil {
|
|
||||||
return MetaData{}, err
|
|
||||||
}
|
|
||||||
return Decode(string(bs), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeReader is just like Decode, except it will consume all bytes
|
|
||||||
// from the reader and decode it for you.
|
|
||||||
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
|
|
||||||
bs, err := ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
return MetaData{}, err
|
|
||||||
}
|
|
||||||
return Decode(string(bs), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// unify performs a sort of type unification based on the structure of `rv`,
|
|
||||||
// which is the client representation.
|
|
||||||
//
|
|
||||||
// Any type mismatch produces an error. Finding a type that we don't know
|
|
||||||
// how to handle produces an unsupported type error.
|
|
||||||
func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
|
||||||
// Special case. Look for a `Primitive` value.
|
|
||||||
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
|
|
||||||
// Save the undecoded data and the key context into the primitive
|
|
||||||
// value.
|
|
||||||
context := make(Key, len(md.context))
|
|
||||||
copy(context, md.context)
|
|
||||||
rv.Set(reflect.ValueOf(Primitive{
|
|
||||||
undecoded: data,
|
|
||||||
context: context,
|
|
||||||
}))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case. Unmarshaler Interface support.
|
|
||||||
if rv.CanAddr() {
|
|
||||||
if v, ok := rv.Addr().Interface().(Unmarshaler); ok {
|
|
||||||
return v.UnmarshalTOML(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rv.Type() == reflect.TypeOf(time.Second) {
|
|
||||||
return md.unifyDuration(data, rv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case. Look for a value satisfying the TextUnmarshaler interface.
|
|
||||||
if v, ok := rv.Interface().(TextUnmarshaler); ok {
|
|
||||||
return md.unifyText(data, v)
|
|
||||||
}
|
|
||||||
// BUG(burntsushi)
|
|
||||||
// The behavior here is incorrect whenever a Go type satisfies the
|
|
||||||
// encoding.TextUnmarshaler interface but also corresponds to a TOML
|
|
||||||
// hash or array. In particular, the unmarshaler should only be applied
|
|
||||||
// to primitive TOML values. But at this point, it will be applied to
|
|
||||||
// all kinds of values and produce an incorrect error whenever those values
|
|
||||||
// are hashes or arrays (including arrays of tables).
|
|
||||||
|
|
||||||
k := rv.Kind()
|
|
||||||
|
|
||||||
// laziness
|
|
||||||
if k >= reflect.Int && k <= reflect.Uint64 {
|
|
||||||
return md.unifyInt(data, rv)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch k {
|
|
||||||
case reflect.Ptr:
|
|
||||||
elem := reflect.New(rv.Type().Elem())
|
|
||||||
err := md.unify(data, reflect.Indirect(elem))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rv.Set(elem)
|
|
||||||
return nil
|
|
||||||
case reflect.Struct:
|
|
||||||
return md.unifyStruct(data, rv)
|
|
||||||
case reflect.Map:
|
|
||||||
return md.unifyMap(data, rv)
|
|
||||||
case reflect.Array:
|
|
||||||
return md.unifyArray(data, rv)
|
|
||||||
case reflect.Slice:
|
|
||||||
return md.unifySlice(data, rv)
|
|
||||||
case reflect.String:
|
|
||||||
return md.unifyString(data, rv)
|
|
||||||
case reflect.Bool:
|
|
||||||
return md.unifyBool(data, rv)
|
|
||||||
case reflect.Interface:
|
|
||||||
// we only support empty interfaces.
|
|
||||||
if rv.NumMethod() > 0 {
|
|
||||||
return e("unsupported type %s", rv.Type())
|
|
||||||
}
|
|
||||||
return md.unifyAnything(data, rv)
|
|
||||||
case reflect.Float32:
|
|
||||||
fallthrough
|
|
||||||
case reflect.Float64:
|
|
||||||
return md.unifyFloat64(data, rv)
|
|
||||||
}
|
|
||||||
return e("unsupported type %s", rv.Kind())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
|
||||||
tmap, ok := mapping.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
if mapping == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return e("type mismatch for %s: expected table but found %T",
|
|
||||||
rv.Type().String(), mapping)
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, datum := range tmap {
|
|
||||||
var f *field
|
|
||||||
fields := cachedTypeFields(rv.Type())
|
|
||||||
for i := range fields {
|
|
||||||
ff := &fields[i]
|
|
||||||
if ff.name == key {
|
|
||||||
f = ff
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if f == nil && strings.EqualFold(ff.name, key) {
|
|
||||||
f = ff
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if f != nil {
|
|
||||||
subv := rv
|
|
||||||
for _, i := range f.index {
|
|
||||||
subv = indirect(subv.Field(i))
|
|
||||||
}
|
|
||||||
if isUnifiable(subv) {
|
|
||||||
md.decoded[md.context.add(key).String()] = true
|
|
||||||
md.context = append(md.context, key)
|
|
||||||
if err := md.unify(datum, subv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
md.context = md.context[0 : len(md.context)-1]
|
|
||||||
} else if f.name != "" {
|
|
||||||
// Bad user! No soup for you!
|
|
||||||
return e("cannot write unexported field %s.%s",
|
|
||||||
rv.Type().String(), f.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
|
||||||
tmap, ok := mapping.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
if tmap == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("map", mapping)
|
|
||||||
}
|
|
||||||
if rv.IsNil() {
|
|
||||||
rv.Set(reflect.MakeMap(rv.Type()))
|
|
||||||
}
|
|
||||||
for k, v := range tmap {
|
|
||||||
md.decoded[md.context.add(k).String()] = true
|
|
||||||
md.context = append(md.context, k)
|
|
||||||
|
|
||||||
rvkey := indirect(reflect.New(rv.Type().Key()))
|
|
||||||
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
|
|
||||||
if err := md.unify(v, rvval); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
md.context = md.context[0 : len(md.context)-1]
|
|
||||||
|
|
||||||
rvkey.SetString(k)
|
|
||||||
rv.SetMapIndex(rvkey, rvval)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
|
||||||
datav := reflect.ValueOf(data)
|
|
||||||
if datav.Kind() != reflect.Slice {
|
|
||||||
if !datav.IsValid() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("slice", data)
|
|
||||||
}
|
|
||||||
sliceLen := datav.Len()
|
|
||||||
if sliceLen != rv.Len() {
|
|
||||||
return e("expected array length %d; got TOML array of length %d",
|
|
||||||
rv.Len(), sliceLen)
|
|
||||||
}
|
|
||||||
return md.unifySliceArray(datav, rv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
|
||||||
datav := reflect.ValueOf(data)
|
|
||||||
if datav.Kind() != reflect.Slice {
|
|
||||||
if !datav.IsValid() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("slice", data)
|
|
||||||
}
|
|
||||||
n := datav.Len()
|
|
||||||
if rv.IsNil() || rv.Cap() < n {
|
|
||||||
rv.Set(reflect.MakeSlice(rv.Type(), n, n))
|
|
||||||
}
|
|
||||||
rv.SetLen(n)
|
|
||||||
return md.unifySliceArray(datav, rv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
|
|
||||||
sliceLen := data.Len()
|
|
||||||
for i := 0; i < sliceLen; i++ {
|
|
||||||
v := data.Index(i).Interface()
|
|
||||||
sliceval := indirect(rv.Index(i))
|
|
||||||
if err := md.unify(v, sliceval); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
|
|
||||||
if _, ok := data.(time.Time); ok {
|
|
||||||
rv.Set(reflect.ValueOf(data))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("time.Time", data)
|
|
||||||
}
|
|
||||||
func (md *MetaData) unifyDuration(data interface{}, rv reflect.Value) error {
|
|
||||||
switch v := data.(type) {
|
|
||||||
case string:
|
|
||||||
d, err := time.ParseDuration(v)
|
|
||||||
if err != nil {
|
|
||||||
return e("invalid time.duration format %s", v)
|
|
||||||
}
|
|
||||||
rv.SetInt(int64(d))
|
|
||||||
return nil
|
|
||||||
case int, int64, int32, int16, int8:
|
|
||||||
return md.unifyInt(data, rv)
|
|
||||||
}
|
|
||||||
return badtype("time.duration", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
|
|
||||||
if s, ok := data.(string); ok {
|
|
||||||
rv.SetString(s)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("string", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
|
||||||
if num, ok := data.(float64); ok {
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Float32:
|
|
||||||
fallthrough
|
|
||||||
case reflect.Float64:
|
|
||||||
rv.SetFloat(num)
|
|
||||||
default:
|
|
||||||
panic("bug")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("float", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
|
||||||
if num, ok := data.(int64); ok {
|
|
||||||
if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 {
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Int, reflect.Int64:
|
|
||||||
// No bounds checking necessary.
|
|
||||||
case reflect.Int8:
|
|
||||||
if num < math.MinInt8 || num > math.MaxInt8 {
|
|
||||||
return e("value %d is out of range for int8", num)
|
|
||||||
}
|
|
||||||
case reflect.Int16:
|
|
||||||
if num < math.MinInt16 || num > math.MaxInt16 {
|
|
||||||
return e("value %d is out of range for int16", num)
|
|
||||||
}
|
|
||||||
case reflect.Int32:
|
|
||||||
if num < math.MinInt32 || num > math.MaxInt32 {
|
|
||||||
return e("value %d is out of range for int32", num)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rv.SetInt(num)
|
|
||||||
} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 {
|
|
||||||
unum := uint64(num)
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Uint, reflect.Uint64:
|
|
||||||
// No bounds checking necessary.
|
|
||||||
case reflect.Uint8:
|
|
||||||
if num < 0 || unum > math.MaxUint8 {
|
|
||||||
return e("value %d is out of range for uint8", num)
|
|
||||||
}
|
|
||||||
case reflect.Uint16:
|
|
||||||
if num < 0 || unum > math.MaxUint16 {
|
|
||||||
return e("value %d is out of range for uint16", num)
|
|
||||||
}
|
|
||||||
case reflect.Uint32:
|
|
||||||
if num < 0 || unum > math.MaxUint32 {
|
|
||||||
return e("value %d is out of range for uint32", num)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rv.SetUint(unum)
|
|
||||||
} else {
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("integer", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
|
||||||
if b, ok := data.(bool); ok {
|
|
||||||
rv.SetBool(b)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("boolean", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
|
||||||
rv.Set(reflect.ValueOf(data))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
|
|
||||||
var s string
|
|
||||||
switch sdata := data.(type) {
|
|
||||||
case TextMarshaler:
|
|
||||||
text, err := sdata.MarshalText()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s = string(text)
|
|
||||||
case fmt.Stringer:
|
|
||||||
s = sdata.String()
|
|
||||||
case string:
|
|
||||||
s = sdata
|
|
||||||
case bool:
|
|
||||||
s = fmt.Sprintf("%v", sdata)
|
|
||||||
case int64:
|
|
||||||
s = fmt.Sprintf("%d", sdata)
|
|
||||||
case float64:
|
|
||||||
s = fmt.Sprintf("%f", sdata)
|
|
||||||
default:
|
|
||||||
return badtype("primitive (string-like)", data)
|
|
||||||
}
|
|
||||||
if err := v.UnmarshalText([]byte(s)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
|
|
||||||
func rvalue(v interface{}) reflect.Value {
|
|
||||||
return indirect(reflect.ValueOf(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// indirect returns the value pointed to by a pointer.
|
|
||||||
// Pointers are followed until the value is not a pointer.
|
|
||||||
// New values are allocated for each nil pointer.
|
|
||||||
//
|
|
||||||
// An exception to this rule is if the value satisfies an interface of
|
|
||||||
// interest to us (like encoding.TextUnmarshaler).
|
|
||||||
func indirect(v reflect.Value) reflect.Value {
|
|
||||||
if v.Kind() != reflect.Ptr {
|
|
||||||
if v.CanSet() {
|
|
||||||
pv := v.Addr()
|
|
||||||
if _, ok := pv.Interface().(TextUnmarshaler); ok {
|
|
||||||
return pv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
if v.IsNil() {
|
|
||||||
v.Set(reflect.New(v.Type().Elem()))
|
|
||||||
}
|
|
||||||
return indirect(reflect.Indirect(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
func isUnifiable(rv reflect.Value) bool {
|
|
||||||
if rv.CanSet() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := rv.Interface().(TextUnmarshaler); ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func badtype(expected string, data interface{}) error {
|
|
||||||
return e("cannot load TOML value of type %T into a Go %s", data, expected)
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// MetaData allows access to meta information about TOML data that may not
|
|
||||||
// be inferrable via reflection. In particular, whether a key has been defined
|
|
||||||
// and the TOML type of a key.
|
|
||||||
type MetaData struct {
|
|
||||||
mapping map[string]interface{}
|
|
||||||
types map[string]tomlType
|
|
||||||
keys []Key
|
|
||||||
decoded map[string]bool
|
|
||||||
context Key // Used only during decoding.
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDefined returns true if the key given exists in the TOML data. The key
|
|
||||||
// should be specified hierarchially. e.g.,
|
|
||||||
//
|
|
||||||
// // access the TOML key 'a.b.c'
|
|
||||||
// IsDefined("a", "b", "c")
|
|
||||||
//
|
|
||||||
// IsDefined will return false if an empty key given. Keys are case sensitive.
|
|
||||||
func (md *MetaData) IsDefined(key ...string) bool {
|
|
||||||
if len(key) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var hash map[string]interface{}
|
|
||||||
var ok bool
|
|
||||||
var hashOrVal interface{} = md.mapping
|
|
||||||
for _, k := range key {
|
|
||||||
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if hashOrVal, ok = hash[k]; !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns a string representation of the type of the key specified.
|
|
||||||
//
|
|
||||||
// Type will return the empty string if given an empty key or a key that
|
|
||||||
// does not exist. Keys are case sensitive.
|
|
||||||
func (md *MetaData) Type(key ...string) string {
|
|
||||||
fullkey := strings.Join(key, ".")
|
|
||||||
if typ, ok := md.types[fullkey]; ok {
|
|
||||||
return typ.typeString()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key is the type of any TOML key, including key groups. Use (MetaData).Keys
|
|
||||||
// to get values of this type.
|
|
||||||
type Key []string
|
|
||||||
|
|
||||||
func (k Key) String() string {
|
|
||||||
return strings.Join(k, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k Key) maybeQuotedAll() string {
|
|
||||||
var ss []string
|
|
||||||
for i := range k {
|
|
||||||
ss = append(ss, k.maybeQuoted(i))
|
|
||||||
}
|
|
||||||
return strings.Join(ss, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k Key) maybeQuoted(i int) string {
|
|
||||||
quote := false
|
|
||||||
for _, c := range k[i] {
|
|
||||||
if !isBareKeyChar(c) {
|
|
||||||
quote = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if quote {
|
|
||||||
return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\""
|
|
||||||
}
|
|
||||||
return k[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k Key) add(piece string) Key {
|
|
||||||
newKey := make(Key, len(k)+1)
|
|
||||||
copy(newKey, k)
|
|
||||||
newKey[len(k)] = piece
|
|
||||||
return newKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys returns a slice of every key in the TOML data, including key groups.
|
|
||||||
// Each key is itself a slice, where the first element is the top of the
|
|
||||||
// hierarchy and the last is the most specific.
|
|
||||||
//
|
|
||||||
// The list will have the same order as the keys appeared in the TOML data.
|
|
||||||
//
|
|
||||||
// All keys returned are non-empty.
|
|
||||||
func (md *MetaData) Keys() []Key {
|
|
||||||
return md.keys
|
|
||||||
}
|
|
||||||
|
|
||||||
// Undecoded returns all keys that have not been decoded in the order in which
|
|
||||||
// they appear in the original TOML document.
|
|
||||||
//
|
|
||||||
// This includes keys that haven't been decoded because of a Primitive value.
|
|
||||||
// Once the Primitive value is decoded, the keys will be considered decoded.
|
|
||||||
//
|
|
||||||
// Also note that decoding into an empty interface will result in no decoding,
|
|
||||||
// and so no keys will be considered decoded.
|
|
||||||
//
|
|
||||||
// In this sense, the Undecoded keys correspond to keys in the TOML document
|
|
||||||
// that do not have a concrete type in your representation.
|
|
||||||
func (md *MetaData) Undecoded() []Key {
|
|
||||||
undecoded := make([]Key, 0, len(md.keys))
|
|
||||||
for _, key := range md.keys {
|
|
||||||
if !md.decoded[key.String()] {
|
|
||||||
undecoded = append(undecoded, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undecoded
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) Mappings() map[string]interface{} {
|
|
||||||
return md.mapping
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user