Add OpenLDAP Secret Plugin (#8360)
* Add openldap secret plugin * go mod vendor * Revert to go-ldap 3.1.3 * go mod vendor
This commit is contained in:
parent
9dd18d8487
commit
dd9f25a118
|
@ -380,6 +380,7 @@ func TestPredict_Plugins(t *testing.T) {
|
||||||
"oci",
|
"oci",
|
||||||
"oidc",
|
"oidc",
|
||||||
"okta",
|
"okta",
|
||||||
|
"openldap",
|
||||||
"pcf", // Deprecated.
|
"pcf", // Deprecated.
|
||||||
"pki",
|
"pki",
|
||||||
"postgresql",
|
"postgresql",
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -87,6 +87,7 @@ require (
|
||||||
github.com/hashicorp/vault-plugin-secrets-gcpkms v0.5.2-0.20190814210149-315cdbf5de6e
|
github.com/hashicorp/vault-plugin-secrets-gcpkms v0.5.2-0.20190814210149-315cdbf5de6e
|
||||||
github.com/hashicorp/vault-plugin-secrets-kv v0.5.2-0.20191017213228-e8cf7060a4d0
|
github.com/hashicorp/vault-plugin-secrets-kv v0.5.2-0.20191017213228-e8cf7060a4d0
|
||||||
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.0.0-20200124190647-0026e6bed4fb
|
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.0.0-20200124190647-0026e6bed4fb
|
||||||
|
github.com/hashicorp/vault-plugin-secrets-openldap v0.0.0-20200215165936-237ad8919d2c
|
||||||
github.com/hashicorp/vault/api v1.0.5-0.20200214222743-c39f5634b39f
|
github.com/hashicorp/vault/api v1.0.5-0.20200214222743-c39f5634b39f
|
||||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200214222719-7a3b716487a5
|
github.com/hashicorp/vault/sdk v0.1.14-0.20200214222719-7a3b716487a5
|
||||||
github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4
|
github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -436,6 +436,8 @@ github.com/hashicorp/vault-plugin-secrets-kv v0.5.2-0.20191017213228-e8cf7060a4d
|
||||||
github.com/hashicorp/vault-plugin-secrets-kv v0.5.2-0.20191017213228-e8cf7060a4d0/go.mod h1:H0VKQagsJoK9o2qpULMgbspuWVnFe3G4S/K7f0Dr8qY=
|
github.com/hashicorp/vault-plugin-secrets-kv v0.5.2-0.20191017213228-e8cf7060a4d0/go.mod h1:H0VKQagsJoK9o2qpULMgbspuWVnFe3G4S/K7f0Dr8qY=
|
||||||
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.0.0-20200124190647-0026e6bed4fb h1:mKgSR5EimaRgFzdV1ld5+k1l+zzpJ3x7T+eTHNxxKuo=
|
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.0.0-20200124190647-0026e6bed4fb h1:mKgSR5EimaRgFzdV1ld5+k1l+zzpJ3x7T+eTHNxxKuo=
|
||||||
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.0.0-20200124190647-0026e6bed4fb/go.mod h1:O+csRy1tVsA/LEvBUgRNM3GUml6TFnVBld+HRciLAdg=
|
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.0.0-20200124190647-0026e6bed4fb/go.mod h1:O+csRy1tVsA/LEvBUgRNM3GUml6TFnVBld+HRciLAdg=
|
||||||
|
github.com/hashicorp/vault-plugin-secrets-openldap v0.0.0-20200215165936-237ad8919d2c h1:QkOEpku9tJjgE9Y4w5FfzaUIrO1QvnJvEt0SPx58zAE=
|
||||||
|
github.com/hashicorp/vault-plugin-secrets-openldap v0.0.0-20200215165936-237ad8919d2c/go.mod h1:oOUQK0t8vnW7qHXMlg7jcCi8z7THVkeqpUl00nOSvZA=
|
||||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
||||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
|
@ -821,6 +823,7 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
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=
|
||||||
|
|
|
@ -47,6 +47,7 @@ import (
|
||||||
logicalMssql "github.com/hashicorp/vault/builtin/logical/mssql"
|
logicalMssql "github.com/hashicorp/vault/builtin/logical/mssql"
|
||||||
logicalMysql "github.com/hashicorp/vault/builtin/logical/mysql"
|
logicalMysql "github.com/hashicorp/vault/builtin/logical/mysql"
|
||||||
logicalNomad "github.com/hashicorp/vault/builtin/logical/nomad"
|
logicalNomad "github.com/hashicorp/vault/builtin/logical/nomad"
|
||||||
|
logicalOpenLDAP "github.com/hashicorp/vault-plugin-secrets-openldap"
|
||||||
logicalPki "github.com/hashicorp/vault/builtin/logical/pki"
|
logicalPki "github.com/hashicorp/vault/builtin/logical/pki"
|
||||||
logicalPostgres "github.com/hashicorp/vault/builtin/logical/postgresql"
|
logicalPostgres "github.com/hashicorp/vault/builtin/logical/postgresql"
|
||||||
logicalRabbit "github.com/hashicorp/vault/builtin/logical/rabbitmq"
|
logicalRabbit "github.com/hashicorp/vault/builtin/logical/rabbitmq"
|
||||||
|
@ -122,6 +123,7 @@ func newRegistry() *registry {
|
||||||
"mssql": logicalMssql.Factory, // Deprecated
|
"mssql": logicalMssql.Factory, // Deprecated
|
||||||
"mysql": logicalMysql.Factory, // Deprecated
|
"mysql": logicalMysql.Factory, // Deprecated
|
||||||
"nomad": logicalNomad.Factory,
|
"nomad": logicalNomad.Factory,
|
||||||
|
"openldap": logicalOpenLDAP.Factory,
|
||||||
"pki": logicalPki.Factory,
|
"pki": logicalPki.Factory,
|
||||||
"postgresql": logicalPostgres.Factory, // Deprecated
|
"postgresql": logicalPostgres.Factory, // Deprecated
|
||||||
"rabbitmq": logicalRabbit.Factory,
|
"rabbitmq": logicalRabbit.Factory,
|
||||||
|
|
|
@ -59,6 +59,7 @@ vault secrets enable mongodb
|
||||||
vault secrets enable mssql
|
vault secrets enable mssql
|
||||||
vault secrets enable mysql
|
vault secrets enable mysql
|
||||||
vault secrets enable nomad
|
vault secrets enable nomad
|
||||||
|
vault secrets enable openldap
|
||||||
vault secrets enable pki
|
vault secrets enable pki
|
||||||
vault secrets enable postgresql
|
vault secrets enable postgresql
|
||||||
vault secrets enable rabbitmq
|
vault secrets enable rabbitmq
|
||||||
|
|
85
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/.gitignore
generated
vendored
Normal file
85
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
.cover
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
# Other dirs
|
||||||
|
/bin/
|
||||||
|
/pkg/
|
||||||
|
|
||||||
|
# Vault-specific
|
||||||
|
example.hcl
|
||||||
|
example.vault.d
|
||||||
|
|
||||||
|
# Ruby
|
||||||
|
website/vendor
|
||||||
|
website/.bundle
|
||||||
|
website/build
|
||||||
|
|
||||||
|
# Vagrant
|
||||||
|
.vagrant/
|
||||||
|
Vagrantfile
|
||||||
|
|
||||||
|
# Configs
|
||||||
|
*.hcl
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
dist/*
|
||||||
|
|
||||||
|
tags
|
||||||
|
|
||||||
|
# Editor backups
|
||||||
|
*~
|
||||||
|
*.sw[a-z]
|
||||||
|
|
||||||
|
# IntelliJ IDEA project files
|
||||||
|
.idea
|
||||||
|
*.ipr
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# compiled output
|
||||||
|
ui/dist
|
||||||
|
ui/tmp
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
ui/node_modules
|
||||||
|
ui/bower_components
|
||||||
|
|
||||||
|
# misc
|
||||||
|
ui/.DS_Store
|
||||||
|
ui/.sass-cache
|
||||||
|
ui/connect.lock
|
||||||
|
ui/coverage/*
|
||||||
|
ui/libpeerconnection.log
|
||||||
|
ui/npm-debug.log
|
||||||
|
ui/testem.log
|
||||||
|
tmp/
|
||||||
|
|
||||||
|
scripts/custom.sh
|
||||||
|
|
||||||
|
# binary
|
||||||
|
bin/vault-plugin-auth-openldap
|
373
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/LICENSE
generated
vendored
Normal file
373
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
51
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/Makefile
generated
vendored
Normal file
51
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
TOOL?=vault-plugin-secrets-openldap
|
||||||
|
TEST?=$$(go list ./... | grep -v /vendor/ | grep -v teamcity)
|
||||||
|
VETARGS?=-asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr
|
||||||
|
EXTERNAL_TOOLS=\
|
||||||
|
github.com/mitchellh/gox \
|
||||||
|
github.com/golang/dep/cmd/dep
|
||||||
|
BUILD_TAGS?=${TOOL}
|
||||||
|
GOFMT_FILES?=$$(find . -name '*.go' | grep -v vendor)
|
||||||
|
|
||||||
|
# bin generates the releaseable binaries for this plugin
|
||||||
|
bin: fmtcheck generate
|
||||||
|
@CGO_ENABLED=0 BUILD_TAGS='$(BUILD_TAGS)' sh -c "'$(CURDIR)/scripts/build.sh'"
|
||||||
|
|
||||||
|
default: dev
|
||||||
|
|
||||||
|
# dev creates binaries for testing Vault locally. These are put
|
||||||
|
# into ./bin/ as well as $GOPATH/bin.
|
||||||
|
dev: fmtcheck generate
|
||||||
|
@CGO_ENABLED=0 BUILD_TAGS='$(BUILD_TAGS)' VAULT_DEV_BUILD=1 sh -c "'$(CURDIR)/scripts/build.sh'"
|
||||||
|
|
||||||
|
# test runs the unit tests and vets the code
|
||||||
|
test: fmtcheck generate
|
||||||
|
CGO_ENABLED=0 VAULT_TOKEN= VAULT_ACC= go test -v -tags='$(BUILD_TAGS)' $(TEST) $(TESTARGS) -count=1 -timeout=20m -parallel=4
|
||||||
|
|
||||||
|
testcompile: fmtcheck generate
|
||||||
|
@for pkg in $(TEST) ; do \
|
||||||
|
go test -v -c -tags='$(BUILD_TAGS)' $$pkg -parallel=4 ; \
|
||||||
|
done
|
||||||
|
|
||||||
|
# generate runs `go generate` to build the dynamically generated
|
||||||
|
# source files.
|
||||||
|
generate:
|
||||||
|
go generate $(go list ./... | grep -v /vendor/)
|
||||||
|
|
||||||
|
# bootstrap the build by downloading additional tools
|
||||||
|
bootstrap:
|
||||||
|
@for tool in $(EXTERNAL_TOOLS) ; do \
|
||||||
|
echo "Installing/Updating $$tool" ; \
|
||||||
|
go get -u $$tool; \
|
||||||
|
done
|
||||||
|
|
||||||
|
fmtcheck:
|
||||||
|
@sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'"
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
gofmt -w $(GOFMT_FILES)
|
||||||
|
|
||||||
|
proto:
|
||||||
|
protoc *.proto --go_out=plugins=grpc:.
|
||||||
|
|
||||||
|
.PHONY: bin default generate test vet bootstrap fmt fmtcheck
|
34
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/README.md
generated
vendored
Normal file
34
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/README.md
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# Vault Plugin: OpenLDAP Secrets Backend
|
||||||
|
|
||||||
|
This is a standalone backend plugin for use with [Hashicorp Vault](https://www.github.com/hashicorp/vault).
|
||||||
|
This plugin provides OpenLDAP functionality to Vault.
|
||||||
|
|
||||||
|
**Please note**: We take Vault's security and our users' trust very seriously. If you believe you have found a security issue in Vault, _please responsibly disclose_ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com).
|
||||||
|
|
||||||
|
## Quick Links
|
||||||
|
- Vault Website: https://www.vaultproject.io
|
||||||
|
- OpenLDAP Docs: https://www.vaultproject.io/docs/secrets/openldap/index.html
|
||||||
|
- Main Project Github: https://www.github.com/hashicorp/vault
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
This is a [Vault plugin](https://www.vaultproject.io/docs/internals/plugins.html)
|
||||||
|
and is meant to work with Vault. This guide assumes you have already installed Vault
|
||||||
|
and have a basic understanding of how Vault works.
|
||||||
|
|
||||||
|
Otherwise, first read this guide on how to [get started with Vault](https://www.vaultproject.io/intro/getting-started/install.html).
|
||||||
|
|
||||||
|
To learn specifically about how plugins work, see documentation on [Vault plugins](https://www.vaultproject.io/docs/internals/plugins.html).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Please see [documentation for the plugin](https://www.vaultproject.io/docs/secrets/openldap/index.html)
|
||||||
|
on the Vault website.
|
||||||
|
|
||||||
|
This plugin is currently built into Vault and by default is accessed
|
||||||
|
at `openldap`. To enable this in a running Vault server:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ vault secrets enable openldap
|
||||||
|
Success! Enabled the openldap secrets engine at: openldap/
|
||||||
|
```
|
116
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/backend.go
generated
vendored
Normal file
116
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/backend.go
generated
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package openldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/locksutil"
|
||||||
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
|
"github.com/hashicorp/vault/sdk/queue"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
|
||||||
|
ldapClient := NewClient()
|
||||||
|
b := Backend(ldapClient)
|
||||||
|
if err := b.Setup(ctx, conf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Backend(client ldapClient) *backend {
|
||||||
|
var b backend
|
||||||
|
b.Backend = &framework.Backend{
|
||||||
|
Help: strings.TrimSpace(backendHelp),
|
||||||
|
|
||||||
|
PathsSpecial: &logical.Paths{
|
||||||
|
LocalStorage: []string{
|
||||||
|
framework.WALPrefix,
|
||||||
|
},
|
||||||
|
SealWrapStorage: []string{
|
||||||
|
"config",
|
||||||
|
"static-role/*",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Paths: framework.PathAppend(
|
||||||
|
b.pathListRoles(),
|
||||||
|
b.pathRoles(),
|
||||||
|
b.pathCredsCreate(),
|
||||||
|
b.pathRotateCredentials(),
|
||||||
|
b.pathConfig(),
|
||||||
|
),
|
||||||
|
InitializeFunc: b.initialize,
|
||||||
|
|
||||||
|
Secrets: []*framework.Secret{},
|
||||||
|
Clean: b.clean,
|
||||||
|
BackendType: logical.TypeLogical,
|
||||||
|
}
|
||||||
|
b.client = client
|
||||||
|
b.roleLocks = locksutil.CreateLocks()
|
||||||
|
b.credRotationQueue = queue.New()
|
||||||
|
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) initialize(ctx context.Context, initRequest *logical.InitializationRequest) error {
|
||||||
|
// Create a context with a cancel method for processing any WAL entries and
|
||||||
|
// populating the queue
|
||||||
|
ictx, cancel := context.WithCancel(context.Background())
|
||||||
|
b.cancelQueue = cancel
|
||||||
|
|
||||||
|
// Load queue and kickoff new periodic ticker
|
||||||
|
go b.initQueue(ictx, initRequest)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) clean(ctx context.Context) {
|
||||||
|
b.invalidateQueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalidateQueue cancels any background queue loading and destroys the queue.
|
||||||
|
func (b *backend) invalidateQueue() {
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
|
||||||
|
if b.cancelQueue != nil {
|
||||||
|
b.cancelQueue()
|
||||||
|
}
|
||||||
|
b.credRotationQueue = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type backend struct {
|
||||||
|
*framework.Backend
|
||||||
|
sync.RWMutex
|
||||||
|
// CredRotationQueue is an in-memory priority queue used to track Static Roles
|
||||||
|
// that require periodic rotation. Backends will have a PriorityQueue
|
||||||
|
// initialized on setup, but only backends that are mounted by a primary
|
||||||
|
// server or mounted as a local mount will perform the rotations.
|
||||||
|
//
|
||||||
|
// cancelQueue is used to remove the priority queue and terminate the
|
||||||
|
// background ticker.
|
||||||
|
credRotationQueue *queue.PriorityQueue
|
||||||
|
cancelQueue context.CancelFunc
|
||||||
|
|
||||||
|
// roleLocks is used to lock modifications to roles in the queue, to ensure
|
||||||
|
// concurrent requests are not modifying the same role and possibly causing
|
||||||
|
// issues with the priority queue.
|
||||||
|
roleLocks []*locksutil.LockEntry
|
||||||
|
client ldapClient
|
||||||
|
}
|
||||||
|
|
||||||
|
const backendHelp = `
|
||||||
|
The OpenLDAP backend supports managing existing LDAP entry passwords by providing:
|
||||||
|
|
||||||
|
* end points to add entries
|
||||||
|
* manual rotation of entry passwords
|
||||||
|
* auto rotation of entry passwords
|
||||||
|
|
||||||
|
The OpenLDAP secret engine is limited to OpenLDAP and does not support any other
|
||||||
|
implementations of LDAP.
|
||||||
|
|
||||||
|
After mounting this secret backend, configure it using the "openldap/config" path.
|
||||||
|
`
|
57
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/client.go
generated
vendored
Normal file
57
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/client.go
generated
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package openldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault-plugin-secrets-openldap/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ldapClient interface {
|
||||||
|
Get(conf *client.Config, dn string) (*client.Entry, error)
|
||||||
|
UpdatePassword(conf *client.Config, dn string, newPassword string) error
|
||||||
|
UpdateRootPassword(conf *client.Config, newPassword string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient() *Client {
|
||||||
|
return &Client{
|
||||||
|
ldap: client.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
ldap client.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Get(conf *client.Config, dn string) (*client.Entry, error) {
|
||||||
|
filters := map[*client.Field][]string{
|
||||||
|
client.FieldRegistry.ObjectClass: {"*"},
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := c.ldap.Search(conf, dn, filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return nil, fmt.Errorf("unable to find entry %s in openldap", dn)
|
||||||
|
}
|
||||||
|
if len(entries) > 1 {
|
||||||
|
return nil, fmt.Errorf("expected one matching entry, but received %d", len(entries))
|
||||||
|
}
|
||||||
|
return entries[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdatePassword(conf *client.Config, dn string, newPassword string) error {
|
||||||
|
filters := map[*client.Field][]string{
|
||||||
|
client.FieldRegistry.ObjectClass: {"*"},
|
||||||
|
}
|
||||||
|
return c.ldap.UpdatePassword(conf, dn, filters, newPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateRootPassword(conf *client.Config, newPassword string) error {
|
||||||
|
filters := map[*client.Field][]string{
|
||||||
|
client.FieldRegistry.ObjectClass: {"*"},
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.ldap.UpdatePassword(conf, conf.BindDN, filters, newPassword)
|
||||||
|
}
|
147
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/client/client.go
generated
vendored
Normal file
147
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/client/client.go
generated
vendored
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/ldaputil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
*ldaputil.ConfigEntry
|
||||||
|
LastBindPassword string `json:"last_bind_password"`
|
||||||
|
LastBindPasswordRotation time.Time `json:"last_bind_password_rotation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() Client {
|
||||||
|
return Client{
|
||||||
|
ldap: &ldaputil.Client{
|
||||||
|
LDAP: ldaputil.NewLDAP(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
ldap *ldaputil.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Search(cfg *Config, baseDN string, filters map[*Field][]string) ([]*Entry, error) {
|
||||||
|
req := &ldap.SearchRequest{
|
||||||
|
BaseDN: baseDN,
|
||||||
|
Scope: ldap.ScopeBaseObject,
|
||||||
|
Filter: toString(filters),
|
||||||
|
SizeLimit: math.MaxInt32,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := c.ldap.DialLDAP(cfg.ConfigEntry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
if err := bind(cfg, conn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := conn.Search(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := make([]*Entry, len(result.Entries))
|
||||||
|
for i, rawEntry := range result.Entries {
|
||||||
|
entries[i] = NewEntry(rawEntry)
|
||||||
|
}
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateEntry(cfg *Config, baseDN string, filters map[*Field][]string, newValues map[*Field][]string) error {
|
||||||
|
entries, err := c.Search(cfg, baseDN, filters)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(entries) != 1 {
|
||||||
|
return fmt.Errorf("expected one matching entry, but received %d", len(entries))
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyReq := &ldap.ModifyRequest{
|
||||||
|
DN: entries[0].DN,
|
||||||
|
}
|
||||||
|
|
||||||
|
for field, vals := range newValues {
|
||||||
|
modifyReq.Replace(field.String(), vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := c.ldap.DialLDAP(cfg.ConfigEntry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
if err := bind(cfg, conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn.Modify(modifyReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePassword uses a Modify call under the hood instead of LDAP change password function.
|
||||||
|
// This allows AD and OpenLDAP secret engines to use the same api without changes to
|
||||||
|
// the interface.
|
||||||
|
func (c *Client) UpdatePassword(cfg *Config, baseDN string, filters map[*Field][]string, newPassword string) error {
|
||||||
|
newValues := map[*Field][]string{
|
||||||
|
FieldRegistry.UserPassword: {newPassword},
|
||||||
|
}
|
||||||
|
return c.UpdateEntry(cfg, baseDN, filters, newValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ex. "(cn=Ellen Jones)"
|
||||||
|
func toString(filters map[*Field][]string) string {
|
||||||
|
var fieldEquals []string
|
||||||
|
for f, values := range filters {
|
||||||
|
for _, v := range values {
|
||||||
|
fieldEquals = append(fieldEquals, fmt.Sprintf("%s=%s", f, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result := strings.Join(fieldEquals, ",")
|
||||||
|
return "(" + result + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
func bind(cfg *Config, conn ldaputil.Connection) error {
|
||||||
|
if cfg.BindPassword == "" {
|
||||||
|
return errors.New("unable to bind due to lack of configured password")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.BindDN == "" {
|
||||||
|
return errors.New("must provide binddn")
|
||||||
|
}
|
||||||
|
|
||||||
|
origErr := conn.Bind(cfg.BindDN, cfg.BindPassword)
|
||||||
|
if origErr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !shouldTryLastPwd(cfg.LastBindPassword, cfg.LastBindPasswordRotation) {
|
||||||
|
return origErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn.Bind(cfg.BindDN, cfg.LastBindPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldTryLastPwd determines if we should try a previous password.
|
||||||
|
// LDAP can return a variety of errors when a password is invalid.
|
||||||
|
// Rather than attempting to catalogue these errors across multiple versions of
|
||||||
|
// OpenLDAP, we simply try the last password if it's been less than a set amount of
|
||||||
|
// time since a rotation occurred.
|
||||||
|
func shouldTryLastPwd(lastPwd string, lastBindPasswordRotation time.Time) bool {
|
||||||
|
if lastPwd == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lastBindPasswordRotation.IsZero() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return lastBindPasswordRotation.Add(10 * time.Minute).After(time.Now())
|
||||||
|
}
|
41
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/client/entry.go
generated
vendored
Normal file
41
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/client/entry.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Entry is an LDAP-specific construct
|
||||||
|
// to make knowing and grabbing fields more convenient,
|
||||||
|
// while retaining all original information.
|
||||||
|
func NewEntry(ldapEntry *ldap.Entry) *Entry {
|
||||||
|
fieldMap := make(map[string][]string)
|
||||||
|
for _, attribute := range ldapEntry.Attributes {
|
||||||
|
field := FieldRegistry.Parse(attribute.Name)
|
||||||
|
if field == nil {
|
||||||
|
// This field simply isn't in the registry, no big deal.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fieldMap[field.String()] = attribute.Values
|
||||||
|
}
|
||||||
|
return &Entry{fieldMap: fieldMap, Entry: ldapEntry}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entry struct {
|
||||||
|
*ldap.Entry
|
||||||
|
fieldMap map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entry) Get(field *Field) ([]string, bool) {
|
||||||
|
values, found := e.fieldMap[field.String()]
|
||||||
|
return values, found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entry) GetJoined(field *Field) (string, bool) {
|
||||||
|
values, found := e.Get(field)
|
||||||
|
if !found {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return strings.Join(values, ","), true
|
||||||
|
}
|
88
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/client/fieldregistry.go
generated
vendored
Normal file
88
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/client/fieldregistry.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FieldRegistry is designed to look and feel
|
||||||
|
// like an enum from another language like Python.
|
||||||
|
//
|
||||||
|
// Example: Accessing constants
|
||||||
|
//
|
||||||
|
// FieldRegistry.AccountExpires
|
||||||
|
// FieldRegistry.BadPasswordCount
|
||||||
|
//
|
||||||
|
// Example: Utility methods
|
||||||
|
//
|
||||||
|
// FieldRegistry.List()
|
||||||
|
// FieldRegistry.Parse("givenName")
|
||||||
|
//
|
||||||
|
var FieldRegistry = newFieldRegistry()
|
||||||
|
|
||||||
|
// newFieldRegistry iterates through all the fields in the registry,
|
||||||
|
// pulls their ldap strings, and sets up each field to contain its ldap string
|
||||||
|
func newFieldRegistry() *fieldRegistry {
|
||||||
|
reg := &fieldRegistry{}
|
||||||
|
vOfReg := reflect.ValueOf(reg)
|
||||||
|
|
||||||
|
registryFields := vOfReg.Elem()
|
||||||
|
for i := 0; i < registryFields.NumField(); i++ {
|
||||||
|
|
||||||
|
if registryFields.Field(i).Kind() == reflect.Ptr {
|
||||||
|
|
||||||
|
field := registryFields.Type().Field(i)
|
||||||
|
ldapString := field.Tag.Get("ldap")
|
||||||
|
ldapField := &Field{ldapString}
|
||||||
|
vOfLDAPField := reflect.ValueOf(ldapField)
|
||||||
|
|
||||||
|
registryFields.FieldByName(field.Name).Set(vOfLDAPField)
|
||||||
|
|
||||||
|
reg.fieldList = append(reg.fieldList, ldapField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reg
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldRegistry isn't currently intended to be an exhaustive list -
|
||||||
|
// there are more fields in LDAP because schema can be defined by the user.
|
||||||
|
// Here are some of the more common fields.
|
||||||
|
type fieldRegistry struct {
|
||||||
|
CommonName *Field `ldap:"cn"`
|
||||||
|
DisplayName *Field `ldap:"displayName"`
|
||||||
|
DistinguishedName *Field `ldap:"distinguishedName"`
|
||||||
|
DomainComponent *Field `ldap:"dc"`
|
||||||
|
DomainName *Field `ldap:"dn"`
|
||||||
|
Name *Field `ldap:"name"`
|
||||||
|
ObjectCategory *Field `ldap:"objectCategory"`
|
||||||
|
ObjectClass *Field `ldap:"objectClass"`
|
||||||
|
ObjectGUID *Field `ldap:"objectGUID"`
|
||||||
|
ObjectSID *Field `ldap:"objectSid"`
|
||||||
|
OrganizationalUnit *Field `ldap:"ou"`
|
||||||
|
PasswordLastSet *Field `ldap:"passwordLastSet"`
|
||||||
|
UnicodePassword *Field `ldap:"unicodePwd"`
|
||||||
|
UserPassword *Field `ldap:"userPassword"`
|
||||||
|
UserPrincipalName *Field `ldap:"userPrincipalName"`
|
||||||
|
|
||||||
|
fieldList []*Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fieldRegistry) List() []*Field {
|
||||||
|
return r.fieldList
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fieldRegistry) Parse(s string) *Field {
|
||||||
|
for _, f := range r.List() {
|
||||||
|
if f.String() == s {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Field struct {
|
||||||
|
str string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Field) String() string {
|
||||||
|
return f.str
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
module github.com/hashicorp/vault-plugin-secrets-openldap
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-ldap/ldap/v3 v3.1.3
|
||||||
|
github.com/hashicorp/errwrap v1.0.0
|
||||||
|
github.com/hashicorp/go-hclog v0.12.0
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0
|
||||||
|
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||||
|
github.com/hashicorp/go-version v1.2.0 // indirect
|
||||||
|
github.com/hashicorp/vault/api v1.0.5-0.20200117231345-460d63e36490
|
||||||
|
github.com/hashicorp/vault/sdk v0.1.14-0.20200117231345-460d63e36490
|
||||||
|
github.com/stretchr/testify v1.4.0 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect
|
||||||
|
golang.org/x/text v0.3.2 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,183 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||||
|
github.com/armon/go-metrics v0.3.0 h1:B7AQgHi8QSEi4uHu7Sbsga+IJDU+CENgjxoo81vDUqU=
|
||||||
|
github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
|
||||||
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to=
|
||||||
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||||
|
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
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/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
|
github.com/go-ldap/ldap/v3 v3.1.3 h1:RIgdpHXJpsUqUK5WXwKyVsESrGFqo5BRWPk3RR4/ogQ=
|
||||||
|
github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=
|
||||||
|
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31 h1:28FVBuwkwowZMjbA7M0wXsI6t3PYulRTMio3SO+eKCM=
|
||||||
|
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||||
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||||
|
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||||
|
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||||
|
github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM=
|
||||||
|
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
|
github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE=
|
||||||
|
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.6.2 h1:bHM2aVXwBtBJWxHtkSrWuI4umABCUczs52eiUS9nSiw=
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.2-0.20191001231223-f32f5fe8d6a8/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
|
||||||
|
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
|
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
||||||
|
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/hashicorp/vault/api v1.0.5-0.20200117231345-460d63e36490 h1:jzQDZKCJV74KjQS3Ag3Aunjd9aUP1whRaNZzXqcEHs4=
|
||||||
|
github.com/hashicorp/vault/api v1.0.5-0.20200117231345-460d63e36490/go.mod h1:Uf8LaHyrYsgVgHzO2tMZKhqRGlL3UJ6XaSwW2EA1Iqo=
|
||||||
|
github.com/hashicorp/vault/sdk v0.1.14-0.20191108161836-82f2b5571044/go.mod h1:PcekaFGiPJyHnFy+NZhP6ll650zEw51Ag7g/YEa+EOU=
|
||||||
|
github.com/hashicorp/vault/sdk v0.1.14-0.20200117231345-460d63e36490 h1:ZrfYu5Cdi1RXvqFsu8ssK2gRsI+QQ76NtPx4PqByfEM=
|
||||||
|
github.com/hashicorp/vault/sdk v0.1.14-0.20200117231345-460d63e36490/go.mod h1:PcekaFGiPJyHnFy+NZhP6ll650zEw51Ag7g/YEa+EOU=
|
||||||
|
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||||
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
||||||
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||||
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||||
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||||
|
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
|
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||||
|
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
|
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
|
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||||
|
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||||
|
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
|
||||||
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
|
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
|
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||||
|
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||||
|
github.com/stretchr/objx v0.1.0/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 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
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-20181201002055-351d144fa1fc/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-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
|
||||||
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo=
|
||||||
|
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
|
||||||
|
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
153
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/path_config.go
generated
vendored
Normal file
153
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/path_config.go
generated
vendored
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
package openldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault-plugin-secrets-openldap/client"
|
||||||
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/ldaputil"
|
||||||
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
configPath = "config"
|
||||||
|
defaultPasswordLength = 64
|
||||||
|
defaultTLSVersion = "tls12"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readConfig(ctx context.Context, storage logical.Storage) (*config, error) {
|
||||||
|
entry, err := storage.Get(ctx, configPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if entry == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
config := &config{}
|
||||||
|
if err := entry.DecodeJSON(config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) pathConfig() []*framework.Path {
|
||||||
|
return []*framework.Path{
|
||||||
|
{
|
||||||
|
Pattern: configPath,
|
||||||
|
Fields: b.configFields(),
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.CreateOperation: &framework.PathOperation{
|
||||||
|
Callback: b.configCreateUpdateOperation,
|
||||||
|
},
|
||||||
|
logical.UpdateOperation: &framework.PathOperation{
|
||||||
|
Callback: b.configCreateUpdateOperation,
|
||||||
|
},
|
||||||
|
logical.ReadOperation: &framework.PathOperation{
|
||||||
|
Callback: b.configReadOperation,
|
||||||
|
},
|
||||||
|
logical.DeleteOperation: &framework.PathOperation{
|
||||||
|
Callback: b.configDeleteOperation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HelpSynopsis: configHelpSynopsis,
|
||||||
|
HelpDescription: configHelpDescription,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) configFields() map[string]*framework.FieldSchema {
|
||||||
|
fields := ldaputil.ConfigFields()
|
||||||
|
fields["ttl"] = &framework.FieldSchema{
|
||||||
|
Type: framework.TypeDurationSecond,
|
||||||
|
Description: "The default password time-to-live.",
|
||||||
|
}
|
||||||
|
fields["max_ttl"] = &framework.FieldSchema{
|
||||||
|
Type: framework.TypeDurationSecond,
|
||||||
|
Description: "The maximum password time-to-live.",
|
||||||
|
}
|
||||||
|
fields["length"] = &framework.FieldSchema{
|
||||||
|
Type: framework.TypeInt,
|
||||||
|
Default: defaultPasswordLength,
|
||||||
|
Description: "The desired length of passwords that Vault generates.",
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) configCreateUpdateOperation(ctx context.Context, req *logical.Request, fieldData *framework.FieldData) (*logical.Response, error) {
|
||||||
|
// Build and validate the ldap conf.
|
||||||
|
ldapConf, err := ldaputil.NewConfigEntry(nil, fieldData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ldapConf.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
length := fieldData.Get("length").(int)
|
||||||
|
url := fieldData.Get("url").(string)
|
||||||
|
|
||||||
|
if url == "" {
|
||||||
|
return nil, errors.New("url is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &config{
|
||||||
|
LDAP: &client.Config{
|
||||||
|
ConfigEntry: ldapConf,
|
||||||
|
},
|
||||||
|
PasswordLength: length,
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := logical.StorageEntryJSON(configPath, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := req.Storage.Put(ctx, entry); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respond with a 204.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) configReadOperation(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||||
|
config, err := readConfig(ctx, req.Storage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// "password" is intentionally not returned by this endpoint
|
||||||
|
configMap := config.LDAP.Map()
|
||||||
|
delete(configMap, "bindpass")
|
||||||
|
configMap["length"] = config.PasswordLength
|
||||||
|
|
||||||
|
resp := &logical.Response{
|
||||||
|
Data: configMap,
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) configDeleteOperation(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||||
|
if err := req.Storage.Delete(ctx, configPath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
LDAP *client.Config
|
||||||
|
PasswordLength int `json:"length"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const configHelpSynopsis = `
|
||||||
|
Configure the OpenLDAP secret engine plugin.
|
||||||
|
`
|
||||||
|
|
||||||
|
const configHelpDescription = `
|
||||||
|
This path configures the OpenLDAP secret engine plugin. See the documentation for the plugin specified
|
||||||
|
for a full list of accepted connection details.
|
||||||
|
`
|
64
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/path_creds.go
generated
vendored
Normal file
64
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/path_creds.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package openldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
|
)
|
||||||
|
|
||||||
|
const staticCredPath = "static-cred/"
|
||||||
|
|
||||||
|
func (b *backend) pathCredsCreate() []*framework.Path {
|
||||||
|
return []*framework.Path{
|
||||||
|
{
|
||||||
|
Pattern: staticCredPath + framework.GenericNameRegex("name"),
|
||||||
|
Fields: map[string]*framework.FieldSchema{
|
||||||
|
"name": {
|
||||||
|
Type: framework.TypeLowerCaseString,
|
||||||
|
Description: "Name of the static role.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.ReadOperation: &framework.PathOperation{
|
||||||
|
Callback: b.pathStaticCredsRead,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HelpSynopsis: pathStaticCredsReadHelpSyn,
|
||||||
|
HelpDescription: pathStaticCredsReadHelpDesc,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) pathStaticCredsRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||||
|
name := data.Get("name").(string)
|
||||||
|
|
||||||
|
role, err := b.StaticRole(ctx, req.Storage, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if role == nil {
|
||||||
|
return logical.ErrorResponse("unknown role: %s", name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &logical.Response{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"dn": role.StaticAccount.DN,
|
||||||
|
"username": role.StaticAccount.Username,
|
||||||
|
"password": role.StaticAccount.Password,
|
||||||
|
"ttl": role.StaticAccount.PasswordTTL().Seconds(),
|
||||||
|
"rotation_period": role.StaticAccount.RotationPeriod.Seconds(),
|
||||||
|
"last_vault_rotation": role.StaticAccount.LastVaultRotation,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathStaticCredsReadHelpSyn = `
|
||||||
|
Request LDAP credentials for a certain static role. These credentials are
|
||||||
|
rotated periodically.`
|
||||||
|
|
||||||
|
const pathStaticCredsReadHelpDesc = `
|
||||||
|
This path reads LDAP credentials for a certain static role. The LDAPs
|
||||||
|
credentials are rotated periodically according to their configuration, and will
|
||||||
|
return the same password until they are rotated.
|
||||||
|
`
|
370
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/path_roles.go
generated
vendored
Normal file
370
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/path_roles.go
generated
vendored
Normal file
|
@ -0,0 +1,370 @@
|
||||||
|
package openldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/locksutil"
|
||||||
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
|
"github.com/hashicorp/vault/sdk/queue"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
staticRolePath = "static-role/"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *backend) pathListRoles() []*framework.Path {
|
||||||
|
return []*framework.Path{
|
||||||
|
{
|
||||||
|
Pattern: staticRolePath + "?$",
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.ListOperation: &framework.PathOperation{
|
||||||
|
Callback: b.pathRoleList,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HelpSynopsis: staticRolesListHelpSynopsis,
|
||||||
|
HelpDescription: staticRolesListHelpDescription,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) pathRoles() []*framework.Path {
|
||||||
|
return []*framework.Path{
|
||||||
|
{
|
||||||
|
Pattern: staticRolePath + framework.GenericNameRegex("name"),
|
||||||
|
Fields: fieldsForType(staticRolePath),
|
||||||
|
ExistenceCheck: b.pathStaticRoleExistenceCheck,
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.UpdateOperation: &framework.PathOperation{
|
||||||
|
Callback: b.pathStaticRoleCreateUpdate,
|
||||||
|
},
|
||||||
|
logical.CreateOperation: &framework.PathOperation{
|
||||||
|
Callback: b.pathStaticRoleCreateUpdate,
|
||||||
|
},
|
||||||
|
logical.ReadOperation: &framework.PathOperation{
|
||||||
|
Callback: b.pathStaticRoleRead,
|
||||||
|
},
|
||||||
|
logical.DeleteOperation: &framework.PathOperation{
|
||||||
|
Callback: b.pathStaticRoleDelete,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HelpSynopsis: staticRoleHelpSynopsis,
|
||||||
|
HelpDescription: staticRoleHelpDescription,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldsForType returns a map of string/FieldSchema items for the given role
|
||||||
|
// type. The purpose is to keep the shared fields between dynamic and static
|
||||||
|
// roles consistent, and allow for each type to override or provide their own
|
||||||
|
// specific fields
|
||||||
|
func fieldsForType(roleType string) map[string]*framework.FieldSchema {
|
||||||
|
fields := map[string]*framework.FieldSchema{
|
||||||
|
"name": {
|
||||||
|
Type: framework.TypeLowerCaseString,
|
||||||
|
Description: "Name of the role",
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "The username/logon name for the entry with which this role will be associated.",
|
||||||
|
},
|
||||||
|
"dn": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "The distinguished name of the entry to manage.",
|
||||||
|
},
|
||||||
|
"ttl": {
|
||||||
|
Type: framework.TypeDurationSecond,
|
||||||
|
Description: "The time-to-live for the password.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the fields that are specific to the type of role, and add them to the
|
||||||
|
// common fields. In the future we can add additional for dynamic roles.
|
||||||
|
var typeFields map[string]*framework.FieldSchema
|
||||||
|
switch roleType {
|
||||||
|
case staticRolePath:
|
||||||
|
typeFields = staticFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range typeFields {
|
||||||
|
fields[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// staticFields returns a map of key and field schema items that are specific
|
||||||
|
// only to static roles
|
||||||
|
func staticFields() map[string]*framework.FieldSchema {
|
||||||
|
fields := map[string]*framework.FieldSchema{
|
||||||
|
"rotation_period": {
|
||||||
|
Type: framework.TypeDurationSecond,
|
||||||
|
Description: "Period for automatic credential rotation of the given entry.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) pathStaticRoleExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
|
||||||
|
role, err := b.StaticRole(ctx, req.Storage, data.Get("name").(string))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return role != nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) pathStaticRoleDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||||
|
name := data.Get("name").(string)
|
||||||
|
|
||||||
|
// Grab the exclusive lock
|
||||||
|
lock := locksutil.LockForKey(b.roleLocks, name)
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
|
||||||
|
//TODO: Add retry logic
|
||||||
|
|
||||||
|
// Remove the item from the queue
|
||||||
|
_, err := b.popFromRotationQueueByKey(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
role, err := b.StaticRole(ctx, req.Storage, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if role == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = req.Storage.Delete(ctx, staticRolePath+name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) pathStaticRoleRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||||
|
role, err := b.StaticRole(ctx, req.Storage, d.Get("name").(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if role == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"dn": role.StaticAccount.DN,
|
||||||
|
"username": role.StaticAccount.Username,
|
||||||
|
}
|
||||||
|
|
||||||
|
data["rotation_period"] = role.StaticAccount.RotationPeriod.Seconds()
|
||||||
|
if !role.StaticAccount.LastVaultRotation.IsZero() {
|
||||||
|
data["last_vault_rotation"] = role.StaticAccount.LastVaultRotation
|
||||||
|
}
|
||||||
|
|
||||||
|
return &logical.Response{Data: data}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) pathStaticRoleCreateUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||||
|
name := data.Get("name").(string)
|
||||||
|
|
||||||
|
// Grab the exclusive lock as well potentially pop and re-push the queue item
|
||||||
|
// for this role
|
||||||
|
lock := locksutil.LockForKey(b.roleLocks, name)
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
|
||||||
|
role, err := b.StaticRole(ctx, req.Storage, data.Get("name").(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if role == nil {
|
||||||
|
role = &roleEntry{
|
||||||
|
StaticAccount: &staticAccount{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dn := data.Get("dn").(string)
|
||||||
|
if dn == "" {
|
||||||
|
return logical.ErrorResponse("dn is a required field to manage a static account"), nil
|
||||||
|
}
|
||||||
|
role.StaticAccount.DN = dn
|
||||||
|
|
||||||
|
username := data.Get("username").(string)
|
||||||
|
if username == "" {
|
||||||
|
return logical.ErrorResponse("username is a required field to manage a static account"), nil
|
||||||
|
}
|
||||||
|
role.StaticAccount.Username = username
|
||||||
|
|
||||||
|
rotationPeriodSecondsRaw, ok := data.GetOk("rotation_period")
|
||||||
|
if !ok {
|
||||||
|
return logical.ErrorResponse("rotation_period is required for static accounts"), nil
|
||||||
|
}
|
||||||
|
rotationPeriodSeconds := rotationPeriodSecondsRaw.(int)
|
||||||
|
if rotationPeriodSeconds < queueTickSeconds {
|
||||||
|
// If rotation frequency is specified the value
|
||||||
|
// must be at least that of the constant queueTickSeconds (5 seconds at
|
||||||
|
// time of writing), otherwise we wont be able to rotate in time
|
||||||
|
return logical.ErrorResponse("rotation_period must be %d seconds or more", queueTickSeconds), nil
|
||||||
|
}
|
||||||
|
role.StaticAccount.RotationPeriod = time.Duration(rotationPeriodSeconds) * time.Second
|
||||||
|
|
||||||
|
// lvr represents the role's LastVaultRotation
|
||||||
|
lvr := role.StaticAccount.LastVaultRotation
|
||||||
|
|
||||||
|
// Only call setStaticAccountPassword if we're creating the role for the
|
||||||
|
// first time
|
||||||
|
switch req.Operation {
|
||||||
|
case logical.CreateOperation:
|
||||||
|
// setStaticAccountPassword calls Storage.Put and saves the role to storage
|
||||||
|
resp, err := b.setStaticAccountPassword(ctx, req.Storage, &setStaticAccountInput{
|
||||||
|
RoleName: name,
|
||||||
|
Role: role,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// guard against RotationTime not being set or zero-value
|
||||||
|
lvr = resp.RotationTime
|
||||||
|
case logical.UpdateOperation:
|
||||||
|
// store updated Role
|
||||||
|
entry, err := logical.StorageEntryJSON(staticRolePath+name, role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := req.Storage.Put(ctx, entry); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case this is an update, remove any previous version of the item from
|
||||||
|
// the queue
|
||||||
|
|
||||||
|
//TODO: Add retry logic
|
||||||
|
_, err = b.popFromRotationQueueByKey(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add their rotation to the queue
|
||||||
|
if err := b.pushItem(&queue.Item{
|
||||||
|
Key: name,
|
||||||
|
Priority: lvr.Add(role.StaticAccount.RotationPeriod).Unix(),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type roleEntry struct {
|
||||||
|
StaticAccount *staticAccount `json:"static_account" mapstructure:"static_account"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type staticAccount struct {
|
||||||
|
// DN to create or assume management for static accounts
|
||||||
|
DN string `json:"dn"`
|
||||||
|
|
||||||
|
// Username to create or assume management for static accounts
|
||||||
|
Username string `json:"username"`
|
||||||
|
|
||||||
|
// Password is the current password for static accounts. As an input, this is
|
||||||
|
// used/required when trying to assume management of an existing static
|
||||||
|
// account. Return this on credential request if it exists.
|
||||||
|
Password string `json:"password"`
|
||||||
|
|
||||||
|
// LastVaultRotation represents the last time Vault rotated the password
|
||||||
|
LastVaultRotation time.Time `json:"last_vault_rotation"`
|
||||||
|
|
||||||
|
// RotationPeriod is number in seconds between each rotation, effectively a
|
||||||
|
// "time to live". This value is compared to the LastVaultRotation to
|
||||||
|
// determine if a password needs to be rotated
|
||||||
|
RotationPeriod time.Duration `json:"rotation_period"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextRotationTime calculates the next rotation by adding the Rotation Period
|
||||||
|
// to the last known vault rotation
|
||||||
|
func (s *staticAccount) NextRotationTime() time.Time {
|
||||||
|
return s.LastVaultRotation.Add(s.RotationPeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasswordTTL calculates the approximate time remaining until the password is
|
||||||
|
// no longer valid. This is approximate because the periodic rotation is only
|
||||||
|
// checked approximately every 5 seconds, and each rotation can take a small
|
||||||
|
// amount of time to process. This can result in a negative TTL time while the
|
||||||
|
// rotation function processes the Static Role and performs the rotation. If the
|
||||||
|
// TTL is negative, zero is returned. Users should not trust passwords with a
|
||||||
|
// Zero TTL, as they are likely in the process of being rotated and will quickly
|
||||||
|
// be invalidated.
|
||||||
|
func (s *staticAccount) PasswordTTL() time.Duration {
|
||||||
|
next := s.NextRotationTime()
|
||||||
|
ttl := next.Sub(time.Now()).Round(time.Second)
|
||||||
|
if ttl < 0 {
|
||||||
|
ttl = time.Duration(0)
|
||||||
|
}
|
||||||
|
return ttl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) pathRoleList(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||||
|
path := staticRolePath
|
||||||
|
entries, err := req.Storage.List(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return logical.ListResponse(entries), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) StaticRole(ctx context.Context, s logical.Storage, roleName string) (*roleEntry, error) {
|
||||||
|
return b.roleAtPath(ctx, s, roleName, staticRolePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) roleAtPath(ctx context.Context, s logical.Storage, roleName string, pathPrefix string) (*roleEntry, error) {
|
||||||
|
entry, err := s.Get(ctx, pathPrefix+roleName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if entry == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result roleEntry
|
||||||
|
if err := entry.DecodeJSON(&result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const staticRoleHelpSynopsis = `
|
||||||
|
Manage the static roles that can be created with this backend.
|
||||||
|
`
|
||||||
|
|
||||||
|
const staticRoleHelpDescription = `
|
||||||
|
This path lets you manage the static roles that can be created with this
|
||||||
|
backend. Static Roles are associated with a single LDAP entry, and manage the
|
||||||
|
password based on a rotation period, automatically rotating the password.
|
||||||
|
|
||||||
|
The "dn" parameter is required and configures the domain name to use when managing
|
||||||
|
the existing entry.
|
||||||
|
|
||||||
|
The "username" parameter is required and configures the username for the LDAP entry.
|
||||||
|
This is helpful to provide a usable name when domain name (DN) isn't used directly for
|
||||||
|
authentication.
|
||||||
|
|
||||||
|
|
||||||
|
The "rotation_period' parameter is required and configures how often, in seconds, the credentials should be
|
||||||
|
automatically rotated by Vault. The minimum is 5 seconds (5s).
|
||||||
|
`
|
||||||
|
|
||||||
|
const staticRolesListHelpDescription = `
|
||||||
|
List all the static roles being managed by Vault.
|
||||||
|
`
|
||||||
|
|
||||||
|
const staticRolesListHelpSynopsis = `
|
||||||
|
This path lists all the static roles Vault is currently managing in OpenLDAP.
|
||||||
|
`
|
197
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/path_rotate.go
generated
vendored
Normal file
197
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/path_rotate.go
generated
vendored
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
package openldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/base62"
|
||||||
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
|
"github.com/hashicorp/vault/sdk/queue"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rotateRootPath = "rotate-root"
|
||||||
|
rotateRolePath = "rotate-role/"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *backend) pathRotateCredentials() []*framework.Path {
|
||||||
|
return []*framework.Path{
|
||||||
|
{
|
||||||
|
Pattern: rotateRootPath,
|
||||||
|
Fields: map[string]*framework.FieldSchema{},
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.UpdateOperation: &framework.PathOperation{
|
||||||
|
Callback: b.pathRotateCredentialsUpdate,
|
||||||
|
},
|
||||||
|
logical.CreateOperation: &framework.PathOperation{
|
||||||
|
Callback: b.pathRotateCredentialsUpdate,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HelpSynopsis: pathRotateCredentialsUpdateHelpSyn,
|
||||||
|
HelpDescription: pathRotateCredentialsUpdateHelpDesc,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: rotateRolePath + framework.GenericNameRegex("name"),
|
||||||
|
Fields: map[string]*framework.FieldSchema{
|
||||||
|
"name": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Name of the static role",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
|
logical.UpdateOperation: &framework.PathOperation{
|
||||||
|
Callback: b.pathRotateRoleCredentialsUpdate,
|
||||||
|
},
|
||||||
|
logical.CreateOperation: &framework.PathOperation{
|
||||||
|
Callback: b.pathRotateRoleCredentialsUpdate,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HelpSynopsis: pathRotateRoleCredentialsUpdateHelpSyn,
|
||||||
|
HelpDescription: pathRotateRoleCredentialsUpdateHelpDesc,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) pathRotateCredentialsUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||||
|
config, err := readConfig(ctx, req.Storage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("the config is currently unset")
|
||||||
|
}
|
||||||
|
|
||||||
|
newPassword, err := GeneratePassword(config.PasswordLength)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
oldPassword := config.LDAP.BindPassword
|
||||||
|
|
||||||
|
// Take out the backend lock since we are swapping out the connection
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
|
||||||
|
// Update the password remotely.
|
||||||
|
if err := b.client.UpdateRootPassword(config.LDAP, newPassword); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.LDAP.BindPassword = newPassword
|
||||||
|
|
||||||
|
// Update the password locally.
|
||||||
|
if pwdStoringErr := storePassword(ctx, req.Storage, config); pwdStoringErr != nil {
|
||||||
|
// We were unable to store the new password locally. We can't continue in this state because we won't be able
|
||||||
|
// to roll any passwords, including our own to get back into a state of working. So, we need to roll back to
|
||||||
|
// the last password we successfully got into storage.
|
||||||
|
if rollbackErr := b.rollBackPassword(ctx, config, oldPassword); rollbackErr != nil {
|
||||||
|
return nil, fmt.Errorf(`unable to store new password due to %s and unable to return to previous password
|
||||||
|
due to %s, configure a new binddn and bindpass to restore openldap function`, pwdStoringErr, rollbackErr)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to update password due to storage err: %s", pwdStoringErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respond with a 204.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (b *backend) pathRotateRoleCredentialsUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||||
|
name := data.Get("name").(string)
|
||||||
|
if name == "" {
|
||||||
|
return logical.ErrorResponse("empty role name attribute given"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
role, err := b.StaticRole(ctx, req.Storage, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if role == nil {
|
||||||
|
return logical.ErrorResponse("role doesn't exist: %s", name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// In create/update of static accounts, we only care if the operation
|
||||||
|
// err'd , and this call does not return credentials
|
||||||
|
item, err := b.popFromRotationQueueByKey(name)
|
||||||
|
if err != nil {
|
||||||
|
item = &queue.Item{
|
||||||
|
Key: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := b.setStaticAccountPassword(ctx, req.Storage, &setStaticAccountInput{
|
||||||
|
RoleName: name,
|
||||||
|
Role: role,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
b.Logger().Warn("unable to rotate credentials in rotate-role", "error", err)
|
||||||
|
// Update the priority to re-try this rotation and re-add the item to
|
||||||
|
// the queue
|
||||||
|
item.Priority = time.Now().Add(10 * time.Second).Unix()
|
||||||
|
|
||||||
|
// Preserve the WALID if it was returned
|
||||||
|
if resp.WALID != "" {
|
||||||
|
item.Value = resp.WALID
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item.Priority = resp.RotationTime.Add(role.StaticAccount.RotationPeriod).Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add their rotation to the queue
|
||||||
|
if err := b.pushItem(item); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're not returning creds here because we do not know if its been processed
|
||||||
|
// by the queue.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollBackPassword uses naive exponential backoff to retry updating to an old password,
|
||||||
|
// because LDAP may still be propagating the previous password change.
|
||||||
|
func (b *backend) rollBackPassword(ctx context.Context, config *config, oldPassword string) error {
|
||||||
|
var err error
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
waitSeconds := math.Pow(float64(i), 2)
|
||||||
|
timer := time.NewTimer(time.Duration(waitSeconds) * time.Second)
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Outer environment is closing.
|
||||||
|
return fmt.Errorf("unable to roll back password because enclosing environment is shutting down")
|
||||||
|
}
|
||||||
|
if err = b.client.UpdateRootPassword(config.LDAP, oldPassword); err == nil {
|
||||||
|
// Success.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Failure after looping.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func storePassword(ctx context.Context, s logical.Storage, config *config) error {
|
||||||
|
entry, err := logical.StorageEntryJSON(configPath, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.Put(ctx, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GeneratePassword(length int) (string, error) {
|
||||||
|
return base62.Random(length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathRotateCredentialsUpdateHelpSyn = `
|
||||||
|
Request to rotate the root credentials Vault uses for the OpenLDAP administrator account.
|
||||||
|
`
|
||||||
|
|
||||||
|
const pathRotateCredentialsUpdateHelpDesc = `
|
||||||
|
This path attempts to rotate the root credentials of the administrator account (binddn) used by Vault to manage OpenLDAP.
|
||||||
|
`
|
||||||
|
|
||||||
|
const pathRotateRoleCredentialsUpdateHelpSyn = `
|
||||||
|
Request to rotate the credentials for a static user account.
|
||||||
|
`
|
||||||
|
const pathRotateRoleCredentialsUpdateHelpDesc = `
|
||||||
|
This path attempts to rotate the credentials for the given OpenLDAP static user account.
|
||||||
|
`
|
498
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/rotation.go
generated
vendored
Normal file
498
vendor/github.com/hashicorp/vault-plugin-secrets-openldap/rotation.go
generated
vendored
Normal file
|
@ -0,0 +1,498 @@
|
||||||
|
package openldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/errwrap"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/locksutil"
|
||||||
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
|
"github.com/hashicorp/vault/sdk/queue"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Interval to check the queue for items needing rotation
|
||||||
|
queueTickSeconds = 5
|
||||||
|
queueTickInterval = queueTickSeconds * time.Second
|
||||||
|
|
||||||
|
// WAL storage key used for static account rotations
|
||||||
|
staticWALKey = "staticRotationKey"
|
||||||
|
)
|
||||||
|
|
||||||
|
// populateQueue loads the priority queue with existing static accounts. This
|
||||||
|
// occurs at initialization, after any WAL entries of failed or interrupted
|
||||||
|
// rotations have been processed. It lists the roles from storage and searches
|
||||||
|
// for any that have an associated static account, then adds them to the
|
||||||
|
// priority queue for rotations.
|
||||||
|
func (b *backend) populateQueue(ctx context.Context, s logical.Storage) {
|
||||||
|
log := b.Logger()
|
||||||
|
log.Info("populating role rotation queue")
|
||||||
|
|
||||||
|
// Build map of role name / wal entries
|
||||||
|
walMap, err := b.loadStaticWALs(ctx, s)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("unable to load rotation WALs", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
roles, err := s.List(ctx, staticRolePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("unable to list role for enqueueing", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, roleName := range roles {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
log.Info("rotation queue restore cancelled")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
role, err := b.StaticRole(ctx, s, roleName)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("unable to read static role", "error", err, "role", roleName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
item := queue.Item{
|
||||||
|
Key: roleName,
|
||||||
|
Priority: role.StaticAccount.LastVaultRotation.Add(role.StaticAccount.RotationPeriod).Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if role name is in map
|
||||||
|
walEntry := walMap[roleName]
|
||||||
|
if walEntry != nil {
|
||||||
|
// Check walEntry last vault time
|
||||||
|
if !walEntry.LastVaultRotation.IsZero() && walEntry.LastVaultRotation.Before(role.StaticAccount.LastVaultRotation) {
|
||||||
|
// WAL's last vault rotation record is older than the role's data, so
|
||||||
|
// delete and move on
|
||||||
|
if err := framework.DeleteWAL(ctx, s, walEntry.walID); err != nil {
|
||||||
|
log.Warn("unable to delete WAL", "error", err, "WAL ID", walEntry.walID)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Info("adjusting priority for Role")
|
||||||
|
item.Value = walEntry.walID
|
||||||
|
item.Priority = time.Now().Unix()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.pushItem(&item); err != nil {
|
||||||
|
log.Warn("unable to enqueue item", "error", err, "role", roleName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runTicker kicks off a periodic ticker that invoke the automatic credential
|
||||||
|
// rotation method at a determined interval. The default interval is 5 seconds.
|
||||||
|
func (b *backend) runTicker(ctx context.Context, s logical.Storage) {
|
||||||
|
b.Logger().Info("starting periodic ticker")
|
||||||
|
tick := time.NewTicker(queueTickInterval)
|
||||||
|
defer tick.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tick.C:
|
||||||
|
b.rotateCredentials(ctx, s)
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
b.Logger().Info("stopping periodic ticker")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setCredentialsWAL is used to store information in a WAL that can retry a
|
||||||
|
// credential setting or rotation in the event of partial failure.
|
||||||
|
type setCredentialsWAL struct {
|
||||||
|
NewPassword string `json:"new_password"`
|
||||||
|
OldPassword string `json:"old_password"`
|
||||||
|
RoleName string `json:"role_name"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
DN string `json:"dn"`
|
||||||
|
|
||||||
|
LastVaultRotation time.Time `json:"last_vault_rotation"`
|
||||||
|
|
||||||
|
walID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotateCredentials sets a new password for a static account. This method is
|
||||||
|
// invoked in the runTicker method, which is in it's own go-routine, and invoked
|
||||||
|
// periodically (approximately every 5 seconds).
|
||||||
|
//
|
||||||
|
// This method loops through the priority queue, popping the highest priority
|
||||||
|
// item until it encounters the first item that does not yet need rotation,
|
||||||
|
// based on the current time.
|
||||||
|
func (b *backend) rotateCredentials(ctx context.Context, s logical.Storage) {
|
||||||
|
for b.rotateCredential(ctx, s) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) rotateCredential(ctx context.Context, s logical.Storage) bool {
|
||||||
|
// Quit rotating credentials if shutdown has started
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
item, err := b.popFromRotationQueue()
|
||||||
|
if err != nil {
|
||||||
|
if err != queue.ErrEmpty {
|
||||||
|
b.Logger().Error("error popping item from queue", "err", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guard against possible nil item
|
||||||
|
if item == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab the exclusive lock for this Role, to make sure we don't incur and
|
||||||
|
// writes during the rotation process
|
||||||
|
lock := locksutil.LockForKey(b.roleLocks, item.Key)
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
|
||||||
|
// Validate the role still exists
|
||||||
|
role, err := b.StaticRole(ctx, s, item.Key)
|
||||||
|
if err != nil {
|
||||||
|
b.Logger().Error("unable to load role", "role", item.Key, "error", err)
|
||||||
|
item.Priority = time.Now().Add(10 * time.Second).Unix()
|
||||||
|
if err := b.pushItem(item); err != nil {
|
||||||
|
b.Logger().Error("unable to push item on to queue", "error", err)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if role == nil {
|
||||||
|
b.Logger().Warn("role not found", "role", item.Key, "error", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If "now" is less than the Item priority, then this item does not need to
|
||||||
|
// be rotated
|
||||||
|
if time.Now().Unix() < item.Priority {
|
||||||
|
if err := b.pushItem(item); err != nil {
|
||||||
|
b.Logger().Error("unable to push item on to queue", "error", err)
|
||||||
|
}
|
||||||
|
// Break out of the for loop
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
input := &setStaticAccountInput{
|
||||||
|
RoleName: item.Key,
|
||||||
|
Role: role,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a WAL entry related to this Role, the corresponding WAL ID
|
||||||
|
// should be stored in the Item's Value field.
|
||||||
|
if walID, ok := item.Value.(string); ok {
|
||||||
|
walEntry, err := b.findStaticWAL(ctx, s, walID)
|
||||||
|
if err != nil {
|
||||||
|
b.Logger().Error("error finding static WAL", "error", err)
|
||||||
|
item.Priority = time.Now().Add(10 * time.Second).Unix()
|
||||||
|
if err := b.pushItem(item); err != nil {
|
||||||
|
b.Logger().Error("unable to push item on to queue", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if walEntry != nil && walEntry.NewPassword != "" {
|
||||||
|
input.Password = walEntry.NewPassword
|
||||||
|
input.WALID = walID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := b.setStaticAccountPassword(ctx, s, input)
|
||||||
|
if err != nil {
|
||||||
|
b.Logger().Error("unable to rotate credentials in periodic function", "error", err)
|
||||||
|
// Increment the priority enough so that the next call to this method
|
||||||
|
// likely will not attempt to rotate it, as a back-off of sorts
|
||||||
|
item.Priority = time.Now().Add(10 * time.Second).Unix()
|
||||||
|
|
||||||
|
// Preserve the WALID if it was returned
|
||||||
|
if resp != nil && resp.WALID != "" {
|
||||||
|
item.Value = resp.WALID
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.pushItem(item); err != nil {
|
||||||
|
b.Logger().Error("unable to push item on to queue", "error", err)
|
||||||
|
}
|
||||||
|
// Go to next item
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
lvr := resp.RotationTime
|
||||||
|
if lvr.IsZero() {
|
||||||
|
lvr = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update priority and push updated Item to the queue
|
||||||
|
nextRotation := lvr.Add(role.StaticAccount.RotationPeriod)
|
||||||
|
item.Priority = nextRotation.Unix()
|
||||||
|
if err := b.pushItem(item); err != nil {
|
||||||
|
b.Logger().Warn("unable to push item on to queue", "error", err)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// findStaticWAL loads a WAL entry by ID. If found, only return the WAL if it
|
||||||
|
// is of type staticWALKey, otherwise return nil
|
||||||
|
func (b *backend) findStaticWAL(ctx context.Context, s logical.Storage, id string) (*setCredentialsWAL, error) {
|
||||||
|
wal, err := framework.GetWAL(ctx, s, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if wal == nil || wal.Kind != staticWALKey {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data := wal.Data.(map[string]interface{})
|
||||||
|
walEntry := setCredentialsWAL{
|
||||||
|
walID: id,
|
||||||
|
NewPassword: data["new_password"].(string),
|
||||||
|
OldPassword: data["old_password"].(string),
|
||||||
|
RoleName: data["role_name"].(string),
|
||||||
|
Username: data["username"].(string),
|
||||||
|
DN: data["dn"].(string),
|
||||||
|
}
|
||||||
|
lvr, err := time.Parse(time.RFC3339, data["last_vault_rotation"].(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
walEntry.LastVaultRotation = lvr
|
||||||
|
|
||||||
|
return &walEntry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type setStaticAccountInput struct {
|
||||||
|
RoleName string
|
||||||
|
Role *roleEntry
|
||||||
|
Password string
|
||||||
|
CreateUser bool
|
||||||
|
WALID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type setStaticAccountOutput struct {
|
||||||
|
RotationTime time.Time
|
||||||
|
Password string
|
||||||
|
// Optional return field, in the event WAL was created and not destroyed
|
||||||
|
// during the operation
|
||||||
|
WALID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// setStaticAccountPassword sets the password for a static account associated with a
|
||||||
|
// Role. This method does many things:
|
||||||
|
// - verifies role exists and is in the allowed roles list
|
||||||
|
// - loads an existing WAL entry if WALID input is given, otherwise creates a
|
||||||
|
// new WAL entry
|
||||||
|
// - gets a database connection
|
||||||
|
// - accepts an input password, otherwise generates a new one via gRPC to the
|
||||||
|
// database plugin
|
||||||
|
// - sets new password for the static account
|
||||||
|
// - uses WAL for ensuring passwords are not lost if storage to Vault fails
|
||||||
|
//
|
||||||
|
// This method does not perform any operations on the priority queue. Those
|
||||||
|
// tasks must be handled outside of this method.
|
||||||
|
func (b *backend) setStaticAccountPassword(ctx context.Context, s logical.Storage, input *setStaticAccountInput) (*setStaticAccountOutput, error) {
|
||||||
|
var merr error
|
||||||
|
if input == nil || input.Role == nil || input.RoleName == "" {
|
||||||
|
return nil, errors.New("input was empty when attempting to set credentials for static account")
|
||||||
|
}
|
||||||
|
// Re-use WAL ID if present, otherwise PUT a new WAL
|
||||||
|
output := &setStaticAccountOutput{WALID: input.WALID}
|
||||||
|
|
||||||
|
config, err := readConfig(ctx, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("the config is currently unset")
|
||||||
|
}
|
||||||
|
|
||||||
|
newPassword, err := GeneratePassword(config.PasswordLength)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldPassword := input.Role.StaticAccount.Password
|
||||||
|
|
||||||
|
// Take out the backend lock since we are swapping out the connection
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
|
||||||
|
if output.WALID == "" {
|
||||||
|
output.WALID, err = framework.PutWAL(ctx, s, staticWALKey, &setCredentialsWAL{
|
||||||
|
RoleName: input.RoleName,
|
||||||
|
Username: input.Role.StaticAccount.Username,
|
||||||
|
DN: input.Role.StaticAccount.DN,
|
||||||
|
NewPassword: newPassword,
|
||||||
|
OldPassword: oldPassword,
|
||||||
|
LastVaultRotation: input.Role.StaticAccount.LastVaultRotation,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return output, errwrap.Wrapf("error writing WAL entry: {{err}}", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the password remotely.
|
||||||
|
if err := b.client.UpdatePassword(config.LDAP, input.Role.StaticAccount.DN, newPassword); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the password locally.
|
||||||
|
if pwdStoringErr := storePassword(ctx, s, config); pwdStoringErr != nil {
|
||||||
|
// We were unable to store the new password locally. We can't continue in this state because we won't be able
|
||||||
|
// to roll any passwords, including our own to get back into a state of working. So, we need to roll back to
|
||||||
|
// the last password we successfully got into storage.
|
||||||
|
if rollbackErr := b.rollBackPassword(ctx, config, oldPassword); rollbackErr != nil {
|
||||||
|
return nil, fmt.Errorf(`unable to store new password due to %s and unable to return to previous password due
|
||||||
|
to %s, configure a new binddn and bindpass to restore openldap function`, pwdStoringErr, rollbackErr)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to update password due to storage err: %s", pwdStoringErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store updated role information
|
||||||
|
// lvr is the known LastVaultRotation
|
||||||
|
lvr := time.Now()
|
||||||
|
input.Role.StaticAccount.LastVaultRotation = lvr
|
||||||
|
input.Role.StaticAccount.Password = newPassword
|
||||||
|
output.RotationTime = lvr
|
||||||
|
|
||||||
|
entry, err := logical.StorageEntryJSON(staticRolePath+input.RoleName, input.Role)
|
||||||
|
if err != nil {
|
||||||
|
return output, err
|
||||||
|
}
|
||||||
|
if err := s.Put(ctx, entry); err != nil {
|
||||||
|
return output, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup WAL after successfully rotating and pushing new item on to queue
|
||||||
|
if err := framework.DeleteWAL(ctx, s, output.WALID); err != nil {
|
||||||
|
merr = multierror.Append(merr, err)
|
||||||
|
return output, merr
|
||||||
|
}
|
||||||
|
|
||||||
|
// The WAL has been deleted, return new setStaticAccountOutput without it
|
||||||
|
return &setStaticAccountOutput{RotationTime: lvr}, merr
|
||||||
|
}
|
||||||
|
|
||||||
|
// initQueue preforms the necessary checks and initializations needed to preform
|
||||||
|
// automatic credential rotation for roles associated with static accounts. This
|
||||||
|
// method verifies if a queue is needed (primary server or local mount), and if
|
||||||
|
// so initializes the queue and launches a go-routine to periodically invoke a
|
||||||
|
// method to preform the rotations.
|
||||||
|
//
|
||||||
|
// initQueue is invoked by the Factory method in a go-routine. The Factory does
|
||||||
|
// not wait for success or failure of it's tasks before continuing. This is to
|
||||||
|
// avoid blocking the mount process while loading and evaluating existing roles,
|
||||||
|
// etc.
|
||||||
|
func (b *backend) initQueue(ctx context.Context, conf *logical.InitializationRequest) {
|
||||||
|
// Verify this mount is on the primary server, or is a local mount. If not, do
|
||||||
|
// not create a queue or launch a ticker. Both processing the WAL list and
|
||||||
|
// populating the queue are done sequentially and before launching a
|
||||||
|
// go-routine to run the periodic ticker.
|
||||||
|
replicationState := b.System().ReplicationState()
|
||||||
|
if (b.System().LocalMount() || !replicationState.HasState(consts.ReplicationPerformanceSecondary)) &&
|
||||||
|
!replicationState.HasState(consts.ReplicationDRSecondary) &&
|
||||||
|
!replicationState.HasState(consts.ReplicationPerformanceStandby) {
|
||||||
|
b.Logger().Info("initializing database rotation queue")
|
||||||
|
|
||||||
|
// Load roles and populate queue with static accounts
|
||||||
|
b.populateQueue(ctx, conf.Storage)
|
||||||
|
|
||||||
|
// Launch ticker
|
||||||
|
go b.runTicker(ctx, conf.Storage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadStaticWALs reads WAL entries and returns a map of roles and their
|
||||||
|
// setCredentialsWAL, if found.
|
||||||
|
func (b *backend) loadStaticWALs(ctx context.Context, s logical.Storage) (map[string]*setCredentialsWAL, error) {
|
||||||
|
keys, err := framework.ListWAL(ctx, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(keys) == 0 {
|
||||||
|
b.Logger().Debug("no WAL entries found")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
walMap := make(map[string]*setCredentialsWAL)
|
||||||
|
// Loop through WAL keys and process any rotation ones
|
||||||
|
for _, walID := range keys {
|
||||||
|
walEntry, err := b.findStaticWAL(ctx, s, walID)
|
||||||
|
if err != nil {
|
||||||
|
b.Logger().Error("error loading static WAL", "id", walID, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if walEntry == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the static role still exists
|
||||||
|
roleName := walEntry.RoleName
|
||||||
|
role, err := b.StaticRole(ctx, s, roleName)
|
||||||
|
if err != nil {
|
||||||
|
b.Logger().Warn("unable to read static role", "error", err, "role", roleName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if role == nil || role.StaticAccount == nil {
|
||||||
|
if err := framework.DeleteWAL(ctx, s, walEntry.walID); err != nil {
|
||||||
|
b.Logger().Warn("unable to delete WAL", "error", err, "WAL ID", walEntry.walID)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
walEntry.walID = walID
|
||||||
|
walMap[walEntry.RoleName] = walEntry
|
||||||
|
}
|
||||||
|
return walMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushItem wraps the internal queue's Push call, to make sure a queue is
|
||||||
|
// actually available. This is needed because both runTicker and initQueue
|
||||||
|
// operate in go-routines, and could be accessing the queue concurrently
|
||||||
|
func (b *backend) pushItem(item *queue.Item) error {
|
||||||
|
b.RLock()
|
||||||
|
defer b.RUnlock()
|
||||||
|
|
||||||
|
if b.credRotationQueue != nil {
|
||||||
|
return b.credRotationQueue.Push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Logger().Warn("no queue found during push item")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// popFromRotationQueue wraps the internal queue's Pop call, to make sure a queue is
|
||||||
|
// actually available. This is needed because both runTicker and initQueue
|
||||||
|
// operate in go-routines, and could be accessing the queue concurrently
|
||||||
|
func (b *backend) popFromRotationQueue() (*queue.Item, error) {
|
||||||
|
b.RLock()
|
||||||
|
defer b.RUnlock()
|
||||||
|
if b.credRotationQueue != nil {
|
||||||
|
return b.credRotationQueue.Pop()
|
||||||
|
}
|
||||||
|
return nil, queue.ErrEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
// popFromRotationQueueByKey wraps the internal queue's PopByKey call, to make sure a queue is
|
||||||
|
// actually available. This is needed because both runTicker and initQueue
|
||||||
|
// operate in go-routines, and could be accessing the queue concurrently
|
||||||
|
func (b *backend) popFromRotationQueueByKey(name string) (*queue.Item, error) {
|
||||||
|
b.RLock()
|
||||||
|
defer b.RUnlock()
|
||||||
|
if b.credRotationQueue != nil {
|
||||||
|
item, err := b.credRotationQueue.PopByKey(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if item != nil {
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, queue.ErrEmpty
|
||||||
|
}
|
|
@ -425,6 +425,9 @@ github.com/hashicorp/vault-plugin-secrets-gcpkms
|
||||||
github.com/hashicorp/vault-plugin-secrets-kv
|
github.com/hashicorp/vault-plugin-secrets-kv
|
||||||
# github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.0.0-20200124190647-0026e6bed4fb
|
# github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.0.0-20200124190647-0026e6bed4fb
|
||||||
github.com/hashicorp/vault-plugin-secrets-mongodbatlas
|
github.com/hashicorp/vault-plugin-secrets-mongodbatlas
|
||||||
|
# github.com/hashicorp/vault-plugin-secrets-openldap v0.0.0-20200215165936-237ad8919d2c
|
||||||
|
github.com/hashicorp/vault-plugin-secrets-openldap
|
||||||
|
github.com/hashicorp/vault-plugin-secrets-openldap/client
|
||||||
# github.com/hashicorp/vault/api v1.0.5-0.20200214222743-c39f5634b39f => ./api
|
# github.com/hashicorp/vault/api v1.0.5-0.20200214222743-c39f5634b39f => ./api
|
||||||
github.com/hashicorp/vault/api
|
github.com/hashicorp/vault/api
|
||||||
# github.com/hashicorp/vault/sdk v0.1.14-0.20200214222719-7a3b716487a5 => ./sdk
|
# github.com/hashicorp/vault/sdk v0.1.14-0.20200214222719-7a3b716487a5 => ./sdk
|
||||||
|
|
Loading…
Reference in New Issue