mirror of
https://github.com/glauth/glauth.git
synced 2026-10-27 06:26:59 +00:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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.21.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
|
||||||
|
|||||||
+15
@@ -3,4 +3,19 @@ v2/certs/
|
|||||||
v2/bin
|
v2/bin
|
||||||
v2/docker/assets/linux
|
v2/docker/assets/linux
|
||||||
v2/gl.db
|
v2/gl.db
|
||||||
|
v2/buildlogs
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|||||||
+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.1"
|
||||||
|
}
|
||||||
@@ -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": [
|
||||||
|
"v2/internal/version/const.go"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
|
||||||
|
}
|
||||||
@@ -1,5 +1,101 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+161
-43
@@ -1,5 +1,7 @@
|
|||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
|
MAKEFLAGS += --no-print-directory
|
||||||
|
|
||||||
VERSION=$(shell bin/linuxamd64/glauth --version)
|
VERSION=$(shell bin/linuxamd64/glauth --version)
|
||||||
|
|
||||||
GIT_COMMIT=$(shell git rev-list -1 HEAD )
|
GIT_COMMIT=$(shell git rev-list -1 HEAD )
|
||||||
@@ -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,30 @@ 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
|
||||||
|
#test: @ runs package, root integration, and shell integration tests
|
||||||
|
test: mocks vet
|
||||||
|
go test -v -cover -coverprofile coverage.out ./pkg/... ./internal/... .
|
||||||
|
$(MAKE) runtest
|
||||||
#all: @ run build process for all binaries
|
#all: @ 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 +80,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,7 +114,7 @@ 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
|
||||||
@@ -87,14 +125,22 @@ mkbindir:
|
|||||||
.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 +153,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 +176,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} /^\)/{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)
|
(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))
|
|
||||||
|
#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 && \
|
@mkdir -p docker/assets/linux/amd64 docker/assets/linux/arm64 docker/assets/linux/arm/v7 && \
|
||||||
cp -f bin/glauth-linux-amd64 docker/assets/linux/amd64/glauth && \
|
cp -f bin/glauth-linux-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 && \
|
||||||
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 _buildmain _buildplugins remember-embed _buildmainembeds builddocker
|
||||||
|
|
||||||
#testdocker: @ run integration test using docker
|
#testdocker: @ run integration test using docker
|
||||||
testdocker:
|
testdocker:
|
||||||
@@ -225,4 +339,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 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
|
||||||
|
|||||||
+104
-556
@@ -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,46 +130,87 @@ func startService() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
monitor := monitoring.NewMonitor(&log)
|
||||||
|
tracer := tracing.NewTracer(
|
||||||
|
tracing.NewConfig(
|
||||||
|
activeConfig.Tracing.Enabled,
|
||||||
|
activeConfig.Tracing.GRPCEndpoint,
|
||||||
|
activeConfig.Tracing.HTTPEndpoint,
|
||||||
|
&log,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
startConfigWatcher()
|
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
|
go func() {
|
||||||
shouldBlock := !activeConfig.LDAPS.Enabled
|
|
||||||
|
|
||||||
if shouldBlock {
|
|
||||||
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")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} else {
|
}()
|
||||||
go func() {
|
|
||||||
if err := s.ListenAndServe(); err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not start LDAP server")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if activeConfig.LDAPS.Enabled {
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
+322
-152
@@ -1,12 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type testEnv struct {
|
type testEnv struct {
|
||||||
@@ -24,174 +27,341 @@ type testEnv struct {
|
|||||||
checkemployeetype string
|
checkemployeetype string
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegerStuff(t *testing.T) {
|
func glauthBinary() string {
|
||||||
|
if binary := os.Getenv("GLAUTH_TEST_BINARY"); binary != "" {
|
||||||
Convey("Testing sample-simple local file-based LDAP server", t, func() {
|
return binary
|
||||||
svc := startSvc(SD, "bin/glauth", "-c", "sample-simple.cfg")
|
|
||||||
batteryOfTests(
|
|
||||||
t,
|
|
||||||
svc, testEnv{
|
|
||||||
checkanonymousrootDSE: true,
|
|
||||||
checkTOTP: true,
|
|
||||||
checkbindUPN: true,
|
|
||||||
expectedinfo: "supportedLDAPVersion: 3",
|
|
||||||
svcdn: "cn=serviceuser,ou=svcaccts,dc=glauth,dc=com",
|
|
||||||
svcdnnogroup: "cn=serviceuser,dc=glauth,dc=com",
|
|
||||||
otpdn: "cn=otpuser,ou=superheros,dc=glauth,dc=com",
|
|
||||||
expectedaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
|
|
||||||
scopedaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
|
|
||||||
expectedfirstaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
|
|
||||||
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
|
|
||||||
checkemployeetype: "cn=hackers",
|
|
||||||
})
|
|
||||||
stopSvc(svc)
|
|
||||||
})
|
|
||||||
|
|
||||||
matchingLibrary := doRunGetFirst(RD, "ls", "bin/sqlite.so")
|
|
||||||
if matchingLibrary == "bin/sqlite.so" {
|
|
||||||
Convey("Testing sample-database LDAP server", t, func() {
|
|
||||||
svc := startSvc(SD, "bin/glauth", "-c", "pkg/plugins/sample-database.cfg")
|
|
||||||
batteryOfTests(
|
|
||||||
t,
|
|
||||||
svc, testEnv{
|
|
||||||
checkanonymousrootDSE: true,
|
|
||||||
checkTOTP: false,
|
|
||||||
checkbindUPN: true,
|
|
||||||
expectedinfo: "supportedLDAPVersion: 3",
|
|
||||||
svcdn: "cn=serviceuser,ou=svcaccts,dc=glauth,dc=com",
|
|
||||||
svcdnnogroup: "cn=serviceuser,dc=glauth,dc=com",
|
|
||||||
otpdn: "cn=otpuser,ou=superheros,dc=glauth,dc=com",
|
|
||||||
expectedaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
|
|
||||||
scopedaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
|
|
||||||
expectedfirstaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
|
|
||||||
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
|
|
||||||
checkemployeetype: "",
|
|
||||||
})
|
|
||||||
stopSvc(svc)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
matchingContainers := doRunGetFirst(RD, "sh", "-c", "docker ps | grep openldap-service | wc -l")
|
qualified := filepath.Join("bin", runtime.GOOS+runtime.GOARCH, "glauth")
|
||||||
if matchingContainers == "1" {
|
if _, err := os.Stat(qualified); err == nil {
|
||||||
Convey("Testing sample-simple local LDAP server", t, func() {
|
return qualified
|
||||||
svc := startSvc(SD, "bin/glauth", "-c", "sample-ldap-injection.cfg")
|
}
|
||||||
batteryOfTests(
|
return "bin/glauth"
|
||||||
t,
|
}
|
||||||
svc, testEnv{
|
|
||||||
checkanonymousrootDSE: false,
|
func TestProperBuild(t *testing.T) {
|
||||||
checkTOTP: true,
|
info, err := os.Stat(glauthBinary())
|
||||||
checkbindUPN: false,
|
if err != nil {
|
||||||
expectedinfo: "objectClass: top",
|
t.Fatal(err)
|
||||||
svcdn: "cn=serviceuser,cn=svcaccts,ou=users,dc=glauth,dc=com",
|
}
|
||||||
svcdnnogroup: "", // ignore
|
mode := uint32(info.Mode())
|
||||||
otpdn: "cn=otpuser,cn=superheros,ou=users,dc=glauth,dc=com",
|
if mode&0b001001001 == 0 {
|
||||||
expectedaccount: "dn: cn=hackers,cn=superheros,ou=users,dc=glauth,dc=com",
|
t.Fatalf("bad file mode: %b", mode)
|
||||||
scopedaccount: "dn: cn=hackers,cn=superheros,dc=glauth,dc=com",
|
}
|
||||||
expectedfirstaccount: "dn: cn=johndoe,cn=superheros,ou=users,dc=glauth,dc=com",
|
}
|
||||||
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
|
|
||||||
checkemployeetype: "",
|
func TestConfigBackendRejectsPasswordlessUserBinds(t *testing.T) {
|
||||||
})
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
stopSvc(svc)
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
port := listener.Addr().(*net.TCPAddr).Port
|
||||||
|
listener.Close()
|
||||||
|
|
||||||
|
configPath := filepath.Join(t.TempDir(), "passwordless.cfg")
|
||||||
|
config := fmt.Sprintf(`debug = false
|
||||||
|
|
||||||
|
[ldap]
|
||||||
|
enabled = true
|
||||||
|
listen = "127.0.0.1:%d"
|
||||||
|
tls = false
|
||||||
|
|
||||||
|
[ldaps]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[backend]
|
||||||
|
datastore = "config"
|
||||||
|
baseDN = "dc=glauth,dc=com"
|
||||||
|
|
||||||
|
[behaviors]
|
||||||
|
LimitFailedBinds = false
|
||||||
|
|
||||||
|
[[groups]]
|
||||||
|
name = "svc"
|
||||||
|
gidnumber = 5501
|
||||||
|
|
||||||
|
[[users]]
|
||||||
|
name = "nopass"
|
||||||
|
uidnumber = 5001
|
||||||
|
primarygroup = 5501
|
||||||
|
[[users.capabilities]]
|
||||||
|
action = "search"
|
||||||
|
object = "*"
|
||||||
|
`, port)
|
||||||
|
if err := os.WriteFile(configPath, []byte(config), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc := startSvc(SD, glauthBinary(), "-c", configPath)
|
||||||
|
defer stopSvc(svc)
|
||||||
|
|
||||||
|
bindDN := "cn=nopass,dc=glauth,dc=com"
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
password string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "arbitrary password", password: "definitely-wrong", want: "exit status 49"},
|
||||||
|
{name: "empty password", password: "", want: "exit status 53"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got := doRunGetFirst(0, "ldapsearch", "-LLL", "-H", fmt.Sprintf("ldap://127.0.0.1:%d", port), "-D", bindDN, "-w", tc.password, "-x", "-b", "dc=glauth,dc=com", "(cn=nopass)")
|
||||||
|
if got != tc.want {
|
||||||
|
t.Fatalf("bind result = %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func batteryOfTests(t *testing.T, svc *exec.Cmd, env testEnv) {
|
func ldapOnlyConfig(t *testing.T, source string) string {
|
||||||
Convey("When searching for the 'hackers' user", func() {
|
t.Helper()
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
data, err := os.ReadFile(source)
|
||||||
Convey("We should find them in the 'superheros' group", func() {
|
if err != nil {
|
||||||
So(out, ShouldEqual, env.expectedaccount)
|
t.Fatal(err)
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
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 {
|
contents := string(data)
|
||||||
Convey("When searching for the 'hackers' user after binding using the account's UPN", func() {
|
sectionMarker := "\n[ldaps]\n"
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", "serviceuser@example.com", "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
markerStart := strings.Index(contents, sectionMarker)
|
||||||
Convey("We should find them in the 'superheros' group", func() {
|
if markerStart < 0 {
|
||||||
So(out, ShouldEqual, env.expectedaccount)
|
t.Fatalf("%s has no [ldaps] section", source)
|
||||||
})
|
}
|
||||||
})
|
start := markerStart + 1
|
||||||
|
relEnd := strings.Index(contents[start+len("[ldaps]"):], "\n[")
|
||||||
|
if relEnd < 0 {
|
||||||
|
t.Fatalf("%s has no section after [ldaps]", source)
|
||||||
|
}
|
||||||
|
end := start + len("[ldaps]") + relEnd
|
||||||
|
section := contents[start:end]
|
||||||
|
sectionWithoutLDAPS := strings.Replace(section, "enabled = true", "enabled = false", 1)
|
||||||
|
if section == sectionWithoutLDAPS {
|
||||||
|
t.Fatalf("%s [ldaps] section does not enable LDAPS", source)
|
||||||
}
|
}
|
||||||
|
|
||||||
Convey("When querying the root SDE", func() {
|
contents = contents[:start] + sectionWithoutLDAPS + contents[end:]
|
||||||
out := doRunGetSecond(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-s", "base", "(objectclass=*)")
|
path := filepath.Join(t.TempDir(), filepath.Base(source))
|
||||||
Convey("We should get some meta information", func() {
|
if err := os.WriteFile(path, []byte(contents), 0600); err != nil {
|
||||||
So(out, ShouldEqual, env.expectedinfo)
|
t.Fatal(err)
|
||||||
})
|
}
|
||||||
})
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
if env.checkanonymousrootDSE {
|
func batteryOfTests(t *testing.T, env *testEnv) {
|
||||||
Convey("When querying the root SDE anonymously without authorizing in config file", func() {
|
tests := []struct {
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-x", "-s", "base", "(objectclass=*)")
|
Name string
|
||||||
Convey("We should get error 50", func() {
|
Path string
|
||||||
So(out, ShouldEqual, "exit status 50")
|
Check func(t testing.TB)
|
||||||
})
|
}{
|
||||||
})
|
{
|
||||||
|
Name: "searching for the 'hackers' user",
|
||||||
|
Check: func(t testing.TB) {
|
||||||
|
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
||||||
|
if got, want := out, env.expectedaccount; got != want {
|
||||||
|
t.Fatalf("should find them in the 'superheros' group\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "searching for the 'hackers' user without binding with a group",
|
||||||
|
Check: func(t testing.TB) {
|
||||||
|
if env.svcdnnogroup == "" {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdnnogroup, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
||||||
|
if got, want := out, env.expectedaccount; got != want {
|
||||||
|
t.Fatalf("should find them in the 'superheros' group\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "searching for the 'hackers' user after binding using the account's UPN",
|
||||||
|
Check: func(t testing.TB) {
|
||||||
|
if !env.checkbindUPN {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", "serviceuser@example.com", "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
||||||
|
if got, want := out, env.expectedaccount; got != want {
|
||||||
|
t.Fatalf("should find them in the 'superheros' group\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "querying the root SDE",
|
||||||
|
Check: func(t testing.TB) {
|
||||||
|
out := doRunGetSecond(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-s", "base", "(objectclass=*)")
|
||||||
|
if got, want := out, env.expectedinfo; got != want {
|
||||||
|
t.Fatalf("should get some meta information\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "querying the root SDE anonymously without authorizing in config file",
|
||||||
|
Check: func(t testing.TB) {
|
||||||
|
if !env.checkanonymousrootDSE {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-x", "-s", "base", "(objectclass=*)")
|
||||||
|
if got, want := out, "exit status 50"; got != want {
|
||||||
|
t.Fatalf("should get error 50\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "enumerating posix groups",
|
||||||
|
Check: func(t testing.TB) {
|
||||||
|
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(objectclass=posixgroup)")
|
||||||
|
if got, want := out, env.expectedgroup; got != want {
|
||||||
|
t.Fatalf("should get a list starting with the 'superheros' group\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "searching for members of the 'superheros' group",
|
||||||
|
Check: func(t testing.TB) {
|
||||||
|
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)")
|
||||||
|
if got, want := out, env.expectedfirstaccount; got != want {
|
||||||
|
t.Fatalf("should get a list starting with the 'hackers' user\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "performing a complex search for members of 'superheros' group",
|
||||||
|
Check: func(t testing.TB) {
|
||||||
|
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))")
|
||||||
|
if got, want := out, env.expectedfirstaccount; got != want {
|
||||||
|
t.Fatalf("should get a list starting with the 'hackers' user\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "searching for the 'hacker' user using a TOTP-enabled account",
|
||||||
|
Check: func(t testing.TB) {
|
||||||
|
if !env.checkTOTP {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
if got, want := out, env.scopedaccount; got != want {
|
||||||
|
t.Fatalf("should find them in in the 'superheros' group\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "searching for the 'hacker' user using a TOTP-enabled account and no value",
|
||||||
|
Check: func(t testing.TB) {
|
||||||
|
if !env.checkTOTP {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret", "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
|
||||||
|
if got, want := out, "exit status 49"; got != want {
|
||||||
|
t.Fatalf("should get 'Invalid credentials(49)'\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "searching for the 'hacker' user using a TOTP-enabled account and the wrong value",
|
||||||
|
Check: func(t testing.TB) {
|
||||||
|
if !env.checkTOTP {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret123456", "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
|
||||||
|
if got, want := out, "exit status 49"; got != want {
|
||||||
|
t.Fatalf("should get 'Invalid credentials(49)'\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "searching for the 'hacker' user",
|
||||||
|
Check: func(t testing.TB) {
|
||||||
|
if env.checkemployeetype == "" {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
out := doRunGetSecond(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", env.checkemployeetype, "employeetype")
|
||||||
|
if got, want := out, "employeetype: Intern"; got != want {
|
||||||
|
t.Fatalf("type should be 'Intern'\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Convey("When enumerating posix groups", func() {
|
for _, tc := range tests {
|
||||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(objectclass=posixgroup)")
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
Convey("We should get a list starting with the 'superheros' group", func() {
|
tc.Check(t)
|
||||||
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 != "" {
|
func TestSampleSimple(t *testing.T) {
|
||||||
Convey("When searching for the 'hacker' user", func() {
|
env := testEnv{
|
||||||
out := doRunGetSecond(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", env.checkemployeetype, "employeetype")
|
checkanonymousrootDSE: true,
|
||||||
Convey("Employee type should be 'Intern'", func() {
|
checkTOTP: true,
|
||||||
So(out, ShouldEqual, "employeetype: Intern")
|
checkbindUPN: true,
|
||||||
})
|
expectedinfo: "supportedLDAPVersion: 3",
|
||||||
})
|
svcdn: "cn=serviceuser,ou=svcaccts,dc=glauth,dc=com",
|
||||||
|
svcdnnogroup: "cn=serviceuser,dc=glauth,dc=com",
|
||||||
|
otpdn: "cn=otpuser,ou=superheros,dc=glauth,dc=com",
|
||||||
|
expectedaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
|
||||||
|
scopedaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
|
||||||
|
expectedfirstaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
|
||||||
|
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
|
||||||
|
checkemployeetype: "cn=hackers",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svc := startSvc(SD, glauthBinary(), "-c", ldapOnlyConfig(t, "sample-simple.cfg"))
|
||||||
|
batteryOfTests(t, &env)
|
||||||
|
stopSvc(svc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSQLitePlugin(t *testing.T) {
|
||||||
|
matchingLibrary := doRunGetFirst(RD, "ls", "bin/sqlite.so")
|
||||||
|
if matchingLibrary != "bin/sqlite.so" {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
env := testEnv{
|
||||||
|
checkanonymousrootDSE: true,
|
||||||
|
checkTOTP: false,
|
||||||
|
checkbindUPN: true,
|
||||||
|
expectedinfo: "supportedLDAPVersion: 3",
|
||||||
|
svcdn: "cn=serviceuser,ou=svcaccts,dc=glauth,dc=com",
|
||||||
|
svcdnnogroup: "cn=serviceuser,dc=glauth,dc=com",
|
||||||
|
otpdn: "cn=otpuser,ou=superheros,dc=glauth,dc=com",
|
||||||
|
expectedaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
|
||||||
|
scopedaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
|
||||||
|
expectedfirstaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
|
||||||
|
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
|
||||||
|
checkemployeetype: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
svc := startSvc(SD, glauthBinary(), "-c", "pkg/plugins/glauth-sqlite/sample-database.cfg")
|
||||||
|
batteryOfTests(t, &env)
|
||||||
|
stopSvc(svc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLdapInjection(t *testing.T) {
|
||||||
|
matchingContainers := doRunGetFirst(RD, "sh", "-c", "docker ps | grep ldap-service | wc -l")
|
||||||
|
if matchingContainers != "1" {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
env := testEnv{
|
||||||
|
checkanonymousrootDSE: false,
|
||||||
|
checkTOTP: true,
|
||||||
|
checkbindUPN: false,
|
||||||
|
expectedinfo: "objectClass: top",
|
||||||
|
svcdn: "cn=serviceuser,cn=svcaccts,ou=users,dc=glauth,dc=com",
|
||||||
|
svcdnnogroup: "", // ignore
|
||||||
|
otpdn: "cn=otpuser,cn=superheros,ou=users,dc=glauth,dc=com",
|
||||||
|
expectedaccount: "dn: cn=hackers,cn=superheros,ou=users,dc=glauth,dc=com",
|
||||||
|
scopedaccount: "dn: cn=hackers,cn=superheros,ou=users,dc=glauth,dc=com",
|
||||||
|
expectedfirstaccount: "dn: cn=johndoe,cn=superheros,ou=users,dc=glauth,dc=com",
|
||||||
|
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
|
||||||
|
checkemployeetype: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
svc := startSvc(SD, glauthBinary(), "-c", "sample-ldap-injection.cfg")
|
||||||
|
batteryOfTests(t, &env)
|
||||||
|
stopSvc(svc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----=============================================================================----
|
// -----=============================================================================----
|
||||||
|
|||||||
@@ -1,44 +1,59 @@
|
|||||||
module github.com/glauth/glauth/v2
|
module github.com/glauth/glauth/v2
|
||||||
|
|
||||||
go 1.19
|
go 1.21
|
||||||
|
|
||||||
// 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.21.1
|
||||||
|
go.opentelemetry.io/otel v1.21.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0
|
||||||
|
go.opentelemetry.io/otel/sdk v1.21.0
|
||||||
|
go.opentelemetry.io/otel/trace v1.21.0
|
||||||
|
go.uber.org/mock v0.4.0
|
||||||
|
golang.org/x/crypto v0.18.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/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
github.com/go-logr/logr v1.3.0 // indirect
|
||||||
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
|
||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/common v0.45.0 // indirect
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
golang.org/x/sys v0.6.0 // indirect
|
github.com/rickb777/date v1.12.4 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
github.com/rickb777/plural v1.2.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
go.opentelemetry.io/otel/metric v1.21.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||||
|
golang.org/x/net v0.17.0 // indirect
|
||||||
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
|
||||||
|
google.golang.org/grpc v1.59.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,534 +1,175 @@
|
|||||||
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/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
|
||||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
|
||||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/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/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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 h1:D9EvfGQvlkKaDr2CRKN++7HbSXbefUNDrPq60T+g24s=
|
|
||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484/go.mod h1:O1EljZ+oHprtxDDPHiMWVo/5dBT6PlvWX5PSwj80aBA=
|
|
||||||
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba h1:DO8NFYdcRv1dnyAINJIBm6Bw2XibtLvQniNFGzf2W8E=
|
|
||||||
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba/go.mod h1:4S0XndRL8HNOaQBfdViJ2F/GPCgL524xlXRuXFH12/U=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/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/contrib/propagators/jaeger v1.21.1 h1:f4beMGDKiVzg9IcX7/VuWVy+oGdjx3dNJ72YehmtY5k=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opentelemetry.io/contrib/propagators/jaeger v1.21.1/go.mod h1:U9jhkEl8d1LL+QXY7q3kneJWJugiN3kZJV2OWz3hkBY=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
|
||||||
|
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/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/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/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||||
|
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||||
|
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/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/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||||
|
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/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
|
||||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.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.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-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/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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-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/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
|
||||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
|
||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-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=
|
|
||||||
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.1" // 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)
|
||||||
}
|
}
|
||||||
+148
-116
@@ -3,120 +3,152 @@ package config
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// config file
|
// config file
|
||||||
type Backend struct {
|
type (
|
||||||
BaseDN string
|
Backend struct {
|
||||||
Datastore string
|
BaseDN string
|
||||||
Insecure bool // For LDAP and owncloud backend only
|
Datastore string
|
||||||
Servers []string // For LDAP and owncloud backend only
|
Insecure bool // For LDAP and owncloud backend only
|
||||||
NameFormat string
|
Servers []string // For LDAP and owncloud backend only
|
||||||
GroupFormat string
|
NameFormat string // e.g. cn, ou, uid, or a comma separated list of them
|
||||||
SSHKeyAttr string
|
NameFormatAsArray []string // we will explode NameFormat on commas
|
||||||
UseGraphAPI bool // For ownCloud backend only
|
GroupFormat string // e.g. cn, ou, gid, or a comma separated list of them
|
||||||
Plugin string // Path to plugin library, for plugin backend only
|
GroupFormatAsArray []string // we will explode GroupFormat on commas
|
||||||
PluginHandler string // Name of plugin's main handler function
|
SSHKeyAttr string
|
||||||
Database string // For Database backends only
|
UseGraphAPI bool // For ownCloud backend only
|
||||||
GroupWithSearchCapability string // For PamLinux backend only
|
Plugin string // Path to plugin library, for plugin backend only
|
||||||
AnonymousDSE bool // For Config and Database backends only
|
PluginHandler string // Name of plugin's main handler function
|
||||||
}
|
Database string // For Database backends only
|
||||||
type Helper struct {
|
GroupWithSearchCapability string // For PamLinux backend only
|
||||||
Enabled bool
|
AnonymousDSE bool // For Config and Database backends only
|
||||||
BaseDN string
|
}
|
||||||
Datastore string
|
|
||||||
Plugin string // Path to plugin library, for plugin backend only
|
|
||||||
PluginHandler string // Name of plugin's main handler function
|
|
||||||
Database string // For MySQL backend only TODO REname to match plugin
|
|
||||||
}
|
|
||||||
type Frontend struct {
|
|
||||||
AllowedBaseDNs []string // For LDAP backend only
|
|
||||||
Listen string
|
|
||||||
Cert string
|
|
||||||
Key string
|
|
||||||
TLS bool
|
|
||||||
}
|
|
||||||
type LDAP struct {
|
|
||||||
Enabled bool
|
|
||||||
Listen string
|
|
||||||
}
|
|
||||||
type LDAPS struct {
|
|
||||||
Enabled bool
|
|
||||||
Listen string
|
|
||||||
Cert string
|
|
||||||
Key string
|
|
||||||
}
|
|
||||||
type API struct {
|
|
||||||
Cert string
|
|
||||||
Enabled bool
|
|
||||||
Internals bool
|
|
||||||
Key string
|
|
||||||
Listen string
|
|
||||||
SecretToken string
|
|
||||||
TLS bool
|
|
||||||
}
|
|
||||||
type Behaviors struct {
|
|
||||||
IgnoreCapabilities bool
|
|
||||||
LimitFailedBinds bool
|
|
||||||
NumberOfFailedBinds int
|
|
||||||
PeriodOfFailedBinds time.Duration
|
|
||||||
BlockFailedBindsFor time.Duration
|
|
||||||
PruneSourceTableEvery time.Duration
|
|
||||||
PruneSourcesOlderThan time.Duration
|
|
||||||
LegacyVersion int
|
|
||||||
}
|
|
||||||
type Capability struct {
|
|
||||||
Action string
|
|
||||||
Object string
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserAuthenticator authenticates a user via custom auth from a backend
|
Helper struct {
|
||||||
type UserAuthenticator func(user *User, pw string) error
|
Enabled bool
|
||||||
type User struct {
|
BaseDN string
|
||||||
Name string
|
Datastore string
|
||||||
OtherGroups []int
|
Plugin string // Path to plugin library, for plugin backend only
|
||||||
PassSHA256 string
|
PluginHandler string // Name of plugin's main handler function
|
||||||
PassBcrypt string
|
Database string // For MySQL backend only TODO REname to match plugin
|
||||||
PassAppSHA256 []string
|
}
|
||||||
PassAppBcrypt []string
|
|
||||||
PassAppCustom UserAuthenticator
|
Frontend struct {
|
||||||
PrimaryGroup int
|
AllowedBaseDNs []string // For LDAP backend only
|
||||||
Capabilities []Capability
|
Listen string
|
||||||
SSHKeys []string
|
Cert string
|
||||||
OTPSecret string
|
Key string
|
||||||
Yubikey string
|
TLS bool
|
||||||
Disabled bool
|
}
|
||||||
UnixID int // TODO: remove after deprecating UnixID on User and Group
|
|
||||||
UIDNumber int
|
LDAP struct {
|
||||||
Mail string
|
Enabled bool
|
||||||
LoginShell string
|
Listen string
|
||||||
GivenName string
|
// StartTLS parameters
|
||||||
SN string
|
TLS bool
|
||||||
Homedir string
|
TLSCert string
|
||||||
CustomAttrs map[string]interface{}
|
TLSKey string
|
||||||
}
|
TLSCertPath string
|
||||||
type Group struct {
|
TLSKeyPath string
|
||||||
Name string
|
LegacyTLS bool
|
||||||
UnixID int // TODO: remove after deprecating UnixID on User and Group
|
}
|
||||||
GIDNumber int
|
|
||||||
IncludeGroups []int
|
LDAPS struct {
|
||||||
}
|
Enabled bool
|
||||||
type Config struct {
|
Listen string
|
||||||
API API
|
// LDAPS TLS parameters
|
||||||
Backend Backend // Deprecated
|
Cert string
|
||||||
Backends []Backend
|
Key string
|
||||||
Helper Helper
|
CertPath string
|
||||||
Behaviors Behaviors
|
KeyPath string
|
||||||
Debug bool
|
LegacyTLS bool
|
||||||
Syslog bool
|
}
|
||||||
StructuredLog bool
|
|
||||||
WatchConfig bool
|
API struct {
|
||||||
YubikeyClientID string
|
Cert string
|
||||||
YubikeySecret string
|
Enabled bool
|
||||||
Frontend Frontend
|
Internals bool
|
||||||
LDAP LDAP
|
Key string
|
||||||
LDAPS LDAPS
|
Listen string
|
||||||
Groups []Group
|
SecretToken string
|
||||||
Users []User
|
TLS bool
|
||||||
ConfigFile string
|
}
|
||||||
AwsAccessKeyId string
|
|
||||||
AwsSecretAccessKey string
|
Behaviors struct {
|
||||||
AwsRegion string
|
IgnoreCapabilities bool
|
||||||
}
|
LimitFailedBinds bool
|
||||||
|
NumberOfFailedBinds int
|
||||||
|
PeriodOfFailedBinds time.Duration
|
||||||
|
BlockFailedBindsFor time.Duration
|
||||||
|
PruneSourceTableEvery time.Duration
|
||||||
|
PruneSourcesOlderThan time.Duration
|
||||||
|
LegacyVersion int
|
||||||
|
}
|
||||||
|
|
||||||
|
Capability struct {
|
||||||
|
Action string
|
||||||
|
Object string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAuthenticator authenticates a user via custom auth from a backend
|
||||||
|
UserAuthenticator func(user *User, pw string) error
|
||||||
|
User struct {
|
||||||
|
Name string
|
||||||
|
OtherGroups []int
|
||||||
|
PassSHA256 string
|
||||||
|
PassBcrypt string
|
||||||
|
PassAppSHA256 []string
|
||||||
|
PassAppBcrypt []string
|
||||||
|
PassAppCustom UserAuthenticator `toml:"-"`
|
||||||
|
PrimaryGroup int
|
||||||
|
Capabilities []Capability
|
||||||
|
SSHKeys []string
|
||||||
|
OTPSecret string
|
||||||
|
Yubikey string
|
||||||
|
Disabled bool
|
||||||
|
UnixID int // TODO: remove after deprecating UnixID on User and Group
|
||||||
|
UIDNumber int
|
||||||
|
Mail string
|
||||||
|
LoginShell string
|
||||||
|
GivenName string
|
||||||
|
SN string
|
||||||
|
Homedir string
|
||||||
|
CustomAttrs map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group struct {
|
||||||
|
Name string
|
||||||
|
UnixID int // TODO: remove after deprecating UnixID on User and Group
|
||||||
|
GIDNumber int
|
||||||
|
Capabilities []Capability
|
||||||
|
IncludeGroups []int
|
||||||
|
}
|
||||||
|
|
||||||
|
Tracing struct {
|
||||||
|
Enabled bool
|
||||||
|
GRPCEndpoint string
|
||||||
|
HTTPEndpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
Config struct {
|
||||||
|
API API
|
||||||
|
Backend Backend // Deprecated
|
||||||
|
Backends []Backend
|
||||||
|
Helper Helper
|
||||||
|
Behaviors Behaviors
|
||||||
|
Debug bool
|
||||||
|
Syslog bool
|
||||||
|
StructuredLog bool
|
||||||
|
WatchConfig bool
|
||||||
|
YubikeyClientID string
|
||||||
|
YubikeySecret string
|
||||||
|
Frontend Frontend
|
||||||
|
LDAP LDAP
|
||||||
|
LDAPS LDAPS
|
||||||
|
Groups []Group
|
||||||
|
Users []User
|
||||||
|
Tracing Tracing
|
||||||
|
ConfigFile string
|
||||||
|
AwsAccessKeyId string
|
||||||
|
AwsSecretAccessKey string
|
||||||
|
AwsRegion string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package frontend
|
|||||||
|
|
||||||
import (
|
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"
|
||||||
|
|||||||
+14
-156
@@ -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
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Info().Str("address", cfg.Listen).Msg("Starting HTTP server")
|
|
||||||
err := http.ListenAndServe(cfg.Listen, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("error starting HTTP server")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func register(mux *http.ServeMux, log zerolog.Logger, tls bool, listen string) {
|
|
||||||
fs := http.FileServer(http.FS(assets.Content))
|
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
log.Info().Str("path", r.URL.Path).Msg("Web")
|
|
||||||
if r.URL.Path != "/" {
|
|
||||||
log.Info().Msg("Web 404")
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fs.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
|
|
||||||
var url string
|
|
||||||
if tls {
|
|
||||||
url = fmt.Sprintf("https://%s/debug/vars", listen)
|
|
||||||
} else {
|
|
||||||
url = fmt.Sprintf("http://%s/debug/vars", listen)
|
|
||||||
}
|
|
||||||
c := &collector{
|
|
||||||
url: url,
|
|
||||||
names: map[string]string{
|
|
||||||
"proxy": "glauth_proxy",
|
|
||||||
"proxy_frontend": "glauth_proxy_frontend",
|
|
||||||
"proxy_backend": "glauth_proxy_backend",
|
|
||||||
},
|
|
||||||
helps: map[string]string{
|
|
||||||
"proxy": "General Metrics",
|
|
||||||
"proxy_frontend": "Frontend Metrics",
|
|
||||||
"proxy_backend": "Backend Metrics",
|
|
||||||
},
|
|
||||||
labelName: map[string]string{
|
|
||||||
"proxy": "metric",
|
|
||||||
"proxy_frontend": "metric",
|
|
||||||
"proxy_backend": "metric",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
prometheus.MustRegister(c)
|
|
||||||
mux.Handle("/metrics", promhttp.Handler())
|
|
||||||
}
|
|
||||||
|
|
||||||
// The code below liberally borrows from https://github.com/albertito/prometheus-expvar-exporter
|
|
||||||
// and is adapted (just a little) for our purpose.
|
|
||||||
|
|
||||||
type collector struct {
|
|
||||||
url string
|
|
||||||
names map[string]string
|
|
||||||
helps map[string]string
|
|
||||||
labelName map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
|
||||||
resp, err := http.Get(c.url)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error scraping %q: %v", c.url, err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
log.Info().Str("address", cfg.Listen).Msg("Starting HTTP server")
|
||||||
if err != nil {
|
monitoring.NewCollector(fmt.Sprintf("http://%s/debug/vars", cfg.Listen))
|
||||||
log.Printf("Error reading body of %q: %v", c.url, err)
|
|
||||||
return
|
if err := http.ListenAndServe(cfg.Listen, nil); err != nil {
|
||||||
|
log.Error().Err(err).Msg("error starting HTTP server")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace "\xNN" with "?" because the default parser doesn't handle them
|
|
||||||
// well.
|
|
||||||
re := regexp.MustCompile(`\\x..`)
|
|
||||||
body = re.ReplaceAllFunc(body, func(s []byte) []byte {
|
|
||||||
return []byte("?")
|
|
||||||
})
|
|
||||||
|
|
||||||
var vs map[string]interface{}
|
|
||||||
err = json.Unmarshal(body, &vs)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error unmarshalling json from %q: %v", c.url, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range vs {
|
|
||||||
name := strings.ReplaceAll(k, "/", "_")
|
|
||||||
if n, ok := c.names[k]; ok {
|
|
||||||
name = n
|
|
||||||
}
|
|
||||||
|
|
||||||
help := fmt.Sprintf("expvar %q", k)
|
|
||||||
if h, ok := c.helps[k]; ok {
|
|
||||||
help = h
|
|
||||||
}
|
|
||||||
|
|
||||||
lnames := []string{}
|
|
||||||
if ln, ok := c.labelName[k]; ok {
|
|
||||||
lnames = append(lnames, ln)
|
|
||||||
}
|
|
||||||
|
|
||||||
desc := prometheus.NewDesc(name, help, lnames, nil)
|
|
||||||
|
|
||||||
switch v := v.(type) {
|
|
||||||
case float64:
|
|
||||||
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue, v)
|
|
||||||
case bool:
|
|
||||||
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue,
|
|
||||||
valToFloat(v))
|
|
||||||
case map[string]interface{}:
|
|
||||||
// We only support explicitly written label names.
|
|
||||||
if len(lnames) != 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for lk, lv := range v {
|
|
||||||
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue,
|
|
||||||
valToFloat(lv), lk)
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
// Not supported by Prometheus.
|
|
||||||
continue
|
|
||||||
case []interface{}:
|
|
||||||
// Not supported by Prometheus.
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func valToFloat(v interface{}) float64 {
|
|
||||||
switch v := v.(type) {
|
|
||||||
case float64:
|
|
||||||
return v
|
|
||||||
case bool:
|
|
||||||
if v {
|
|
||||||
return 1.0
|
|
||||||
}
|
|
||||||
return 0.0
|
|
||||||
case string:
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
log.Printf("unexpected value type: %#v", v)
|
|
||||||
return 0.0
|
|
||||||
}
|
}
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
+108
-13
@@ -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)
|
||||||
@@ -255,9 +311,10 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
|
|||||||
}
|
}
|
||||||
|
|
||||||
ssr := ldap.ServerSearchResult{
|
ssr := ldap.ServerSearchResult{
|
||||||
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 8e7354c160
Submodule
+1
Submodule v2/pkg/plugins/glauth-pam added at cfe63488a5
Submodule
+1
Submodule v2/pkg/plugins/glauth-postgres added at 013ae4c4de
Submodule
+1
Submodule v2/pkg/plugins/glauth-sqlite added at f34a4dea13
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,9 +16,13 @@ type Option func(o *Options)
|
|||||||
|
|
||||||
// Options defines the available options for this package.
|
// Options defines the available options for this package.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Logger zerolog.Logger
|
Logger zerolog.Logger
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
Context context.Context
|
StartTLSConfig *tls.Config
|
||||||
|
LDAPSTLSConfig *tls.Config
|
||||||
|
Monitor monitoring.MonitorInterface
|
||||||
|
Tracer trace.Tracer
|
||||||
|
Context context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// newOptions initializes the available default options.
|
// newOptions initializes the available default options.
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+68
-12
@@ -1,30 +1,42 @@
|
|||||||
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) {
|
||||||
options := newOptions(opts...)
|
options := newOptions(opts...)
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -33,14 +33,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
|
"$CI_BUILD_DIR/bin/$(go env GOOS)$(go env GOARCH)/glauth" --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 &
|
"$CI_BUILD_DIR/bin/$(go env GOOS)$(go env GOARCH)/glauth" -c "$CI_BUILD_DIR/scripts/ci/test-config.cfg" &> /dev/null &
|
||||||
|
|
||||||
# Use this instead to see glauth logs while running
|
# 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"
|
||||||
|
|||||||
@@ -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
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
Package toml provides facilities for decoding and encoding TOML configuration
|
|
||||||
files via reflection. There is also support for delaying decoding with
|
|
||||||
the Primitive type, and querying the set of keys in a TOML document with the
|
|
||||||
MetaData type.
|
|
||||||
|
|
||||||
The specification implemented: https://github.com/toml-lang/toml
|
|
||||||
|
|
||||||
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
|
|
||||||
whether a file is a valid TOML document. It can also be used to print the
|
|
||||||
type of each key in a TOML document.
|
|
||||||
|
|
||||||
# Testing
|
|
||||||
|
|
||||||
There are two important types of tests used for this package. The first is
|
|
||||||
contained inside '*_test.go' files and uses the standard Go unit testing
|
|
||||||
framework. These tests are primarily devoted to holistically testing the
|
|
||||||
decoder and encoder.
|
|
||||||
|
|
||||||
The second type of testing is used to verify the implementation's adherence
|
|
||||||
to the TOML specification. These tests have been factored into their own
|
|
||||||
project: https://github.com/BurntSushi/toml-test
|
|
||||||
|
|
||||||
The reason the tests are in a separate project is so that they can be used by
|
|
||||||
any implementation of TOML. Namely, it is language agnostic.
|
|
||||||
*/
|
|
||||||
package toml
|
|
||||||
@@ -1,605 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tomlEncodeError struct{ error }
|
|
||||||
|
|
||||||
var (
|
|
||||||
errArrayMixedElementTypes = errors.New(
|
|
||||||
"toml: cannot encode array with mixed element types")
|
|
||||||
errArrayNilElement = errors.New(
|
|
||||||
"toml: cannot encode array with nil element")
|
|
||||||
errNonString = errors.New(
|
|
||||||
"toml: cannot encode a map with non-string key type")
|
|
||||||
errAnonNonStruct = errors.New(
|
|
||||||
"toml: cannot encode an anonymous field that is not a struct")
|
|
||||||
errArrayNoTable = errors.New(
|
|
||||||
"toml: TOML array element cannot contain a table")
|
|
||||||
errNoKey = errors.New(
|
|
||||||
"toml: top-level values must be Go maps or structs")
|
|
||||||
errAnything = errors.New("") // used in testing
|
|
||||||
)
|
|
||||||
|
|
||||||
var quotedReplacer = strings.NewReplacer(
|
|
||||||
"\t", "\\t",
|
|
||||||
"\n", "\\n",
|
|
||||||
"\r", "\\r",
|
|
||||||
"\"", "\\\"",
|
|
||||||
"\\", "\\\\",
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// tagComment generates a descriptive comment above an object
|
|
||||||
tagComment = "comment"
|
|
||||||
|
|
||||||
// tagCommented will generate the corresponding comment file, but it will be commented out
|
|
||||||
tagCommented = "commented"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Encoder controls the encoding of Go values to a TOML document to some
|
|
||||||
// io.Writer.
|
|
||||||
//
|
|
||||||
// The indentation level can be controlled with the Indent field.
|
|
||||||
type Encoder struct {
|
|
||||||
// A single indentation level. By default it is two spaces.
|
|
||||||
Indent string
|
|
||||||
// indicates in the line should be generated commented out
|
|
||||||
commented bool
|
|
||||||
// hasWritten is whether we have written any output to w yet.
|
|
||||||
hasWritten bool
|
|
||||||
w *bufio.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
|
|
||||||
// given. By default, a single indentation level is 2 spaces.
|
|
||||||
func NewEncoder(w io.Writer) *Encoder {
|
|
||||||
return &Encoder{
|
|
||||||
w: bufio.NewWriter(w),
|
|
||||||
Indent: " ",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode writes a TOML representation of the Go value to the underlying
|
|
||||||
// io.Writer. If the value given cannot be encoded to a valid TOML document,
|
|
||||||
// then an error is returned.
|
|
||||||
//
|
|
||||||
// The mapping between Go values and TOML values should be precisely the same
|
|
||||||
// as for the Decode* functions. Similarly, the TextMarshaler interface is
|
|
||||||
// supported by encoding the resulting bytes as strings. (If you want to write
|
|
||||||
// arbitrary binary data then you will need to use something like base64 since
|
|
||||||
// TOML does not have any binary types.)
|
|
||||||
//
|
|
||||||
// When encoding TOML hashes (i.e., Go maps or structs), keys without any
|
|
||||||
// sub-hashes are encoded first.
|
|
||||||
//
|
|
||||||
// If a Go map is encoded, then its keys are sorted alphabetically for
|
|
||||||
// deterministic output. More control over this behavior may be provided if
|
|
||||||
// there is demand for it.
|
|
||||||
//
|
|
||||||
// Encoding Go values without a corresponding TOML representation---like map
|
|
||||||
// types with non-string keys---will cause an error to be returned. Similarly
|
|
||||||
// for mixed arrays/slices, arrays/slices with nil elements, embedded
|
|
||||||
// non-struct types and nested slices containing maps or structs.
|
|
||||||
// (e.g., [][]map[string]string is not allowed but []map[string]string is OK
|
|
||||||
// and so is []map[string][]string.)
|
|
||||||
func (enc *Encoder) Encode(v interface{}) error {
|
|
||||||
rv := eindirect(reflect.ValueOf(v))
|
|
||||||
if err := enc.safeEncode(Key([]string{}), rv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return enc.w.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
if terr, ok := r.(tomlEncodeError); ok {
|
|
||||||
err = terr.error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
panic(r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
enc.encode(key, rv)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
|
||||||
// Special case. Time needs to be in ISO8601 format.
|
|
||||||
// Special case. If we can marshal the type to text, then we used that.
|
|
||||||
// Basically, this prevents the encoder for handling these types as
|
|
||||||
// generic structs (or whatever the underlying type of a TextMarshaler is).
|
|
||||||
switch rv.Interface().(type) {
|
|
||||||
case time.Time, TextMarshaler, time.Duration:
|
|
||||||
enc.keyEqElement(key, rv)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
k := rv.Kind()
|
|
||||||
switch k {
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
|
||||||
reflect.Int64,
|
|
||||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
|
||||||
reflect.Uint64,
|
|
||||||
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
|
|
||||||
enc.keyEqElement(key, rv)
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
|
|
||||||
enc.eArrayOfTables(key, rv)
|
|
||||||
} else {
|
|
||||||
enc.keyEqElement(key, rv)
|
|
||||||
}
|
|
||||||
case reflect.Interface:
|
|
||||||
if rv.IsNil() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
enc.encode(key, rv.Elem())
|
|
||||||
case reflect.Map:
|
|
||||||
if rv.IsNil() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
enc.eTable(key, rv)
|
|
||||||
case reflect.Ptr:
|
|
||||||
if rv.IsNil() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
enc.encode(key, rv.Elem())
|
|
||||||
case reflect.Struct:
|
|
||||||
enc.eTable(key, rv)
|
|
||||||
default:
|
|
||||||
panic(e("unsupported type for key '%s': %s", key, k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eElement encodes any value that can be an array element (primitives and
|
|
||||||
// arrays).
|
|
||||||
func (enc *Encoder) eElement(rv reflect.Value) {
|
|
||||||
switch v := rv.Interface().(type) {
|
|
||||||
case time.Time:
|
|
||||||
// Special case time.Time as a primitive. Has to come before
|
|
||||||
// TextMarshaler below because time.Time implements
|
|
||||||
// encoding.TextMarshaler, but we need to always use UTC.
|
|
||||||
enc.wf(v.UTC().Format("2006-01-02T15:04:05Z"))
|
|
||||||
return
|
|
||||||
case TextMarshaler:
|
|
||||||
// Special case. Use text marshaler if it's available for this value.
|
|
||||||
if s, err := v.MarshalText(); err != nil {
|
|
||||||
encPanic(err)
|
|
||||||
} else {
|
|
||||||
enc.writeQuoted(string(s))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case time.Duration:
|
|
||||||
s := v.String()
|
|
||||||
enc.writeQuoted(s)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
enc.wf(strconv.FormatBool(rv.Bool()))
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
|
||||||
reflect.Int64:
|
|
||||||
enc.wf(strconv.FormatInt(rv.Int(), 10))
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
|
||||||
reflect.Uint32, reflect.Uint64:
|
|
||||||
enc.wf(strconv.FormatUint(rv.Uint(), 10))
|
|
||||||
case reflect.Float32:
|
|
||||||
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32)))
|
|
||||||
case reflect.Float64:
|
|
||||||
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64)))
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
enc.eArrayOrSliceElement(rv)
|
|
||||||
case reflect.Interface:
|
|
||||||
enc.eElement(rv.Elem())
|
|
||||||
case reflect.String:
|
|
||||||
enc.writeQuoted(rv.String())
|
|
||||||
default:
|
|
||||||
panic(e("unexpected primitive type: %s", rv.Kind()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// By the TOML spec, all floats must have a decimal with at least one
|
|
||||||
// number on either side.
|
|
||||||
func floatAddDecimal(fstr string) string {
|
|
||||||
if !strings.Contains(fstr, ".") {
|
|
||||||
return fstr + ".0"
|
|
||||||
}
|
|
||||||
return fstr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) writeQuoted(s string) {
|
|
||||||
enc.wf("\"%s\"", quotedReplacer.Replace(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
|
|
||||||
length := rv.Len()
|
|
||||||
enc.wf("[")
|
|
||||||
for i := 0; i < length; i++ {
|
|
||||||
elem := rv.Index(i)
|
|
||||||
enc.eElement(elem)
|
|
||||||
if i != length-1 {
|
|
||||||
enc.wf(", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enc.wf("]")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
|
||||||
if len(key) == 0 {
|
|
||||||
encPanic(errNoKey)
|
|
||||||
}
|
|
||||||
for i := 0; i < rv.Len(); i++ {
|
|
||||||
trv := rv.Index(i)
|
|
||||||
if isNil(trv) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
panicIfInvalidKey(key)
|
|
||||||
enc.newline()
|
|
||||||
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
|
|
||||||
enc.newline()
|
|
||||||
enc.eMapOrStruct(key, trv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
|
||||||
panicIfInvalidKey(key)
|
|
||||||
if len(key) == 1 {
|
|
||||||
// Output an extra newline between top-level tables.
|
|
||||||
// (The newline isn't written if nothing else has been written though.)
|
|
||||||
enc.newline()
|
|
||||||
}
|
|
||||||
format := "%s[%s]"
|
|
||||||
if enc.commented {
|
|
||||||
format = "%s#[%s]"
|
|
||||||
}
|
|
||||||
if len(key) > 0 {
|
|
||||||
enc.wf(format, enc.indentStr(key), key.maybeQuotedAll())
|
|
||||||
enc.newline()
|
|
||||||
}
|
|
||||||
enc.eMapOrStruct(key, rv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
|
|
||||||
switch rv := eindirect(rv); rv.Kind() {
|
|
||||||
case reflect.Map:
|
|
||||||
enc.eMap(key, rv)
|
|
||||||
case reflect.Struct:
|
|
||||||
enc.eStruct(key, rv)
|
|
||||||
default:
|
|
||||||
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) eMap(key Key, rv reflect.Value) {
|
|
||||||
rt := rv.Type()
|
|
||||||
if rt.Key().Kind() != reflect.String {
|
|
||||||
encPanic(errNonString)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort keys so that we have deterministic output. And write keys directly
|
|
||||||
// underneath this key first, before writing sub-structs or sub-maps.
|
|
||||||
var mapKeysDirect, mapKeysSub []string
|
|
||||||
for _, mapKey := range rv.MapKeys() {
|
|
||||||
k := mapKey.String()
|
|
||||||
if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
|
|
||||||
mapKeysSub = append(mapKeysSub, k)
|
|
||||||
} else {
|
|
||||||
mapKeysDirect = append(mapKeysDirect, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var writeMapKeys = func(mapKeys []string) {
|
|
||||||
sort.Strings(mapKeys)
|
|
||||||
for _, mapKey := range mapKeys {
|
|
||||||
mrv := rv.MapIndex(reflect.ValueOf(mapKey))
|
|
||||||
if isNil(mrv) {
|
|
||||||
// Don't write anything for nil fields.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
enc.encode(key.add(mapKey), mrv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeMapKeys(mapKeysDirect)
|
|
||||||
writeMapKeys(mapKeysSub)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
|
|
||||||
// Write keys for fields directly under this key first, because if we write
|
|
||||||
// a field that creates a new table, then all keys under it will be in that
|
|
||||||
// table (not the one we're writing here).
|
|
||||||
rt := rv.Type()
|
|
||||||
var fieldsDirect, fieldsSub [][]int
|
|
||||||
var addFields func(rt reflect.Type, rv reflect.Value, start []int)
|
|
||||||
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
|
||||||
for i := 0; i < rt.NumField(); i++ {
|
|
||||||
f := rt.Field(i)
|
|
||||||
// skip unexported fields
|
|
||||||
if f.PkgPath != "" && !f.Anonymous {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
frv := rv.Field(i)
|
|
||||||
if f.Anonymous {
|
|
||||||
t := f.Type
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
// Treat anonymous struct fields with
|
|
||||||
// tag names as though they are not
|
|
||||||
// anonymous, like encoding/json does.
|
|
||||||
if getOptions(f.Tag).name == "" {
|
|
||||||
addFields(t, frv, f.Index)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case reflect.Ptr:
|
|
||||||
if t.Elem().Kind() == reflect.Struct &&
|
|
||||||
getOptions(f.Tag).name == "" {
|
|
||||||
if !frv.IsNil() {
|
|
||||||
addFields(t.Elem(), frv.Elem(), f.Index)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Fall through to the normal field encoding logic below
|
|
||||||
// for non-struct anonymous fields.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if typeIsHash(tomlTypeOfGo(frv)) {
|
|
||||||
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
|
||||||
} else {
|
|
||||||
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addFields(rt, rv, nil)
|
|
||||||
|
|
||||||
var writeFields = func(fields [][]int) {
|
|
||||||
for _, fieldIndex := range fields {
|
|
||||||
sft := rt.FieldByIndex(fieldIndex)
|
|
||||||
sf := rv.FieldByIndex(fieldIndex)
|
|
||||||
if isNil(sf) {
|
|
||||||
// Don't write anything for nil fields.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := getOptions(sft.Tag)
|
|
||||||
if opts.skip {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
keyName := sft.Name
|
|
||||||
if opts.name != "" {
|
|
||||||
keyName = opts.name
|
|
||||||
}
|
|
||||||
if opts.omitempty && isEmpty(sf) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if opts.omitzero && isZero(sf) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if s := sft.Tag.Get(tagCommented); s == "true" {
|
|
||||||
enc.commented = true
|
|
||||||
enc.addComment(key.add(keyName), sft.Tag)
|
|
||||||
enc.encode(key.add(keyName), sf)
|
|
||||||
enc.commented = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
enc.addComment(key.add(keyName), sft.Tag)
|
|
||||||
enc.encode(key.add(keyName), sf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeFields(fieldsDirect)
|
|
||||||
writeFields(fieldsSub)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) addComment(key Key, tag reflect.StructTag) {
|
|
||||||
if s := tag.Get(tagComment); s != "" {
|
|
||||||
enc.wf("%s# %s\n", enc.indentStr(key), s)
|
|
||||||
enc.hasWritten = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tomlTypeName returns the TOML type name of the Go value's type. It is
|
|
||||||
// used to determine whether the types of array elements are mixed (which is
|
|
||||||
// forbidden). If the Go value is nil, then it is illegal for it to be an array
|
|
||||||
// element, and valueIsNil is returned as true.
|
|
||||||
|
|
||||||
// Returns the TOML type of a Go value. The type may be `nil`, which means
|
|
||||||
// no concrete TOML type could be found.
|
|
||||||
func tomlTypeOfGo(rv reflect.Value) tomlType {
|
|
||||||
if isNil(rv) || !rv.IsValid() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return tomlBool
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
|
||||||
reflect.Int64,
|
|
||||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
|
||||||
reflect.Uint64:
|
|
||||||
return tomlInteger
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return tomlFloat
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
if typeEqual(tomlHash, tomlArrayType(rv)) {
|
|
||||||
return tomlArrayHash
|
|
||||||
}
|
|
||||||
return tomlArray
|
|
||||||
case reflect.Ptr, reflect.Interface:
|
|
||||||
return tomlTypeOfGo(rv.Elem())
|
|
||||||
case reflect.String:
|
|
||||||
return tomlString
|
|
||||||
case reflect.Map:
|
|
||||||
return tomlHash
|
|
||||||
case reflect.Struct:
|
|
||||||
switch rv.Interface().(type) {
|
|
||||||
case time.Time:
|
|
||||||
return tomlDatetime
|
|
||||||
case TextMarshaler:
|
|
||||||
return tomlString
|
|
||||||
default:
|
|
||||||
return tomlHash
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("unexpected reflect.Kind: " + rv.Kind().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tomlArrayType returns the element type of a TOML array. The type returned
|
|
||||||
// may be nil if it cannot be determined (e.g., a nil slice or a zero length
|
|
||||||
// slize). This function may also panic if it finds a type that cannot be
|
|
||||||
// expressed in TOML (such as nil elements, heterogeneous arrays or directly
|
|
||||||
// nested arrays of tables).
|
|
||||||
func tomlArrayType(rv reflect.Value) tomlType {
|
|
||||||
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
firstType := tomlTypeOfGo(rv.Index(0))
|
|
||||||
if firstType == nil {
|
|
||||||
encPanic(errArrayNilElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
rvlen := rv.Len()
|
|
||||||
for i := 1; i < rvlen; i++ {
|
|
||||||
elem := rv.Index(i)
|
|
||||||
switch elemType := tomlTypeOfGo(elem); {
|
|
||||||
case elemType == nil:
|
|
||||||
encPanic(errArrayNilElement)
|
|
||||||
case !typeEqual(firstType, elemType):
|
|
||||||
encPanic(errArrayMixedElementTypes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If we have a nested array, then we must make sure that the nested
|
|
||||||
// array contains ONLY primitives.
|
|
||||||
// This checks arbitrarily nested arrays.
|
|
||||||
if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) {
|
|
||||||
nest := tomlArrayType(eindirect(rv.Index(0)))
|
|
||||||
if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
|
|
||||||
encPanic(errArrayNoTable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return firstType
|
|
||||||
}
|
|
||||||
|
|
||||||
type tagOptions struct {
|
|
||||||
skip bool // "-"
|
|
||||||
name string
|
|
||||||
omitempty bool
|
|
||||||
omitzero bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOptions(tag reflect.StructTag) tagOptions {
|
|
||||||
t := tag.Get("toml")
|
|
||||||
if t == "-" {
|
|
||||||
return tagOptions{skip: true}
|
|
||||||
}
|
|
||||||
var opts tagOptions
|
|
||||||
parts := strings.Split(t, ",")
|
|
||||||
opts.name = parts[0]
|
|
||||||
for _, s := range parts[1:] {
|
|
||||||
switch s {
|
|
||||||
case "omitempty":
|
|
||||||
opts.omitempty = true
|
|
||||||
case "omitzero":
|
|
||||||
opts.omitzero = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return opts
|
|
||||||
}
|
|
||||||
|
|
||||||
func isZero(rv reflect.Value) bool {
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return rv.Int() == 0
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
return rv.Uint() == 0
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return rv.Float() == 0.0
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isEmpty(rv reflect.Value) bool {
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
|
||||||
return rv.Len() == 0
|
|
||||||
case reflect.Bool:
|
|
||||||
return !rv.Bool()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) newline() {
|
|
||||||
if enc.hasWritten {
|
|
||||||
enc.wf("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
|
|
||||||
if len(key) == 0 {
|
|
||||||
encPanic(errNoKey)
|
|
||||||
}
|
|
||||||
panicIfInvalidKey(key)
|
|
||||||
format := "%s%s = "
|
|
||||||
if enc.commented {
|
|
||||||
format = "%s#%s = "
|
|
||||||
}
|
|
||||||
enc.wf(format, enc.indentStr(key), key.maybeQuoted(len(key)-1))
|
|
||||||
enc.eElement(val)
|
|
||||||
enc.newline()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) wf(format string, v ...interface{}) {
|
|
||||||
if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
|
|
||||||
encPanic(err)
|
|
||||||
}
|
|
||||||
enc.hasWritten = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) indentStr(key Key) string {
|
|
||||||
return strings.Repeat(enc.Indent, len(key)-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encPanic(err error) {
|
|
||||||
panic(tomlEncodeError{err})
|
|
||||||
}
|
|
||||||
|
|
||||||
func eindirect(v reflect.Value) reflect.Value {
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Ptr, reflect.Interface:
|
|
||||||
return eindirect(v.Elem())
|
|
||||||
default:
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNil(rv reflect.Value) bool {
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
|
||||||
return rv.IsNil()
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func panicIfInvalidKey(key Key) {
|
|
||||||
for _, k := range key {
|
|
||||||
if len(k) == 0 {
|
|
||||||
encPanic(e("Key '%s' is not a valid table name. Key names "+
|
|
||||||
"cannot be empty.", key.maybeQuotedAll()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidKeyName(s string) bool {
|
|
||||||
return len(s) != 0
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user