diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e87a115 --- /dev/null +++ b/LICENSE @@ -0,0 +1,363 @@ +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. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..61e31fa --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +GOARCH = amd64 + +UNAME = $(shell uname -s) + +ifndef OS + ifeq ($(UNAME), Linux) + OS = linux + else ifeq ($(UNAME), Darwin) + OS = darwin + endif +endif + +.DEFAULT_GOAL := all + +all: fmt build start + +build: + GOOS=$(OS) GOARCH="$(GOARCH)" go build -o bin/plugins/vaultplugin-hsmpki cmd/vaultplugin-hsmpki/main.go + +start: + vault server -dev -dev-root-token-id=root -dev-plugin-dir=./bin/plugins -log-level=debug + +clean: + rm -f ./bin/plugins/vaultplugin-hsmpki + +#enable: +# vault secrets enable -path=hsmpki vaultplugin-hsmpki + +fmt: + go fmt $$(go list ./...) + +test: hsmconnection + +hsmconnection: + go test -v -run TestConnectPkcs11Connection ./pkg/hsmpki + +pathrolecreate: + go test -v -run TestPathRoleCreate ./pkg/hsmpki + +pathsetsignedintermediate: + go test -v -run TestPathSetSignedIntermediate ./pkg/hsmpki + +pathsetcrlconfig: + go test -v -run TestPathSetCRLConfig ./pkg/hsmpki + +pathfetchcrl: + go test -v -run TestPathFetchCRL ./pkg/hsmpki + +.PHONY: build clean fmt start enable diff --git a/README.md b/README.md new file mode 100644 index 0000000..119d741 --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# Vault HSM PKI Plugin + +The Vault HSM PKI plugin overlays the modifications to the builtin PKI plugin that enable support for certificate signing using a Hardware Security Module via [PKCS#11](http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/os/pkcs11-base-v2.40-os.html). + +## Software Design + +### Reuse of builtin PKI + +The [builtin PKI](https://github.com/hashicorp/vault/tree/v1.6.3/builtin/logical/pki) has a [specified API](https://www.vaultproject.io/api-docs/secret/pki) in terms of usage which new plugins can conform to, but the code is not expressed as a reusable module. + +As this HSM plugin seeks to retain the majority of existing functionality without modification, eg. roles, the builtin PKI code is included in the [pkg/pki](./pkg/pki) directory with the addition of the pki_api.go file that makes select functions externally accessible. The rest of the included PKI code is included verbatim in the pkg/pki directory. + +The HSM PKI plugin can therefore selectively override some of the PKI paths whilst using some unchanged paths. + +## Usage + +### Dependencies + +[Go](https://golang.org/doc/install) + +[Vault](https://www.vaultproject.io/downloads) + +### Setup HSMs + +The [pkcs11helper module](https://github.com/mode51software/pkcs11helper) provides [detailed setup instructions](https://github.com/mode51software/pkcs11helper/blob/master/SETUP.md) for SoftHSM, Thales's SafeNet and Entrust's nShield. + +### Build + +Note that the following env var may be needed: + +export GOSUMDB=off + +The following command will build the plugin binary and start the Vault server as an in memory dev instance: + +``` +make +``` + +Visit [INSTALL.md](INSTALL.md) for the plugin installation and registration details. + +### Login +Now open a new terminal window and login to Vault. This is an example for a dev instance: + +`export VAULT_ADDR='http://127.0.0.1:8200'` + +`vault login root` + +### Setup + +Enable the HSM PKI plugin: + +`vault secrets enable -path=hsmpki -options="config=conf/config-softhsm.hcl" vaultplugin-hsmpki` + +### Run + +#### Create a Role + +Create a role for the allowed domain, which configures the certificate signing template, in this case localhost: + +`vault write hsmpki/roles/localhost allowed_domains=localhost allow_subdomains=true ttl=24h max_ttl=72h key_type="ec" key_bits="384"` + +#### Set the Signed Intermediate CA + +Set the signed Intermediate certificate and use the HSM PKI extensions supporting the configuration of the HSM key alias and the preferred SHA algorithm : + +`vault write hsmpki/intermediate/set-signed certificate=@data/safenet-inter-0016.ca.cert.pem key_alias="ECTestCAInterKey0016" hash_algo="SHA-512"` + +#### Sign a CSR +Now that Vault is ready for signing, sign a standalone CSR file using the HSM returning the CA and the signed certificate: + +`vault write hsmpki/sign/localhost csr=@data/localhost512.csr.pem` + +#### Generate a Key, CSR and Sign + +Ask Vault to create a new key pair, generate a CSR and sign it using the HSM, returning both the private key, the CA and the signed certificate: + +`vault write hsmpki/issue/localhost common_name=localhost` + +#### Revoke a Certificate + +`vault write hsmpki/revoke serial_number=""` + +#### View Revocation Time of Certificate + +`vault read hsmpki/cert/` + +#### View CRL + +`curl --header "X-Vault-Token: root" http://127.0.0.1:8200/v1/hsmpki/crl/pem > data/crl.txt` + +`openssl crl -in ./data/crl.txt -text` + +### Testing + +View the [TESTING](TESTING.md) README + +## License + +HSM PKI for Vault was sponsored by [BT UK](https://www.globalservices.bt.com/en/aboutus/our-services/security), developed by [mode51 Software](https://mode51.software), and contributed to the [HashiCorp community](https://www.vaultproject.io/docs/plugin-portal) under the Mozilla Public License v2. + +By [Chris Newman](https://mode51.software) diff --git a/TESTING.md b/TESTING.md new file mode 100755 index 0000000..812db8c --- /dev/null +++ b/TESTING.md @@ -0,0 +1,21 @@ +# Testing + +## HSM Notes + +If you are running SafeNet's DPoD, then cd to the base directory and source the setenv file. + +If you want to run tests in an IDE then source DPoD in a shell and start the IDE from the same shell session. + +## Makefile Command Line Tests + +Test the HSM connection where the conf is loaded in from conf/config-hsm.hcl + +`make test hsmconnection` + +`make test pathrolecreate` + +`make test pathsetcrlconfig` + +`make test pathfetchcrl` + +`make test pathsetsignedintermediate` diff --git a/bin/build.sh b/bin/build.sh new file mode 100755 index 0000000..4cdc72f --- /dev/null +++ b/bin/build.sh @@ -0,0 +1 @@ +go build -o ./bin/plugins/vaultplugin-hsmpki ./cmd/vaultplugin-hsmpki/main.go diff --git a/bin/runsigned.sh b/bin/runsigned.sh new file mode 100755 index 0000000..97bc31e --- /dev/null +++ b/bin/runsigned.sh @@ -0,0 +1,15 @@ +export VAULT_ADDR='http://127.0.0.1:8200' + +vault login root + +vault secrets enable -path=hsmpki -options="config=conf/config-softhsm.hcl" vaultplugin-hsmpki + +vault write hsmpki/roles/localhost allowed_domains=localhost allow_subdomains=true max_ttl=72h + +vault write hsmpki/intermediate/set-signed certificate=@data/softhsm-inter-0002.ca.cert.pem key_alias="RSATestCAInterKey0002" hash_algo="SHA-384" + +vault write hsmpki/config/crl expiry=48h + +#vault write hsmpki/sign/localhost csr=@data/localhost512.csr.pem + +#vault write hsmpki/issue/localhost common_name=localhost diff --git a/cmd/vaultplugin-hsmpki/main.go b/cmd/vaultplugin-hsmpki/main.go new file mode 100644 index 0000000..f713127 --- /dev/null +++ b/cmd/vaultplugin-hsmpki/main.go @@ -0,0 +1,32 @@ +package main + +import ( + hsmpki "github.com/mode51software/vaultplugin-hsmpki/pkg/hsmpki" + "os" + + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/sdk/plugin" +) + +func main() { + apiClientMeta := &api.PluginAPIClientMeta{} + flags := apiClientMeta.FlagSet() + + flags.Parse(os.Args[1:]) + + tlsConfig := apiClientMeta.GetTLSConfig() + tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig) + + err := plugin.Serve(&plugin.ServeOpts{ + BackendFactoryFunc: hsmpki.Factory, + TLSProviderFunc: tlsProviderFunc, + }) + + if err != nil { + logger := hclog.New(&hclog.LoggerOptions{}) + + logger.Error("plugin shutting down", "error", err) + os.Exit(1) + } +} diff --git a/conf/config-softhsm.hcl b/conf/config-softhsm.hcl new file mode 100644 index 0000000..20912ca --- /dev/null +++ b/conf/config-softhsm.hcl @@ -0,0 +1,8 @@ + +lib = "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so" +slot_id = 288648064 +pin = "1234" +# be aware that the key_label can be overridden by dynamically providing it during Set Signed Intermediate +key_label = "RSATestCAInterKey0002" +connect_timeout_s = 10 +read_timeout_s = 5 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d3414c9 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module github.com/mode51software/vaultplugin-hsmpki + +go 1.13 + +require ( + github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf + github.com/fatih/structs v1.1.0 + github.com/hashicorp/errwrap v1.0.0 + github.com/hashicorp/go-hclog v0.14.1 + github.com/hashicorp/hcl v1.0.1-vault + github.com/hashicorp/vault v1.6.2 + github.com/hashicorp/vault/api v1.0.5-0.20201001211907-38d91b749c77 + github.com/hashicorp/vault/sdk v0.1.14-0.20210127182440-8477cfe632c0 + github.com/ryanuber/go-glob v1.0.0 + golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 + golang.org/x/net v0.0.0-20200625001655-4c5254603344 + github.com/mode51software/pkcs11helper v0.3.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0595835 --- /dev/null +++ b/go.sum @@ -0,0 +1,1594 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0 h1:WRz29PgAsVEyPSDHyk+0fpEkwEFyfhHn+JbksT6gIL4= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.6.0/go.mod h1:hyFDG0qSGdHNz8Q6nDN8rYIkld0q/+5uBZaelxiDLfE= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/spanner v1.5.1/go.mod h1:e1+8M6PF3ntV9Xr57X2Gf+UhylXXYF6gI4WRZ1kfu2A= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk= +code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= +github.com/Azure/azure-sdk-for-go v36.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v44.0.0+incompatible h1:e82Yv2HNpS0kuyeCrV29OPKvEiqfs2/uJHic3/3iKdg= +github.com/Azure/azure-sdk-for-go v44.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-storage-blob-go v0.11.0/go.mod h1:A0u4VjtpgZJ7Y7um/+ix2DHBuEKFC6sEIlj0xc13a4Q= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.2/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= +github.com/Azure/go-autorest/autorest v0.10.1/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.0/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest v0.11.10 h1:j5sGbX7uj1ieYYkQ3Mpvewd4DCsEQ+ZeJpqnSM9pjnM= +github.com/Azure/go-autorest/autorest v0.11.10/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.6.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.7.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= +github.com/Azure/go-autorest/autorest/adal v0.9.5 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.0/go.mod h1:Oo5cRhLvZteXzI2itUm5ziqsoIxRkzrt3t61FeZaS18= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.0 h1:nSMjYIe24eBYasAIxt859TxyXef/IqoH+8/g4+LmcVs= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.0/go.mod h1:QRTvSZQpxqm8mSErhnbI+tANIBAKP7B+UIE2z4ypUO0= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.0/go.mod h1:rNYMNAefZMRowqCV0cVhr/YDW5dD7afFq9nXAXL4ykE= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.0 h1:Ml+UCrnlKD+cJmSzrZ/RDcDw86NjkRUpnFh7V5JUhzU= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.0/go.mod h1:JljT387FplPzBA31vUcvsetLKF3pec5bdAxjVU4kI2s= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/autorest/validation v0.3.0 h1:3I9AAI63HfcLtphd9g39ruUwRI+Ca+z/f36KHPFRUss= +github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Jeffail/gabs v1.1.1 h1:V0uzR08Hj22EX8+8QMhyI9sX2hwRu+/RJhJUmnwda/E= +github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Microsoft/go-winio v0.4.13/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.9 h1:VrfodqvztU8YSOvygU+DN1BGaSGxmrNfqOv5oOuX2Bk= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/SAP/go-hdb v0.14.1 h1:hkw4ozGZ/i4eak7ZuGkY5e0hxiXFdNUBNhr4AvZVNFE= +github.com/SAP/go-hdb v0.14.1/go.mod h1:7fdQLVC2lER3urZLjZCm0AuMQfApof92n3aylBPEkMo= +github.com/Sectorbob/mlab-ns2 v0.0.0-20171030222938-d3aa0c295a8a h1:KFHLI4QGttB0i7M3qOkAo8Zn/GSsxwwCnInFqBaYtkM= +github.com/Sectorbob/mlab-ns2 v0.0.0-20171030222938-d3aa0c295a8a/go.mod h1:D73UAuEPckrDorYZdtlCu2ySOLuPB5W4rhIkmmc/XbI= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14= +github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190412020505-60e2075261b6/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f h1:oRD16bhpKNAanfcDDVU+J0NXqsgHIvGbbe/sy+r6Rs0= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= +github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apple/foundationdb/bindings/go v0.0.0-20190411004307-cd5c9d91fad2/go.mod h1:OMVSB21p9+xQUIqlGizHPZfjK+SHws1ht+ZytVDoz9U= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= +github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.4 h1:Xqf+7f2Vhl9tsqDYmXhnXInUdcrtgpRNpIA15/uldSc= +github.com/armon/go-metrics v0.3.4/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e h1:h0gP0hBU6DsA5IQduhLWGOEfIUKzJS5hhXQBSgHuF/g= +github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= +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/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.41/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= +github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bitly/go-hostpool v0.1.0 h1:XKmsF6k5el6xHG3WPJ8U0Ku/ye7njX7W81Ng7O2ioR0= +github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/briankassouf/jose v0.9.2-0.20180619214549-d2569464773f h1:ZMEzE7R0WNqgbHplzSBaYJhJi5AZWTCK9baU0ebzG6g= +github.com/briankassouf/jose v0.9.2-0.20180619214549-d2569464773f/go.mod h1:HQhVmdUf7dBNwIIdBTivnCDxcf6IZY3/zrb+uKSJz6Y= +github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/centrify/cloud-golang-sdk v0.0.0-20190214225812-119110094d0f h1:gJzxrodnNd/CtPXjO3WYiakyNzHg3rtAi7rO74ejHYU= +github.com/centrify/cloud-golang-sdk v0.0.0-20190214225812-119110094d0f/go.mod h1:C0rtzmGXgN78pYR0tGJFhtHgkbAs0lIbHwkB81VxDQE= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chrismalek/oktasdk-go v0.0.0-20181212195951-3430665dfaa0 h1:CWU8piLyqoi9qXEUwzOh5KFKGgmSU5ZhktJyYcq6ryQ= +github.com/chrismalek/oktasdk-go v0.0.0-20181212195951-3430665dfaa0/go.mod h1:5d8DqS60xkj9k3aXfL3+mXBH0DPYO0FQjcKosxl+b/Q= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 h1:rdRS5BT13Iae9ssvcslol66gfOOXjaLYwqerEn/cl9s= +github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381/go.mod h1:e5+USP2j8Le2M0Jo3qKPFnNhuo1wueU4nWHCXBOfQ14= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.4 h1:3o0smo5SKY7H6AJCmJhsnCjR2/V2T8VmiHt7seN2/kI= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe h1:PEmIrUvwG9Yyv+0WKZqjXfSFDeZjs/q15g0m08BYS9k= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.0.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/couchbase/gocb/v2 v2.1.4 h1:HRuVhqZpVNIck3FwzTxWh5TnmGXeTmSfjhxkjeradLg= +github.com/couchbase/gocb/v2 v2.1.4/go.mod h1:lESKM6wCEajrFVSZUewYuRzNtuNtnRey5wOfcZZsH90= +github.com/couchbase/gocbcore/v9 v9.0.4 h1:VM7IiKoK25mq9CdFLLchJMzmHa5Grkn+94pQNaG3oc8= +github.com/couchbase/gocbcore/v9 v9.0.4/go.mod h1:jOSQeBSECyNvD7aS4lfuaw+pD5t6ciTOf8hrDP/4Nus= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg= +github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 h1:lrWnAyy/F72MbxIxFUzKmcMCdt9Oi8RzpAxzTNQHD7o= +github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/digitalocean/godo v1.7.5 h1:JOQbAO6QT1GGjor0doT0mXefX2FgUDPOpYh2RaXA+ko= +github.com/digitalocean/godo v1.7.5/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= +github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible h1:G2hY8RD7jB9QaSmcb8mYEIg8QbEvVAB7se8+lXHZHfg= +github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M= +github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/go-bindata-assetfs v1.0.1-0.20200509193318-234c15e7648f h1:AwZUiMWfYSmIiHdFJIubTSs8BFIFoMmUFbeuwBzHIPs= +github.com/elazarl/go-bindata-assetfs v1.0.1-0.20200509193318-234c15e7648f/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.4.0/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= +github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= +github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE= +github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/gammazero/deque v0.0.0-20190130191400-2afb3858e9c7 h1:D2LrfOPgGHQprIxmsTpxtzhpmF66HoM6rXSmcqaX7h8= +github.com/gammazero/deque v0.0.0-20190130191400-2afb3858e9c7/go.mod h1:GeIq9qoE43YdGnDXURnmKTnGg15pQz4mYkXSTChbneI= +github.com/gammazero/workerpool v0.0.0-20190406235159-88d534f22b56 h1:VzbudKn/nvxYKOdzgkEBS6SSreRjAgoJ+ZeS4wPFkgc= +github.com/gammazero/workerpool v0.0.0-20190406235159-88d534f22b56/go.mod h1:w9RqFVO2BM3xwWEcAB8Fwp0OviTBBEiRmSBDfbXnd3w= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= +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-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-ldap/ldap v3.0.2+incompatible h1:kD5HQcAzlQ7yrhfn+h+MSABeAy/jAJhvIJ/QDllP44g= +github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= +github.com/go-ldap/ldap/v3 v3.1.10 h1:7WsKqasmPThNvdl0Q5GPpbTDD/ZD98CfuawrMIuh7qQ= +github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +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/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= +github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= +github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gocql/gocql v0.0.0-20200624222514-34081eda590e h1:SroDcndcOU9BVAduPf/PXihXoR2ZYTQYLXbupbqxAyQ= +github.com/gocql/gocql v0.0.0-20200624222514-34081eda590e/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +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/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-metrics-stackdriver v0.2.0 h1:rbs2sxHAPn2OtUj9JdR/Gij1YKGl0BTVD0augB+HEjE= +github.com/google/go-metrics-stackdriver v0.2.0/go.mod h1:KLcPyp3dWJAFD+yHisGlJSZktIsTjb50eB72U2YZ9K0= +github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= +github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 h1:f0n1xnMSmBLzVfsMMvriDyA75NB/oBgILX2GcHXIQzY= +github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= +github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/consul-template v0.25.1/go.mod h1:/vUsrJvDuuQHcxEw0zik+YXTS7ZKWZjQeaQhshBmfH0= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/api v1.4.0 h1:jfESivXnO5uLdH650JU/6AnjRoHrLhULq0FnC3Kp9EY= +github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.4.0 h1:zBtCfKJZcJDBvSCkQJch4ulp59m1rATFLKwNo/LYY30= +github.com/hashicorp/consul/sdk v0.4.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= +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-bindata v3.0.8-0.20180209072458-bf7910af8997+incompatible/go.mod h1:+IrDq36jUYG0q6TsDY9uO2p77C8f8S5y+RbYHr2UI+U= +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-discover v0.0.0-20200812215701-c4b85f6ed31f h1:7WFMVeuJQp6BkzuTv9O52pzwtEFVUJubKYN+zez8eTI= +github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f/go.mod h1:D4eo8/CN92vm9/9UDG+ldX1/fMFa4kpl8qzyTolus8o= +github.com/hashicorp/go-gatedio v0.5.0/go.mod h1:Lr3t8L6IyxD3DAeaUxGcgl2JnRUpWMCsmBl4Omu/2t4= +github.com/hashicorp/go-gcp-common v0.5.0/go.mod h1:IDGUI2N/OS3PiU4qZcXJeWKPI6O/9Y8hOrbSiMcqyYw= +github.com/hashicorp/go-gcp-common v0.6.0 h1:m1X+DK003bj4WEjqOnv+Csepb3zpfN/bidboUeUSj68= +github.com/hashicorp/go-gcp-common v0.6.0/go.mod h1:RuZi18562/z30wxOzpjeRrGcmk9Ro/rBzixaSZDhIhY= +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.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= +github.com/hashicorp/go-hclog v0.14.1/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-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc= +github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-kms-wrapping v0.5.16 h1:7qvB7JYLFART/bt1wafobMU5dDeyseE3ZBKB6UiyxWs= +github.com/hashicorp/go-kms-wrapping v0.5.16/go.mod h1:lxD7e9q7ZyCtDEP+tnMevsEvw3M0gmZnneAgv8BaO1Q= +github.com/hashicorp/go-kms-wrapping/entropy v0.1.0 h1:xuTi5ZwjimfpvpL09jDE71smCBRpnF5xfo871BSX4gs= +github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= +github.com/hashicorp/go-memdb v1.0.2 h1:AIjzJlwIxz2inhZqRJZfe6D15lPeF0/cZyS1BVlnlHg= +github.com/hashicorp/go-memdb v1.0.2/go.mod h1:I6dKdmYhZqU0RJSheVEWgTNWdVQH5QvTgIUQ0t/t32M= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +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-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-plugin v1.0.0 h1:/gQ1sNR8/LHpoxKRQq4PmLBuacfZb4tC93e9B30o/7c= +github.com/hashicorp/go-plugin v1.0.0/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +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-raftchunking v0.6.3-0.20191002164813-7e9e8525653a h1:FmnBDwGwlTgugDGbVxwV8UavqSMACbGrUpfc98yFLR4= +github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a/go.mod h1:xbXnmKqX9/+RhPkJ4zrEx4738HacP72aaUPlT2RZ4sU= +github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.6.7 h1:8/CAEZt/+F7kR7GevNHulKkUjLht3CPmn7egmhieNKo= +github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +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-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= +github.com/hashicorp/go-uuid v1.0.1/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.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +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/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +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/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +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/hcl v1.0.1-vault h1:UiJeEzCWAYdVaJr8Xo4lBkTozlW1+1yxVUnpbS1xVEk= +github.com/hashicorp/hcl v1.0.1-vault/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1 h1:XFSOubp8KWB+Jd2PDyaX5xUd5bhSP/+pTDZVDMzZJM8= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.1.4 h1:gkyML/r71w3FL8gUi74Vk76avkj/9lYAY9lvg0OcoGs= +github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/nomad/api v0.0.0-20191220223628-edc62acd919d h1:BXqsASWhyiAiEVm6FcltF0dg8XvoookQwmpHn8lstu8= +github.com/hashicorp/nomad/api v0.0.0-20191220223628-edc62acd919d/go.mod h1:WKCL+tLVhN1D+APwH3JiTRZoxcdwRk86bWu1LVCUPaE= +github.com/hashicorp/raft v1.0.1/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI= +github.com/hashicorp/raft v1.1.2-0.20191002163536-9c6bd3e3eb17/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= +github.com/hashicorp/raft v1.1.3-0.20201002073007-f367681f9c48 h1:TpaG+HAdfQyreWNaxIlMU6myVKo2ciBDFdRyc+Z90OI= +github.com/hashicorp/raft v1.1.3-0.20201002073007-f367681f9c48/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= +github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= +github.com/hashicorp/raft-snapshot v1.0.3 h1:lTgBBGMFcuKBTwHqWZ4r0TLzNsqo/OByCga/kM6F0uM= +github.com/hashicorp/raft-snapshot v1.0.3/go.mod h1:5sL9eUn72lH5DzsFIJ9jaysITbHksSSszImWSOTC8Ic= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.8.3 h1:MWYcmct5EtKz0efYooPcL0yNkem+7kWxqXDi/UIh+8k= +github.com/hashicorp/serf v0.8.3/go.mod h1:UpNcs7fFbpKIyZaUuSW6EPiH+eZC7OuyFD+wc1oal+k= +github.com/hashicorp/vault v1.6.2 h1:u/msz3iBUhpVI+/gCFZ2U1bPHeiFoRkQDdyJREOH4bU= +github.com/hashicorp/vault v1.6.2/go.mod h1:n4+hyvzanpDVv9cAWJbAFVv7F1fdJlNxEA7U6wP1CIA= +github.com/hashicorp/vault-plugin-auth-alicloud v0.7.0 h1:qCTaVIb+FUWatLXszACH47Q/yTMrbedHt0CMGX9X41w= +github.com/hashicorp/vault-plugin-auth-alicloud v0.7.0/go.mod h1:lyfBMcULXKJfVu8dNo1w9bUikV5oem5HKmJoWwXupnY= +github.com/hashicorp/vault-plugin-auth-azure v0.6.0 h1:LWUAYV+Td30+AnwjlBiYDk3j4XMDKOyk+OZyBSXU8tk= +github.com/hashicorp/vault-plugin-auth-azure v0.6.0/go.mod h1:xUpcusehlkR1cNbA+wNta9W4slS64pYe7CXx5UYM2OQ= +github.com/hashicorp/vault-plugin-auth-centrify v0.7.0 h1:VBAijPjklpKomM+hxmR/rqZG/aZFJe+V5NxbFcZmRqI= +github.com/hashicorp/vault-plugin-auth-centrify v0.7.0/go.mod h1:tLY05v1tC+sfeeE6DF8RAC/MGw4gflomYfA28b4VULw= +github.com/hashicorp/vault-plugin-auth-cf v0.7.0 h1:DiL63W12LnYxInY3xjCRprlHNkNw+g5v38nh3o9yf38= +github.com/hashicorp/vault-plugin-auth-cf v0.7.0/go.mod h1:exPUMj8yNohKM7yRiHa7OfxQmyDI9Pj8+08qB4hGlVw= +github.com/hashicorp/vault-plugin-auth-gcp v0.5.1/go.mod h1:eLj92eX8MPI4vY1jaazVLF2sVbSAJ3LRHLRhF/pUmlI= +github.com/hashicorp/vault-plugin-auth-gcp v0.8.0 h1:E9EHvC9jCDNix/pB9NKYYLMUkpfv65TSDk2rVvtkdzU= +github.com/hashicorp/vault-plugin-auth-gcp v0.8.0/go.mod h1:sHDguHmyGScoalGLEjuxvDCrMPVlw2c3f+ieeiHcv6w= +github.com/hashicorp/vault-plugin-auth-jwt v0.8.1 h1:6EE5drejNWoIDj6akBCHrTaaQDq8lOVlG1BUQFsMk6w= +github.com/hashicorp/vault-plugin-auth-jwt v0.8.1/go.mod h1:pyR4z5f2Vuz9TXucuN0rivUJTtSdlOtDdZ16IqBjZVo= +github.com/hashicorp/vault-plugin-auth-kerberos v0.2.0 h1:7ct50ngVFTeO7EJ3N9PvPHeHc+2cANTHi2+9RwIUIHM= +github.com/hashicorp/vault-plugin-auth-kerberos v0.2.0/go.mod h1:IM/n7LY1rIM4MVzOfSH6cRmY/C2rGkrjGrEr0B/yO9c= +github.com/hashicorp/vault-plugin-auth-kubernetes v0.8.0 h1:v1jOqR70chxRxONey7g/v0/57MneP05z2dfw6qmlE+8= +github.com/hashicorp/vault-plugin-auth-kubernetes v0.8.0/go.mod h1:2c/k3nsoGPKV+zpAWCiajt4e66vncEq8Li/eKLqErAc= +github.com/hashicorp/vault-plugin-auth-oci v0.6.0 h1:ag69AcGbWvFADQ0TQxiJiJAztCiY5/CXMItF02oi5oY= +github.com/hashicorp/vault-plugin-auth-oci v0.6.0/go.mod h1:Cn5cjR279Y+snw8LTaiLTko3KGrbigRbsQPOd2D5xDw= +github.com/hashicorp/vault-plugin-database-couchbase v0.2.1 h1:WIxp5tCiDZqmd01h9WCcD+wMum+A9KKi/4qIebrxWD8= +github.com/hashicorp/vault-plugin-database-couchbase v0.2.1/go.mod h1:/746Pabh8/0b/4vEcJWYYVgiCaGgM4ntk1ULuxk9Uuw= +github.com/hashicorp/vault-plugin-database-elasticsearch v0.6.1 h1:C3NF3pVF7/Emxy2r6nPDkR5Njfh+uviFggcr4yHaDhs= +github.com/hashicorp/vault-plugin-database-elasticsearch v0.6.1/go.mod h1:813Nvr1IQqAKdlk3yIY97M5WyxMhWOrXtYioPf9PqJg= +github.com/hashicorp/vault-plugin-database-mongodbatlas v0.2.1 h1:Yc8ZJJINvCH6JcJ8uvNkZ6W33KYzVdG4zI98dvbQ8lE= +github.com/hashicorp/vault-plugin-database-mongodbatlas v0.2.1/go.mod h1:2So20ndRRsDAMDyG52az6nd7NwFOZTQER9EsrgPCgVg= +github.com/hashicorp/vault-plugin-mock v0.16.1 h1:5QQvSUHxDjEEbrd2REOeacqyJnCLPD51IQzy71hx8P0= +github.com/hashicorp/vault-plugin-mock v0.16.1/go.mod h1:83G4JKlOwUtxVourn5euQfze3ZWyXcUiLj2wqrKSDIM= +github.com/hashicorp/vault-plugin-secrets-ad v0.8.0 h1:SBOxEjYtxkipmp8AC7Yy3vjBVM5H24YM48WKCSCV+zA= +github.com/hashicorp/vault-plugin-secrets-ad v0.8.0/go.mod h1:L5L6NoJFxRvgxhuA2sWhloc3sbgmE7KxhNcoRxcaH9U= +github.com/hashicorp/vault-plugin-secrets-alicloud v0.7.0 h1:VoB3Q11LX+wF5w5TC8j3LYh6PNfeL1XM0zQAMaT04sY= +github.com/hashicorp/vault-plugin-secrets-alicloud v0.7.0/go.mod h1:SSkKpSTOMnX84PfgYiWHgwVg+YMhxHNjo+YCJGNBoZk= +github.com/hashicorp/vault-plugin-secrets-azure v0.8.0 h1:3BAhoqqDN198vynAfS3rcxUW2STBjREluGPsYCOy2mA= +github.com/hashicorp/vault-plugin-secrets-azure v0.8.0/go.mod h1:4jCVjTG809NCQ8mrSnbBtX17gX1Iush+558BVO6MJeo= +github.com/hashicorp/vault-plugin-secrets-gcp v0.8.2 h1:iZDbNQnlNzwXnuUwOykQ54ReRmKc0VoxXz/b8V48EDM= +github.com/hashicorp/vault-plugin-secrets-gcp v0.8.2/go.mod h1:psRQ/dm5XatoUKLDUeWrpP9icMJNtu/jmscUr37YGK4= +github.com/hashicorp/vault-plugin-secrets-gcpkms v0.7.0 h1:dKPQIr6tLcMmhNKdc2A9pbwaIFLooC80UfNZL+jWMlA= +github.com/hashicorp/vault-plugin-secrets-gcpkms v0.7.0/go.mod h1:hhwps56f2ATeC4Smgghrc5JH9dXR31b4ehSf1HblP5Q= +github.com/hashicorp/vault-plugin-secrets-kv v0.7.0 h1:Sq5CmKWxQu+MtO6AXYM+STPHGnrGD50iKuwzaw87OVM= +github.com/hashicorp/vault-plugin-secrets-kv v0.7.0/go.mod h1:B/Cybh5aVF7LNAMHwVBxY8t7r2eL0C6HVGgTyP4nKK4= +github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.2.0 h1:uTtKxt5qfwTj6PqwnwPdU0fg1lIaaoqTtauuNpI2Epc= +github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.2.0/go.mod h1:JOqn2mWJJbTp9NaC0CSCc3q5HQA99LfeSqgpC3YS+oA= +github.com/hashicorp/vault-plugin-secrets-openldap v0.3.0 h1:aDdWZMdr93OtwZRE3TPKJyZgY6ZTe09G7bb2GL1HeAo= +github.com/hashicorp/vault-plugin-secrets-openldap v0.3.0/go.mod h1:pRE6pJzEVTSn3reeEn6YOV73R/6PcyylwQBz33zZKds= +github.com/hashicorp/vault/api v1.0.1/go.mod h1:AV/+M5VPDpB90arloVX0rVDUIHkONiwz5Uza9HRtpUE= +github.com/hashicorp/vault/api v1.0.5-0.20190730042357-746c0b111519/go.mod h1:i9PKqwFko/s/aihU1uuHGh/FaQS+Xcgvd9dvnfAvQb0= +github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o= +github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE= +github.com/hashicorp/vault/api v1.0.5-0.20200805123347-1ef507638af6/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk= +github.com/hashicorp/vault/api v1.0.5-0.20200826195146-c03009a7e370/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk= +github.com/hashicorp/vault/api v1.0.5-0.20201001211907-38d91b749c77 h1:8e51fWpYe9bnbnPZIxcGnnrHo5iKyk2ToqAHGUZC6vM= +github.com/hashicorp/vault/api v1.0.5-0.20201001211907-38d91b749c77/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk= +github.com/hashicorp/vault/sdk v0.1.8/go.mod h1:tHZfc6St71twLizWNHvnnbiGFo1aq0eD2jGPLtP8kAU= +github.com/hashicorp/vault/sdk v0.1.14-0.20190730042320-0dc007d98cc8/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/vault/sdk v0.1.14-0.20200215195600-2ca765f0a500/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.1.14-0.20200305172021-03a3749f220d/go.mod h1:PcekaFGiPJyHnFy+NZhP6ll650zEw51Ag7g/YEa+EOU= +github.com/hashicorp/vault/sdk v0.1.14-0.20200427170607-03332aaf8d18/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.1.14-0.20200527182800-ad90e0b39d2f/go.mod h1:B2Cbv/tzj8btUA5FF4SvYclTujJhlWU6siK4vo8tgXM= +github.com/hashicorp/vault/sdk v0.1.14-0.20200826195146-c03009a7e370/go.mod h1:+S2qzS1Tex9JgbHxb/Jv7CdZyKydxqg09G/qVvyVmUc= +github.com/hashicorp/vault/sdk v0.1.14-0.20200916184745-5576096032f8/go.mod h1:7GBJyKruotYxJlye8yHyGICV7kN7dQCNsCMTrb+v5J0= +github.com/hashicorp/vault/sdk v0.1.14-0.20201022214319-d87657199d4b/go.mod h1:cAGI4nVnEfAyMeqt9oB+Mase8DNn3qA/LDNHURiwssY= +github.com/hashicorp/vault/sdk v0.1.14-0.20210127182440-8477cfe632c0 h1:NLjrAV3dCTawzKwYF63HOLf2rYvIfUQ3j3ikaLPLZrk= +github.com/hashicorp/vault/sdk v0.1.14-0.20210127182440-8477cfe632c0/go.mod h1:cAGI4nVnEfAyMeqt9oB+Mase8DNn3qA/LDNHURiwssY= +github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyuqg7yuAWUg/jMZR1/0QTzTRdNR6Uw= +github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= +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/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huaweicloud/golangsdk v0.0.0-20200304081349-45ec0797f2a4/go.mod h1:WQBcHRNX9shz3928lWEvstQJtAtYI7ks6XlgtRT9Tcw= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4 h1:3K3KcD4S6/Y2hevi70EzUTNKOS3cryQyhUnkjE6Tz0w= +github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= +github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jarcoal/httpmock v1.0.5 h1:cHtVEcTxRSX4J0je7mWPfc9BpDpqzXSJ5HbymZmyHck= +github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jcmturner/aescts v1.0.1 h1:5jhUSHbHSZjQeWFY//Lv8dpP/O3sMDOxrGV/IfCqh44= +github.com/jcmturner/aescts v1.0.1/go.mod h1:k9gJoDUf1GH5r2IBtBjwjDCoLELYxOcEhitdP8RL7qQ= +github.com/jcmturner/dnsutils v1.0.1 h1:zkF8SbVatbr5LGrvcPSes62SV68lASVv6+x9wo2De+w= +github.com/jcmturner/dnsutils v1.0.1/go.mod h1:tqMo38L01jO8AKxT0S9OQVlGZu3dkEt+z5CA+LOhwB0= +github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.0.0 h1:jNMtRRdNeZDFUNUX+ifpDcQzPS9nZlZH47JNyGIzdeE= +github.com/jcmturner/gokrb5/v8 v8.0.0/go.mod h1:4/sqKY8Yzo/TIQ8MoCyk/EPcjb+czI9czxHcdXuZbFA= +github.com/jcmturner/rpc/v2 v2.0.2 h1:gMB4IwRXYsWw4Bc6o/az2HJgFUA1ffSh90i26ZJ6Xl0= +github.com/jcmturner/rpc/v2 v2.0.2/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jeffchao/backoff v0.0.0-20140404060208-9d7fd7aa17f2 h1:mex1izRBCD+7WjieGgRdy7e651vD/lvB1bD9vNE/3K4= +github.com/jeffchao/backoff v0.0.0-20140404060208-9d7fd7aa17f2/go.mod h1:xkfESuHriIekR+4RoV+fu91j/CfnYM29Zi2tMFw5iD4= +github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4= +github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00= +github.com/jefferai/jsonx v1.0.0 h1:Xoz0ZbmkpBvED5W9W1B5B/zc3Oiq7oXqiW7iRV3B6EI= +github.com/jefferai/jsonx v1.0.0/go.mod h1:OGmqmi2tTeI/PS+qQfBDToLHHJIy/RMp24fPo8vFvoQ= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= +github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f h1:ENpDacvnr8faw5ugQmEF1QYk+f/Y9lXFvuYmRxykago= +github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/keybase/go-crypto v0.0.0-20190403132359-d65b6b94177f h1:Gsc9mVHLRqBjMgdQCghN9NObCcRncDqxJvBvEaIIQEo= +github.com/keybase/go-crypto v0.0.0-20190403132359-d65b6b94177f/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/linode/linodego v0.7.1 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE= +github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw= +github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/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/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= +github.com/michaelklishin/rabbit-hole v0.0.0-20191008194146-93d9988f0cd5 h1:uA3b4GgZMZxAJsTkd+CVQ85b7KBlD7HLpd/FfTNlGN0= +github.com/michaelklishin/rabbit-hole v0.0.0-20191008194146-93d9988f0cd5/go.mod h1:+pmbihVqjC3GPdfWv1V2TnRSuVvwrWLKfEP/MZVB/Wc= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.15 h1:CSSIDtllwGLMoA6zjdKnaE6Tx6eVUxQ29LUgGetiDCI= +github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.1 h1:J64v/xD7Clql+JVKSvkYojLOXu1ibnY9ZjGLwSt/89w= +github.com/mitchellh/cli v1.1.1/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +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.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +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/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= +github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.0.0 h1:ATSdz4NWrmWPOF1CeCBU4sMCno2hgqdbSrRPFWQSVZI= +github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2 h1:SPoLlS9qUUnXcIY4pvA4CTwYjk0Is5f4UPEkeESr53k= +github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2/go.mod h1:TjQg8pa4iejrUrjiz0MCtMV38jdMNW4doKSiBrEvCQQ= +github.com/mode51software/pkcs11helper v0.3.0 h1:MmEUwt5HhI4hR/60LyftLdYEa8+S+UYxF6AAz+pXs0c= +github.com/mode51software/pkcs11helper v0.3.0/go.mod h1:kTP66TAfYMJTuND7oT1y4FWyTmAtkJh/b7QwtH1VOcI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mongodb/go-client-mongodb-atlas v0.1.2/go.mod h1:LS8O0YLkA+sbtOb3fZLF10yY3tJM+1xATXMJ3oU35LU= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwielbut/pointy v1.1.0/go.mod h1:MvvO+uMFj9T5DMda33HlvogsFBX7pWWKAkFIn4teYwY= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/natefinch/atomic v0.0.0-20150920032501-a62ce929ffcc/go.mod h1:1rLVY/DWf3U6vSZgH16S7pymfrhK2lcUlXjgGglw/lY= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= +github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/okta/okta-sdk-golang v1.1.0 h1:sr/KYSMRhs4F2NWEbqWXqN4y4cKKcfzrtOiBqR/J6mI= +github.com/okta/okta-sdk-golang v1.1.0/go.mod h1:KEjmr3Zo+wP3gVa3XhwIvENBfh7L/iRUeIl6ruQYOK0= +github.com/okta/okta-sdk-golang/v2 v2.0.0 h1:qwl5Ezpy5a3I2WphiHolpgTtOC+YMTDIpFqOHmfiAGs= +github.com/okta/okta-sdk-golang/v2 v2.0.0/go.mod h1:fQubbeV8gksr8e1pmRVSE8kIj1TFqlgYqi8WsvSKmQk= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9 h1:/k06BMULKF5hidyoZymkoDCzdJzltZpz/UU4LguQVtc= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/openlyinc/pointy v1.1.2 h1:LywVV2BWC5Sp5v7FoP4bUD+2Yn5k0VNeRbU5vq9jUMY= +github.com/openlyinc/pointy v1.1.2/go.mod h1:w2Sytx+0FVuMKn37xpXIAyBNhFNBIJGR/v2m7ik1WtM= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= +github.com/oracle/oci-go-sdk v12.5.0+incompatible h1:pr08ECoaDKHWO9tnzJB1YqClEs7ZK1CFOez2DQocH14= +github.com/oracle/oci-go-sdk v12.5.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= +github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/ory/dockertest/v3 v3.6.2 h1:Q3Y8naCMyC1Nw91BHum1bGyEsNQc/UOIYS3ZoPoou0g= +github.com/ory/dockertest/v3 v3.6.2/go.mod h1:EFLcVUOl8qCwp9NyDAcCDtq/QviLtYswW/VbWzUnTNE= +github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso= +github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0= +github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c h1:vwpFWvAO8DeIZfFeqASzZfsxuWPno9ncAebBEP0N3uE= +github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= +github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +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/pierrec/lz4 v2.2.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= +github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +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/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6DI= +github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/pquerna/otp v1.2.1-0.20191009055518-468c2dd2b58d h1:PinQItctnaL2LtkaSM678+ZLLy5TajwOeXzWvYC7tII= +github.com/pquerna/otp v1.2.1-0.20191009055518-468c2dd2b58d/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.11.1 h1:0ZISXCMRuCZcxF77aT1BXY5m74mX2vrGYl1dSwBI0Jo= +github.com/prometheus/common v0.11.1/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rboyer/safeio v0.2.1 h1:05xhhdRNAdS3apYm7JRjOqngf4xruaW959jmRxGDuSU= +github.com/rboyer/safeio v0.2.1/go.mod h1:Cq/cEPK+YXFn622lsQ0K4KsPZSPtaptHHEldsy7Fmig= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o= +github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs= +github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +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/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y= +github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= +github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sethvargo/go-limiter v0.3.0 h1:yRMc+Qs2yqw6YJp6UxrO2iUs6DOSq4zcnljbB7/rMns= +github.com/sethvargo/go-limiter v0.3.0/go.mod h1:C0kbSFbiriE5k2FFOe18M1YZbAR2Fiwf72uGu0CXCcU= +github.com/shirou/gopsutil v2.20.9+incompatible h1:msXs2frUV+O/JLva9EDLpuJ84PrFsdCTCQex8PUdtkQ= +github.com/shirou/gopsutil v2.20.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180725160413-e900ae048470/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d h1:bVQRCxQvfjNUeRqaY/uT0tFuvuFY0ulgnczuR684Xic= +github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/square/go-jose v2.4.1+incompatible/go.mod h1:7MxpAF/1WTVUu8Am+T5kNy+t0902CaLWM4Z745MkOa8= +github.com/square/go-jose/v3 v3.0.0-20200225220504-708a9fe87ddc/go.mod h1:JbpHhNyeVc538vtj/ECJ3gPYm1VEitNjsLhm4eJQQbg= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271 h1:WhxRHzgeVGETMlmVfqhRn8RIeeNoPr2Czh33I4Zdccw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.171+incompatible h1:K3fcS92NS8cRntIdu8Uqy2ZSePvX73nNhOkKuPGJLXQ= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.171+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8= +github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= +github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yandex-cloud/go-genproto v0.0.0-20200722140432-762fe965ce77/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= +github.com/yandex-cloud/go-sdk v0.0.0-20200722140627-2194e5077f13/go.mod h1:LEdAMqa1v/7KYe4b13ALLkonuDxLph57ibUb50ctvJk= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200425165423-262c93980547/go.mod h1:YoUyTScD3Vcv2RBm3eGVOq7i1ULiz3OuXoQFWOirmAM= +go.mongodb.org/atlas v0.5.0 h1:WoeXdXSLCyquhSqSqTa0PwpjxWZbm70E1Gkbx+w3Sco= +go.mongodb.org/atlas v0.5.0/go.mod h1:CIaBeO8GLHhtYLw7xSSXsw7N90Z4MFY87Oy9qcPyuEs= +go.mongodb.org/mongo-driver v1.4.2 h1:WlnEglfTg/PfPq4WXs2Vkl/5ICC6hoG8+r+LraPmGk4= +go.mongodb.org/mongo-driver v1.4.2/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= +go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= +go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +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/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +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-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/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-20190209173611-3b5209105503/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a h1:i47hUS795cOydZI4AwJQCKXOr4BvxzvikwDoDtHhP2Y= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190718200317-82a3ea8a504c/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200409170454-77362c5149f0/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200416214402-fc959738d646/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200521155704-91d71f6c2f04 h1:LbW6ziLoA0vw8ZR4bmjKAzgEpunUBaEX1ia1Q1jEGC4= +golang.org/x/tools v0.0.0-20200521155704-91d71f6c2f04/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU= +google.golang.org/api v0.3.0/go.mod h1:IuvZyQh8jgscv8qWfQ4ABd8m7hEudgBFM/EdhA3BnXw= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +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/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190513181449-d00d292a067c/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200323114720-3f67cca34472/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200416231807-8751e049a2a0/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +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/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI= +gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= +gopkg.in/ldap.v3 v3.0.3 h1:YKRHW/2sIl05JsCtx/5ZuUueFuJyoj/6+DGXe3wp6ro= +gopkg.in/ldap.v3 v3.0.3/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/ory-am/dockertest.v3 v3.3.4/go.mod h1:s9mmoLkaGeAh97qygnNj4xWkiN7e1SKekYC6CovU+ek= +gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.3.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +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/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.0.0-20190409092523-d687e77c8ae9/go.mod h1:FQEUn50aaytlU65qqBn/w+5ugllHwrBzKm7DzbnXdzE= +k8s.io/api v0.18.2 h1:wG5g5ZmSVgm5B+eHMIbI9EGATS2L8Z72rda19RIEgY8= +k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= +k8s.io/apimachinery v0.0.0-20190409092423-760d1845f48b/go.mod h1:FW86P8YXVLsbuplGMZeb20J3jYHscrDqw4jELaFJvRU= +k8s.io/apimachinery v0.18.2 h1:44CmtbmkzVDAhCpRVSiP2R5PPrC2RtlIv/MoB8xpdRA= +k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE= +k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.0.0-20190306015804-8e90cee79f82/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +layeh.com/radius v0.0.0-20190322222518-890bc1058917 h1:BDXFaFzUt5EIqe/4wrTc4AcYZWP6iC6Ult+jQWLh5eU= +layeh.com/radius v0.0.0-20190322222518-890bc1058917/go.mod h1:fywZKyu//X7iRzaxLgPWsvc0L26IUpVvE/aeIL2JtIQ= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/pkg/hsmpki/backend.go b/pkg/hsmpki/backend.go new file mode 100644 index 0000000..6c08888 --- /dev/null +++ b/pkg/hsmpki/backend.go @@ -0,0 +1,249 @@ +package hsmpki + +import ( + "context" + "crypto" + "errors" + "fmt" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/hcl" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" + "github.com/mode51software/pkcs11helper/pkg/pkcs11client" + "github.com/mode51software/vaultplugin-hsmpki/pkg/pki" + "io/ioutil" + "strings" + "sync" + "time" +) + +type cachedCAConfig struct { + caKeyAlias string + hashAlgo crypto.Hash +} + +type HsmPkiBackend struct { + pkiBackend pki.PkiBackend + HsmBackend *framework.Backend + pkcs11client pkcs11client.Pkcs11Client + cachedCAConfig cachedCAConfig // needs to be re-cached on startup + refreshMutex sync.Mutex + store map[string][]byte + // this is the same storage as in the pki Backend, so ref here for convenience + //storage logical.Storage +} + +var _ logical.Factory = Factory + +// Factory configures and returns backends +func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) { + + b, err := Backend(conf) + if err != nil { + return nil, err + } + if err = b.pkiBackend.Backend.Setup(ctx, conf); err != nil { + return nil, err + } + + if confFile, ok := conf.Config[CONFIG_PARAM]; ok { + + b.pkiBackend.Backend.Logger().Info("found conf: " + confFile) + + // check the conf is valid + if err = b.loadConf(confFile); err != nil { + return nil, errwrap.Wrapf("Conf file error: {{err}}", err) + } + b.loadStorage() + + b.configurePkcs11Connection() + b.checkPkcs11ConnectionAsync() //; err != nil { + // b.pkiBackend.Backend.Logger().Error("PKCS#11 connection timed out on startup, will retry on request") + //} + + return b.pkiBackend.Backend, nil + + } else { + for key, value := range conf.Config { + b.pkiBackend.Backend.Logger().Info("Conf key=" + key + " Val=" + value) + } + b.pkiBackend.Backend.Logger().Error("Please add a parameter specifying a file containing the HSM's configuration. Plugin mounting aborted.") + // infer whether the plugin is being registered in the catalog or being mounted by checking for the presence of plugin_name + // there may be a better way to do this! + if _, ok := conf.Config[CONFIG_PLUGIN_NAME]; ok { + // if the plugin is being mounted and the config file hasn't been included then fail with a message + return nil, errors.New("Please add a parameter specifying a file containing the HSM's configuration. Plugin mounting aborted.") + } else { + // a Backend is returned here so that the plugin can be registered in the catalog + return b.pkiBackend.Backend, nil + } + } + +} + +func Backend(conf *logical.BackendConfig) (*HsmPkiBackend, error) { + b := &HsmPkiBackend{ + //keyConfig: pkcs11client.KeyConfig{Label: "SSL Root CA 02", Type: pkcs11client.CKK_RSA }, + // keyConfig: pkcs11client.KeyConfig{Id: "0007", Type: pkcs11.CKK_RSA}, + store: make(map[string][]byte), + } + + b.pkiBackend.Backend.Backend = &framework.Backend{ + Help: strings.TrimSpace(PLUGIN_HELP), + BackendType: logical.TypeLogical, + Paths: []*framework.Path{ + pki.PathListRoles(&b.pkiBackend.Backend), + pki.PathRoles(&b.pkiBackend.Backend), + //pki.PathGenerateRoot(&b.pkiBackend.Backend), + //pki.PathGenerateIntermediate(&b.pkiBackend.Backend), + //pki.PathSignIntermediate(&b.pkiBackend.Backend), + pathSetSignedIntermediate(b), + pathSign(b), + pathIssue(b), + pki.PathFetchListCerts(&b.pkiBackend.Backend), + pki.PathFetchValid(&b.pkiBackend.Backend), + pki.PathFetchCA(&b.pkiBackend.Backend), + pki.PathFetchCAChain(&b.pkiBackend.Backend), + pki.PathConfigCRL(&b.pkiBackend.Backend), + pki.PathFetchCRL(&b.pkiBackend.Backend), + pki.PathFetchCRLViaCertPath(&b.pkiBackend.Backend), + pathRevoke(b), + pathFetchCAKeyAlias(b), + // TODO: TIdy and Sign Verbatim + }, + PathsSpecial: &logical.Paths{ + Unauthenticated: []string{ + "cert/*", + "ca/pem", + "ca_chain", + "ca", + "crl/pem", + "crl", + }, + + LocalStorage: []string{ + "revoked/", + "crl", + "certs/", + }, + + SealWrapStorage: []string{ + "config/ca_bundle", + }, + }, + + Secrets: []*framework.Secret{ + pki.SecretCerts(&b.pkiBackend.Backend), + }, + } + b.pkiBackend.SetCrlLifetime(time.Hour * DEFAULT_CRL_LIFETIME) + b.pkiBackend.SetStorage(conf.StorageView) + b.pkiBackend.CreateTidyCASGuard() + + return b, nil +} + +func (b *HsmPkiBackend) configurePkcs11Connection() { + b.pkcs11client.HsmConfig.CheckSetDefaultTimeouts() +} + +// on startup attempt to connect asynchronously +func (b *HsmPkiBackend) checkPkcs11ConnectionAsync() { + + go func() { + // b.refreshMutex.Lock() + // defer b.refreshMutex.Unlock() + //b.pkiBackend.Backend.Logger().Info("Attempt to reconnect PKCS#11 connection") + + //b.pkcs11client.Pkcs11Mutex.Lock() + //b.pkcs11client.FlushSession() + //b.pkcs11client.Pkcs11Mutex.Unlock() + + b.checkPkcs11ConnectionSync() + }() + return +} + +func (b *HsmPkiBackend) checkPkcs11ConnectionFailed() { + b.refreshMutex.Lock() + defer b.refreshMutex.Unlock() + + if b.pkcs11client.ConnectionState != pkcs11client.PKCS11CONNECTION_INPROGRESS && + b.pkcs11client.ConnectionState != pkcs11client.PKCS11CONNECTION_SUCCEEDED { + b.pkcs11client.Pkcs11Mutex.Lock() + b.pkcs11client.FlushSession() + b.pkcs11client.Pkcs11Mutex.Unlock() + //b.checkPkcs11ConnectionAsync() + } +} + +// Synchronously check the PKCS#11 connection +func (b *HsmPkiBackend) checkPkcs11ConnectionSync() (err error) { + + b.refreshMutex.Lock() + defer b.refreshMutex.Unlock() + + if b.pkcs11client.ConnectionState != pkcs11client.PKCS11CONNECTION_SUCCEEDED && + b.pkcs11client.ConnectionState != pkcs11client.PKCS11CONNECTION_INPROGRESS { + + b.pkiBackend.Backend.Logger().Info("Attempt to connect PKCS#11 connection") + + if err = b.pkcs11client.InitAndLoginWithTimeout(); err != nil { + b.pkiBackend.Backend.Logger().Info("PKCS#11 connection error") + } else { + b.pkiBackend.Backend.Logger().Info("PKCS#11 connection successful") + } + } + return +} + +// conf params stored in the conf file that is specified when the secrets engine is enabled +func (b *HsmPkiBackend) loadConf(filename string) error { + + data, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + + tree, err := hcl.ParseBytes(data) + if err != nil { + return err + } + + var hsmPkiConfig HsmPkiConfig + if err = hcl.DecodeObject(&hsmPkiConfig, tree.Node); err != nil { + b.pkiBackend.Backend.Logger().Error(err.Error()) + return err + } + + // the local version of HsmConfig is needed because hcl needs different annotations and doesn't parse uints (?) + b.pkcs11client.HsmConfig = hsmPkiConfig.ConvertToHsmConfig() + + if err = b.pkcs11client.HsmConfig.ValidateConfig(); err != nil { + // if err = b.hsmPkiConfig.validateConfig(); err != nil { + return err + } else { + msg := fmt.Sprintf("Loaded conf file lib %s slot %d label %s", + b.pkcs11client.HsmConfig.Lib, b.pkcs11client.HsmConfig.SlotId, b.pkcs11client.HsmConfig.KeyLabel) + b.pkiBackend.Backend.Logger().Info(msg) + b.pkcs11client.HsmConfig.CheckSetDefaultTimeouts() + return nil + } +} + +// conf items that can be set dynamically +// load in the key label override if it has been set during Set Signed Intermediate +func (b *HsmPkiBackend) loadStorage() { + + caKeyAlias, err := b.loadCAKeyAlias(context.Background(), b.pkiBackend.GetStorage()) + if err != nil || caKeyAlias == nil || caKeyAlias.Value == nil { + msg := fmt.Sprintf("No HSM key label in storage, use conf file: %s", b.pkcs11client.HsmConfig.KeyLabel) + b.pkiBackend.Backend.Logger().Info(msg) + // no override, use the conf file's key label (if set) + b.cachedCAConfig.caKeyAlias = b.pkcs11client.HsmConfig.KeyLabel + } else { + msg := fmt.Sprintf("Found HSM key label in storage: %s", caKeyAlias.Value) + b.pkiBackend.Backend.Logger().Info(msg) + } + +} diff --git a/pkg/hsmpki/backend_test.go b/pkg/hsmpki/backend_test.go new file mode 100644 index 0000000..75037ca --- /dev/null +++ b/pkg/hsmpki/backend_test.go @@ -0,0 +1,249 @@ +package hsmpki + +import ( + "context" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" + "github.com/mode51software/vaultplugin-hsmpki/pkg/pki" + "testing" +) + +type testEnv struct { + HsmPkiBackend *HsmPkiBackend + Context context.Context + Storage logical.Storage + RoleName string +} + +// https://github.com/Venafi/vault-pki-backend-venafi/blob/v0.9.0/plugin/pki/env_test.go#L1787 +func newIntegrationTestEnv() (*testEnv, error) { + ctx := context.Background() + + config := logical.TestBackendConfig() + config.StorageView = &logical.InmemStorage{} + + var err error + b, err := Backend(config) + err = b.pkiBackend.Backend.Setup(context.Background(), config) + if err != nil { + return nil, err + } + + return &testEnv{ + HsmPkiBackend: b, + Context: ctx, + Storage: config.StorageView, + RoleName: "testrole", + }, nil +} + +// Set the working directory if TEST_CONFIG_HSM is relative +func TestConnectPkcs11Connection(t *testing.T) { + integraTest, err := newIntegrationTestEnv() + if err != nil { + t.Fatal(err) + } + + if err = integraTest.HsmPkiBackend.loadConf(TEST_CONFIG_HSM); err != nil { + t.Fatal(err) + } + + integraTest.HsmPkiBackend.configurePkcs11Connection() + if err = integraTest.HsmPkiBackend.checkPkcs11ConnectionSync(); err != nil { + t.Fatal("PKCS#11 connection timed out") + } +} + +// populate the /cert/ca_key_alias path with test data +func TestPathSetFetchCAKeyLabel(t *testing.T) { + integraTest, err := newIntegrationTestEnv() + if err != nil { + t.Fatal(err) + } + entry := logical.StorageEntry{} + entry.Key = PATH_CAKEYALIAS + entry.Value = []byte("testlabel") + + if err = integraTest.HsmPkiBackend.storeEntry(integraTest.Context, &entry, &integraTest.Storage); err != nil { + t.Fatal(err) + } + + testFetchCAKeyLabel(t, integraTest) +} + +// by default the key label will be empty so it needs to have been set first +func testFetchCAKeyLabel(t *testing.T, integraTest *testEnv) { + + req := &logical.Request{ + Operation: logical.ReadOperation, + Path: PATH_CAKEYALIAS, + Storage: integraTest.Storage, + } + + if resp, err := integraTest.HsmPkiBackend.pathFetchCAKeyAlias(integraTest.Context, req, nil); err != nil { + t.Error(err) + } else { + t.Logf("Key Label: %s", resp.Data[FIELD_KEYALIAS]) + } +} + +func TestPathRoleCreate(t *testing.T) { + + integraTest, err := newIntegrationTestEnv() + if err != nil { + t.Fatal(err) + } + + testRoleCreate(t, integraTest) +} + +// vault write hsmpki_int/roles/localhost allowed_domains=localhost allow_subdomains=true max_ttl=72h +func testRoleCreate(t *testing.T, integraTest *testEnv) { + + entry := logical.StorageEntry{} + entry.Key = PATH_ROLE + entry.Value = []byte("localhost") + + if err := integraTest.HsmPkiBackend.storeEntry(integraTest.Context, &entry, &integraTest.Storage); err != nil { + t.Fatal(err) + } + + req := &logical.Request{ + Operation: logical.UpdateOperation, + Path: PATH_ROLE, + Storage: integraTest.Storage, + } + + roles := pki.PathRoles(&integraTest.HsmPkiBackend.pkiBackend.Backend) + + data := framework.FieldData{ + Raw: map[string]interface{}{}, + Schema: roles.Fields, + } + data.Raw["name"] = TEST_ROLENAME + data.Raw["allowed_domains"] = TEST_ALLOWED_DOMAINS + data.Raw["allow_subdomains"] = true + data.Raw["max_ttl"] = TEST_MAX_TTL + data.Raw["ttl"] = TEST_TTL + + if _, err := integraTest.HsmPkiBackend.pkiBackend.Backend.PathRoleCreate(integraTest.Context, req, &data); err != nil { + t.Error(err) + } else { + t.Logf("Created role: %s", TEST_ROLENAME) + } + +} + +func TestPathSetSignedIntermediate(t *testing.T) { + + integraTest, err := newIntegrationTestEnv() + if err != nil { + t.Fatal(err) + } + + testSetSignedIntermediate(t, integraTest) +} + +func testSetSignedIntermediate(t *testing.T, integraTest *testEnv) { + req := &logical.Request{ + Operation: logical.UpdateOperation, + Path: PATH_SETSIGNEDINTERMEDIATE, + Storage: integraTest.Storage, + } + + path := pathSetSignedIntermediate(integraTest.HsmPkiBackend) + + data := framework.FieldData{ + Raw: map[string]interface{}{}, + Schema: path.Fields, + } + data.Raw["certificate"] = TEST_SIGNEDCACERTFILE + + if _, err := integraTest.HsmPkiBackend.pathSetSignedIntermediate(integraTest.Context, req, &data); err != nil { + t.Error(err) + } else { + t.Logf("Set signed intermediate: %s", TEST_SIGNEDCACERTFILE) + } +} + +func TestPathSetCRLConfig(t *testing.T) { + + integraTest, err := newIntegrationTestEnv() + if err != nil { + t.Fatal(err) + } + + testSetCRLConfig(t, integraTest) +} + +func testSetCRLConfig(t *testing.T, integraTest *testEnv) { + req := &logical.Request{ + Operation: logical.UpdateOperation, + Path: PATH_SETCRLCONFIG, + Storage: integraTest.Storage, + } + + path := pki.PathConfigCRL(&integraTest.HsmPkiBackend.pkiBackend.Backend) + + data := framework.FieldData{ + Raw: map[string]interface{}{}, + Schema: path.Fields, + } + data.Raw["expiry"] = DEFAULT_CRL_LIFETIME + + if _, err := integraTest.HsmPkiBackend.pkiBackend.Backend.PathCRLWrite(integraTest.Context, req, &data); err != nil { + t.Error(err) + } else { + t.Logf("Set CRL config: %d", DEFAULT_CRL_LIFETIME) + } +} + +func TestPathFetchCRL(t *testing.T) { + + integraTest, err := newIntegrationTestEnv() + if err != nil { + t.Fatal(err) + } + + testSetCRLConfig(t, integraTest) + testSetSignedIntermediate(t, integraTest) + testFetchCRL(t, integraTest) +} + +func testFetchCRL(t *testing.T, integraTest *testEnv) { + req := &logical.Request{ + Operation: logical.ReadOperation, + Path: PATH_FETCHCRL, + Storage: integraTest.Storage, + } + + path := pki.PathFetchCRL(&integraTest.HsmPkiBackend.pkiBackend.Backend) + + data := framework.FieldData{ + Raw: map[string]interface{}{}, + Schema: path.Fields, + } + + if res, err := integraTest.HsmPkiBackend.pkiBackend.Backend.PathFetchRead(integraTest.Context, req, &data); err != nil { + t.Error(err) + } else { + t.Logf("Fetched CRL status where 200 is populated, 204 is empty: %d", res.Data[logical.HTTPStatusCode]) + } +} + +/*func init() { + + b, err := newBackend() + if err != nil { + panic(err) + } + + if conf == nil { + return nil, fmt.Errorf("configuration passed into backend is nil") + } + + if err := b.pkiBackend.Backend.Setup(ctx, conf); err != nil { + panic(err) + } + +}*/ diff --git a/pkg/hsmpki/hsmcert_util.go b/pkg/hsmpki/hsmcert_util.go new file mode 100644 index 0000000..d0829cb --- /dev/null +++ b/pkg/hsmpki/hsmcert_util.go @@ -0,0 +1,192 @@ +package hsmpki + +import ( + "context" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" + "github.com/mode51software/vaultplugin-hsmpki/pkg/pki" +) + +type inputBundle struct { + role *pki.RoleEntry + req *logical.Request + apiData *framework.FieldData +} + +func signCert(b *HsmPkiBackend, + data *pki.InputBundleA, + caSign *certutil.CAInfoBundle, + isCA bool, + useCSRValues bool) (*certutil.ParsedCertBundle, error) { + + if data.Role == nil { + return nil, errutil.InternalError{Err: "no role found in data bundle"} + } + + csrString := data.ApiData.Get("csr").(string) + if csrString == "" { + return nil, errutil.UserError{Err: fmt.Sprintf("\"csr\" is empty")} + } + + pemBytes := []byte(csrString) + pemBlock, pemBytes := pem.Decode(pemBytes) + if pemBlock == nil { + return nil, errutil.UserError{Err: "csr contains no data"} + } + csr, err := x509.ParseCertificateRequest(pemBlock.Bytes) + if err != nil { + return nil, errutil.UserError{Err: fmt.Sprintf("certificate request could not be parsed: %v", err)} + } + + switch data.Role.KeyType { + case "rsa": + // Verify that the key matches the role type + if csr.PublicKeyAlgorithm != x509.RSA { + return nil, errutil.UserError{Err: fmt.Sprintf( + "role requires keys of type %s", + data.Role.KeyType)} + } + pubKey, ok := csr.PublicKey.(*rsa.PublicKey) + if !ok { + return nil, errutil.UserError{Err: "could not parse CSR's public key"} + } + + // Verify that the key is at least 2048 bits + if pubKey.N.BitLen() < 2048 { + return nil, errutil.UserError{Err: "RSA keys < 2048 bits are unsafe and not supported"} + } + + // Verify that the bit size is at least the size specified in the role + if pubKey.N.BitLen() < data.Role.KeyBits { + return nil, errutil.UserError{Err: fmt.Sprintf( + "role requires a minimum of a %d-bit key, but CSR's key is %d bits", + data.Role.KeyBits, + pubKey.N.BitLen())} + } + + case "ec": + // Verify that the key matches the role type + if csr.PublicKeyAlgorithm != x509.ECDSA { + return nil, errutil.UserError{Err: fmt.Sprintf( + "role requires keys of type %s", + data.Role.KeyType)} + } + pubKey, ok := csr.PublicKey.(*ecdsa.PublicKey) + if !ok { + return nil, errutil.UserError{Err: "could not parse CSR's public key"} + } + + // Verify that the bit size is at least the size specified in the role + if pubKey.Params().BitSize < data.Role.KeyBits { + return nil, errutil.UserError{Err: fmt.Sprintf( + "role requires a minimum of a %d-bit key, but CSR's key is %d bits", + data.Role.KeyBits, + pubKey.Params().BitSize)} + } + + case "any": + // We only care about running RSA < 2048 bit checks, so if not RSA + // break out + if csr.PublicKeyAlgorithm != x509.RSA { + break + } + + // Run RSA < 2048 bit checks + pubKey, ok := csr.PublicKey.(*rsa.PublicKey) + if !ok { + return nil, errutil.UserError{Err: "could not parse CSR's public key"} + } + if pubKey.N.BitLen() < 2048 { + return nil, errutil.UserError{Err: "RSA keys < 2048 bits are unsafe and not supported"} + } + + } + + creation, err := pki.GenerateConvertedCreationBundle(&b.pkiBackend.Backend, data, caSign, csr) + if err != nil { + return nil, err + } + if creation.Params == nil { + return nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"} + } + + creation.Params.IsCA = isCA + creation.Params.UseCSRValues = useCSRValues + + if isCA { + creation.Params.PermittedDNSDomains = data.ApiData.Get("permitted_dns_domains").([]string) + } + + //parsedBundle, err := certutil.SignCertificate(creation) + parsedBundle, err := SignCertificate(b, creation) + + if err != nil { + return nil, err + } + + return parsedBundle, nil +} + +func generateCert(ctx context.Context, + b *HsmPkiBackend, + input *pki.InputBundleA, + caSign *certutil.CAInfoBundle, + isCA bool) (*certutil.ParsedCertBundle, error) { + + if input.Role == nil { + return nil, errutil.InternalError{Err: "no role found in data bundle"} + } + + if input.Role.KeyType == "rsa" && input.Role.KeyBits < 2048 { + return nil, errutil.UserError{Err: "RSA keys < 2048 bits are unsafe and not supported"} + } + + data, err := pki.GenerateConvertedCreationBundle(&b.pkiBackend.Backend, input, caSign, nil) + if err != nil { + return nil, err + } + if data.Params == nil { + return nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"} + } + + if isCA { + data.Params.IsCA = isCA + data.Params.PermittedDNSDomains = input.ApiData.Get("permitted_dns_domains").([]string) + + if data.SigningBundle == nil { + // Generating a self-signed root certificate + entries, err := pki.GetURLs(ctx, input.Req) + if err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch URL information: %v", err)} + } + if entries == nil { + entries = &certutil.URLEntries{ + IssuingCertificates: []string{}, + CRLDistributionPoints: []string{}, + OCSPServers: []string{}, + } + } + data.Params.URLs = entries + + if input.Role.MaxPathLength == nil { + data.Params.MaxPathLength = -1 + } else { + data.Params.MaxPathLength = *input.Role.MaxPathLength + } + } + } + + parsedBundle, err := CreateCertificate(b, data) + if err != nil { + return nil, err + } + + return parsedBundle, nil +} diff --git a/pkg/hsmpki/hsmconsts.go b/pkg/hsmpki/hsmconsts.go new file mode 100644 index 0000000..dba4675 --- /dev/null +++ b/pkg/hsmpki/hsmconsts.go @@ -0,0 +1,36 @@ +package hsmpki + +const ( + PATH_CA = "ca" + PATH_CAKEYALIAS = "cert/ca_keyalias" + PATH_CERTS = "certs/" + PATH_ROLE = "role/" + PATH_SETSIGNEDINTERMEDIATE = "intermediate/set-signed" + PATH_SETCRLCONFIG = "config/crl" + PATH_FETCHCRL = "crl" + + PATH_HASHALGO = "hash_algo" + + FIELD_KEYALIAS = "key_alias" + FIELD_HASHALGO = "hash_algo" + + CONFIG_PARAM = "config" + + CONFIG_PLUGIN_NAME = "plugin_name" + + PLUGIN_HELP = "The hsmpki backend is a PKI plugin that uses an HSM for CA signing." + + DEFAULT_CRL_LIFETIME = 72 + + // relative to test working directory in pkg/hsmpki + TEST_CONFIG_HSM = "../../conf/config-softhsm.hcl" + + TEST_ROLENAME = "localhost" + TEST_ALLOWED_DOMAINS = "localhost" + TEST_MAX_TTL = "72h" + TEST_TTL = "1h" + + TEST_SIGNEDCACERTFILE = "../../data/softhsm-inter-0002.ca.cert.pem" +) + +var oidExtensionBasicConstraints = []int{2, 5, 29, 19} diff --git a/pkg/hsmpki/hsmcrl_util.go b/pkg/hsmpki/hsmcrl_util.go new file mode 100644 index 0000000..2520d4c --- /dev/null +++ b/pkg/hsmpki/hsmcrl_util.go @@ -0,0 +1,252 @@ +package hsmpki + +import ( + "context" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "errors" + "fmt" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" + "github.com/mode51software/pkcs11helper/pkg/pkcs11client" + "github.com/mode51software/vaultplugin-hsmpki/pkg/pki" + "strings" + "time" +) + +type revocationInfo struct { + CertificateBytes []byte `json:"certificate_bytes"` + RevocationTime int64 `json:"revocation_time"` + RevocationTimeUTC time.Time `json:"revocation_time_utc"` +} + +// Revokes a cert, and tries to be smart about error recovery +func revokeCert(ctx context.Context, b *HsmPkiBackend, req *logical.Request, serial string, fromLease bool) (*logical.Response, error) { + // As this backend is self-contained and this function does not hook into + // third parties to manage users or resources, if the mount is tainted, + // revocation doesn't matter anyways -- the CRL that would be written will + // be immediately blown away by the view being cleared. So we can simply + // fast path a successful exit. + if b.pkiBackend.Backend.System().Tainted() { + return nil, nil + } + + signingBundle, caErr := pki.FetchCAInfo(ctx, req) + switch caErr.(type) { + case errutil.UserError: + return logical.ErrorResponse(fmt.Sprintf("could not fetch the CA certificate: %s", caErr)), nil + case errutil.InternalError: + return nil, fmt.Errorf("error fetching CA certificate: %s", caErr) + } + if signingBundle == nil { + return nil, errors.New("CA info not found") + } + colonSerial := strings.Replace(strings.ToLower(serial), "-", ":", -1) + if colonSerial == certutil.GetHexFormatted(signingBundle.Certificate.SerialNumber.Bytes(), ":") { + return logical.ErrorResponse("adding CA to CRL is not allowed"), nil + } + + alreadyRevoked := false + var revInfo revocationInfo + + revEntry, err := pki.FetchCertBySerial(ctx, req, "revoked/", serial) + if err != nil { + switch err.(type) { + case errutil.UserError: + return logical.ErrorResponse(err.Error()), nil + case errutil.InternalError: + return nil, err + } + } + if revEntry != nil { + // Set the revocation info to the existing values + alreadyRevoked = true + err = revEntry.DecodeJSON(&revInfo) + if err != nil { + return nil, fmt.Errorf("error decoding existing revocation info") + } + } + + if !alreadyRevoked { + certEntry, err := pki.FetchCertBySerial(ctx, req, "certs/", serial) + if err != nil { + switch err.(type) { + case errutil.UserError: + return logical.ErrorResponse(err.Error()), nil + case errutil.InternalError: + return nil, err + } + } + if certEntry == nil { + if fromLease { + // We can't write to revoked/ or update the CRL anyway because we don't have the cert, + // and there's no reason to expect this will work on a subsequent + // retry. Just give up and let the lease get deleted. + b.pkiBackend.Backend.Logger().Warn("expired certificate revoke failed because not found in storage, treating as success", "serial", serial) + return nil, nil + } + return logical.ErrorResponse(fmt.Sprintf("certificate with serial %s not found", serial)), nil + } + + cert, err := x509.ParseCertificate(certEntry.Value) + if err != nil { + return nil, errwrap.Wrapf("error parsing certificate: {{err}}", err) + } + if cert == nil { + return nil, fmt.Errorf("got a nil certificate") + } + + // Add a little wiggle room because leases are stored with a second + // granularity + if cert.NotAfter.Before(time.Now().Add(2 * time.Second)) { + return nil, nil + } + + // Compatibility: Don't revoke CAs if they had leases. New CAs going + // forward aren't issued leases. + if cert.IsCA && fromLease { + return nil, nil + } + + currTime := time.Now() + revInfo.CertificateBytes = certEntry.Value + revInfo.RevocationTime = currTime.Unix() + revInfo.RevocationTimeUTC = currTime.UTC() + + revEntry, err = logical.StorageEntryJSON("revoked/"+pki.NormalizeSerial(serial), revInfo) + if err != nil { + return nil, fmt.Errorf("error creating revocation entry") + } + + err = req.Storage.Put(ctx, revEntry) + if err != nil { + return nil, fmt.Errorf("error saving revoked certificate to new location") + } + + } + + crlErr := buildCRL(ctx, b, req, false) + switch crlErr.(type) { + case errutil.UserError: + return logical.ErrorResponse(fmt.Sprintf("Error during CRL building: %s", crlErr)), nil + case errutil.InternalError: + return nil, errwrap.Wrapf("error encountered during CRL building: {{err}}", crlErr) + } + + resp := &logical.Response{ + Data: map[string]interface{}{ + "revocation_time": revInfo.RevocationTime, + }, + } + if !revInfo.RevocationTimeUTC.IsZero() { + resp.Data["revocation_time_rfc3339"] = revInfo.RevocationTimeUTC.Format(time.RFC3339Nano) + } + return resp, nil +} + +// Builds a CRL by going through the list of revoked certificates and building +// a new CRL with the stored revocation times and serial numbers. +func buildCRL(ctx context.Context, b *HsmPkiBackend, req *logical.Request, forceNew bool) error { + crlInfo, err := b.pkiBackend.Backend.CRL(ctx, req.Storage) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error fetching CRL config information: %s", err)} + } + + crlLifetime := b.pkiBackend.GetCrlLifetime() + var revokedCerts []pkix.RevokedCertificate + var revInfo revocationInfo + var revokedSerials []string + + if crlInfo != nil { + if crlInfo.Expiry != "" { + crlDur, err := time.ParseDuration(crlInfo.Expiry) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error parsing CRL duration of %s", crlInfo.Expiry)} + } + crlLifetime = crlDur + } + + if crlInfo.Disable { + if !forceNew { + return nil + } + goto WRITE + } + } + + revokedSerials, err = req.Storage.List(ctx, "revoked/") + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error fetching list of revoked certs: %s", err)} + } + + for _, serial := range revokedSerials { + revokedEntry, err := req.Storage.Get(ctx, "revoked/"+serial) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("unable to fetch revoked cert with serial %s: %s", serial, err)} + } + if revokedEntry == nil { + return errutil.InternalError{Err: fmt.Sprintf("revoked certificate entry for serial %s is nil", serial)} + } + if revokedEntry.Value == nil || len(revokedEntry.Value) == 0 { + // TODO: In this case, remove it and continue? How likely is this to + // happen? Alternately, could skip it entirely, or could implement a + // delete function so that there is a way to remove these + return errutil.InternalError{Err: fmt.Sprintf("found revoked serial but actual certificate is empty")} + } + + err = revokedEntry.DecodeJSON(&revInfo) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error decoding revocation entry for serial %s: %s", serial, err)} + } + + revokedCert, err := x509.ParseCertificate(revInfo.CertificateBytes) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("unable to parse stored revoked certificate with serial %s: %s", serial, err)} + } + + // NOTE: We have to change this to UTC time because the CRL standard + // mandates it but Go will happily encode the CRL without this. + newRevCert := pkix.RevokedCertificate{ + SerialNumber: revokedCert.SerialNumber, + } + if !revInfo.RevocationTimeUTC.IsZero() { + newRevCert.RevocationTime = revInfo.RevocationTimeUTC + } else { + newRevCert.RevocationTime = time.Unix(revInfo.RevocationTime, 0).UTC() + } + revokedCerts = append(revokedCerts, newRevCert) + } + +WRITE: + signingBundle, caErr := pki.FetchCAInfo(ctx, req) + switch caErr.(type) { + case errutil.UserError: + return errutil.UserError{Err: fmt.Sprintf("could not fetch the CA certificate: %s", caErr)} + case errutil.InternalError: + return errutil.InternalError{Err: fmt.Sprintf("error fetching CA certificate: %s", caErr)} + } + + var caSigner pkcs11client.HsmSigner + caSigner.KeyConfig.Label = b.cachedCAConfig.caKeyAlias //"ECTestCAInterKey0016" + caSigner.Pkcs11Client = &b.pkcs11client + caSigner.PublicKey = signingBundle.Certificate.PublicKey + + // crlBytes, err := signingBundle.Certificate.CreateCRL(rand.Reader, signingBundle.PrivateKey, revokedCerts, time.Now(), time.Now().Add(crlLifetime)) + crlBytes, err := signingBundle.Certificate.CreateCRL(rand.Reader, caSigner, revokedCerts, time.Now(), time.Now().Add(crlLifetime)) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error creating new CRL: %s", err)} + } + + err = req.Storage.Put(ctx, &logical.StorageEntry{ + Key: "crl", + Value: crlBytes, + }) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error storing CRL: %s", err)} + } + + return nil +} diff --git a/pkg/hsmpki/hsmhelpers.go b/pkg/hsmpki/hsmhelpers.go new file mode 100644 index 0000000..f9a7cd4 --- /dev/null +++ b/pkg/hsmpki/hsmhelpers.go @@ -0,0 +1,321 @@ +package hsmpki + +import ( + "bytes" + "crypto" + "crypto/rand" + "crypto/sha1" + "crypto/x509" + "fmt" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/mode51software/pkcs11helper/pkg/pkcs11client" + "time" +) + +// Performs the heavy lifting of generating a certificate from a CSR. +// Returns a ParsedCertBundle sans private keys. +func SignCertificate(b *HsmPkiBackend, data *certutil.CreationBundle) (*certutil.ParsedCertBundle, error) { + switch { + case data == nil: + return nil, errutil.UserError{Err: "nil data bundle given to signCertificate"} + case data.Params == nil: + return nil, errutil.UserError{Err: "nil parameters given to signCertificate"} + case data.SigningBundle == nil: + return nil, errutil.UserError{Err: "nil signing bundle given to signCertificate"} + case data.CSR == nil: + return nil, errutil.UserError{Err: "nil csr given to signCertificate"} + } + + err := data.CSR.CheckSignature() + if err != nil { + return nil, errutil.UserError{Err: "request signature invalid"} + } + + result := &certutil.ParsedCertBundle{} + + serialNumber, err := certutil.GenerateSerialNumber() + if err != nil { + return nil, err + } + + marshaledKey, err := x509.MarshalPKIXPublicKey(data.CSR.PublicKey) + if err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("error marshalling public key: %s", err)} + } + subjKeyID := sha1.Sum(marshaledKey) + + caCert := data.SigningBundle.Certificate + + certTemplate := &x509.Certificate{ + SerialNumber: serialNumber, + Subject: data.Params.Subject, + NotBefore: time.Now().Add(-30 * time.Second), + NotAfter: data.Params.NotAfter, + SubjectKeyId: subjKeyID[:], + AuthorityKeyId: caCert.SubjectKeyId, + } + if data.Params.NotBeforeDuration > 0 { + certTemplate.NotBefore = time.Now().Add(-1 * data.Params.NotBeforeDuration) + } + + certTemplate.SignatureAlgorithm = selectHashAlgo(data.SigningBundle.Certificate.PublicKeyAlgorithm, b.cachedCAConfig.hashAlgo) + if certTemplate.SignatureAlgorithm == 0 { + return nil, errutil.InternalError{Err: errwrap.Wrapf("Unknown SignatureAlgorithm", nil).Error()} + } + + if data.Params.UseCSRValues { + certTemplate.Subject = data.CSR.Subject + certTemplate.Subject.ExtraNames = certTemplate.Subject.Names + + certTemplate.DNSNames = data.CSR.DNSNames + certTemplate.EmailAddresses = data.CSR.EmailAddresses + certTemplate.IPAddresses = data.CSR.IPAddresses + certTemplate.URIs = data.CSR.URIs + + for _, name := range data.CSR.Extensions { + if !name.Id.Equal(oidExtensionBasicConstraints) { + certTemplate.ExtraExtensions = append(certTemplate.ExtraExtensions, name) + } + } + + } else { + certTemplate.DNSNames = data.Params.DNSNames + certTemplate.EmailAddresses = data.Params.EmailAddresses + certTemplate.IPAddresses = data.Params.IPAddresses + certTemplate.URIs = data.Params.URIs + } + + if err := certutil.HandleOtherSANs(certTemplate, data.Params.OtherSANs); err != nil { + return nil, errutil.InternalError{Err: errwrap.Wrapf("error marshaling other SANs: {{err}}", err).Error()} + } + + certutil.AddPolicyIdentifiers(data, certTemplate) + + certutil.AddKeyUsages(data, certTemplate) + + certutil.AddExtKeyUsageOids(data, certTemplate) + + var certBytes []byte + + certTemplate.IssuingCertificateURL = data.Params.URLs.IssuingCertificates + certTemplate.CRLDistributionPoints = data.Params.URLs.CRLDistributionPoints + certTemplate.OCSPServer = data.SigningBundle.URLs.OCSPServers + + if data.Params.IsCA { + certTemplate.BasicConstraintsValid = true + certTemplate.IsCA = true + + if data.SigningBundle.Certificate.MaxPathLen == 0 && + data.SigningBundle.Certificate.MaxPathLenZero { + return nil, errutil.UserError{Err: "signing certificate has a max path length of zero, and cannot issue further CA certificates"} + } + + certTemplate.MaxPathLen = data.Params.MaxPathLength + if certTemplate.MaxPathLen == 0 { + certTemplate.MaxPathLenZero = true + } + } else if data.Params.BasicConstraintsValidForNonCA { + certTemplate.BasicConstraintsValid = true + certTemplate.IsCA = false + } + + if len(data.Params.PermittedDNSDomains) > 0 { + certTemplate.PermittedDNSDomains = data.Params.PermittedDNSDomains + certTemplate.PermittedDNSDomainsCritical = true + } + + // serial number is managed by vault + // certTemplate is managed by vault so SignatureAlgorithm not used here + var caSigner pkcs11client.HsmSigner + caSigner.KeyConfig.Label = b.cachedCAConfig.caKeyAlias //"ECTestCAInterKey0016" + caSigner.Pkcs11Client = &b.pkcs11client + caSigner.PublicKey = data.SigningBundle.Certificate.PublicKey + + //pubKey, err := b.pkcs11client.ReadECPublicKey(&caSigner.KeyConfig) + //caSigner.PublicKey = pubKey + + b.pkcs11client.Pkcs11Mutex.Lock() + + certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, caCert, data.CSR.PublicKey, caSigner) + + // whilst the Pkcs11Client is locked, the last error and code can be used + + if b.pkcs11client.LastErrCode == pkcs11client.PKCS11ERR_READTIMEOUT { + b.pkiBackend.Backend.Logger().Info("pkcs11helper: Timeout processing PKCS#11 function") + b.checkPkcs11ConnectionFailed() + } else if b.pkcs11client.LastErrCode == pkcs11client.PKCS11ERR_GENERICERROR { + b.pkiBackend.Backend.Logger().Info("pkcs11helper: generic error") + // TODO: defend against deliberate errors which cause a reconnection attempt + b.checkPkcs11ConnectionFailed() + } else if b.pkcs11client.LastErrCode == 0 { + b.pkiBackend.Backend.Logger().Info("pkcs11helper: no error") + } + + b.pkcs11client.Pkcs11Mutex.Unlock() // unlock asap rather than have deferred + + if err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("unable to create certificate: %s", err)} + } + + result.CertificateBytes = certBytes + result.Certificate, err = x509.ParseCertificate(certBytes) + if err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)} + } + + result.CAChain = data.SigningBundle.GetCAChain() + + return result, nil +} + +// Performs the heavy lifting of creating a certificate. Returns +// a fully-filled-in ParsedCertBundle. +func CreateCertificate(b *HsmPkiBackend, data *certutil.CreationBundle) (*certutil.ParsedCertBundle, error) { + var err error + result := &certutil.ParsedCertBundle{} + + serialNumber, err := certutil.GenerateSerialNumber() + if err != nil { + return nil, err + } + + if err := certutil.GeneratePrivateKey(data.Params.KeyType, + data.Params.KeyBits, + result); err != nil { + return nil, err + } + + subjKeyID, err := certutil.GetSubjKeyID(result.PrivateKey) + if err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("error getting subject key ID: %s", err)} + } + + certTemplate := &x509.Certificate{ + SerialNumber: serialNumber, + NotBefore: time.Now().Add(-30 * time.Second), + NotAfter: data.Params.NotAfter, + IsCA: false, + SubjectKeyId: subjKeyID, + Subject: data.Params.Subject, + DNSNames: data.Params.DNSNames, + EmailAddresses: data.Params.EmailAddresses, + IPAddresses: data.Params.IPAddresses, + URIs: data.Params.URIs, + } + if data.Params.NotBeforeDuration > 0 { + certTemplate.NotBefore = time.Now().Add(-1 * data.Params.NotBeforeDuration) + } + + if err := certutil.HandleOtherSANs(certTemplate, data.Params.OtherSANs); err != nil { + return nil, errutil.InternalError{Err: errwrap.Wrapf("error marshaling other SANs: {{err}}", err).Error()} + } + + // Add this before calling addKeyUsages + if data.SigningBundle == nil { + certTemplate.IsCA = true + } else if data.Params.BasicConstraintsValidForNonCA { + certTemplate.BasicConstraintsValid = true + certTemplate.IsCA = false + } + + // This will only be filled in from the generation paths + if len(data.Params.PermittedDNSDomains) > 0 { + certTemplate.PermittedDNSDomains = data.Params.PermittedDNSDomains + certTemplate.PermittedDNSDomainsCritical = true + } + + certutil.AddPolicyIdentifiers(data, certTemplate) + + certutil.AddKeyUsages(data, certTemplate) + + certutil.AddExtKeyUsageOids(data, certTemplate) + + certTemplate.IssuingCertificateURL = data.Params.URLs.IssuingCertificates + certTemplate.CRLDistributionPoints = data.Params.URLs.CRLDistributionPoints + certTemplate.OCSPServer = data.Params.URLs.OCSPServers + + var certBytes []byte + if data.SigningBundle != nil { + + certTemplate.SignatureAlgorithm = selectHashAlgo(data.SigningBundle.Certificate.PublicKeyAlgorithm, b.cachedCAConfig.hashAlgo) + if certTemplate.SignatureAlgorithm == 0 { + return nil, errutil.InternalError{Err: errwrap.Wrapf("Unknown SignatureAlgorithm", nil).Error()} + } + + caCert := data.SigningBundle.Certificate + certTemplate.AuthorityKeyId = caCert.SubjectKeyId + + var caSigner pkcs11client.HsmSigner + caSigner.KeyConfig.Label = b.cachedCAConfig.caKeyAlias + caSigner.Pkcs11Client = &b.pkcs11client + caSigner.PublicKey = data.SigningBundle.Certificate.PublicKey + + certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, caCert, result.PrivateKey.Public(), caSigner) //data.SigningBundle.PrivateKey) + } else { + return nil, errutil.InternalError{Err: errwrap.Wrapf("Self-signed roots are unsupported", nil).Error()} + } + + if err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("unable to create certificate: %s", err)} + } + + result.CertificateBytes = certBytes + result.Certificate, err = x509.ParseCertificate(certBytes) + if err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)} + } + + if data.SigningBundle != nil { + if len(data.SigningBundle.Certificate.AuthorityKeyId) > 0 && + !bytes.Equal(data.SigningBundle.Certificate.AuthorityKeyId, data.SigningBundle.Certificate.SubjectKeyId) { + + result.CAChain = []*certutil.CertBlock{ + &certutil.CertBlock{ + Certificate: data.SigningBundle.Certificate, + Bytes: data.SigningBundle.CertificateBytes, + }, + } + result.CAChain = append(result.CAChain, data.SigningBundle.CAChain...) + } + } + + return result, nil +} + +func selectHashAlgo(pubKeyAlgo x509.PublicKeyAlgorithm, cachedHashAlgo crypto.Hash) x509.SignatureAlgorithm { + + switch pubKeyAlgo { + + case x509.RSA: + // we don't need to specify whether to use RSASSA-PKCS-v1.5 or RSASSA-PSS here + // because the pkcs11helper.HsmSigner auto detects this from the CA's public key + switch cachedHashAlgo { + case crypto.SHA384: + return x509.SHA384WithRSA + case crypto.SHA512: + return x509.SHA512WithRSA + case crypto.SHA256: + fallthrough + default: + return x509.SHA256WithRSA + } + + case x509.ECDSA: + switch cachedHashAlgo { + case crypto.SHA384: + return x509.ECDSAWithSHA384 + case crypto.SHA512: + return x509.ECDSAWithSHA512 + case crypto.SHA256: + fallthrough + default: + return x509.ECDSAWithSHA256 + } + + default: + return 0 + + } +} diff --git a/pkg/hsmpki/hsmpath_fetch.go b/pkg/hsmpki/hsmpath_fetch.go new file mode 100644 index 0000000..b041017 --- /dev/null +++ b/pkg/hsmpki/hsmpath_fetch.go @@ -0,0 +1,59 @@ +package hsmpki + +import ( + "context" + "fmt" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" +) + +// This returns the currently configured key alias that corresponds to the Intermediate CA's private key in the HSM +func pathFetchCAKeyAlias(b *HsmPkiBackend) *framework.Path { + return &framework.Path{ + Pattern: PATH_CAKEYALIAS, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathFetchCAKeyAlias, + }, + + HelpSynopsis: pathFetchCAKeyAliasHelpSyn, + HelpDescription: pathFetchCAKeyAliasHelpDesc, + } +} + +func (b *HsmPkiBackend) pathFetchCAKeyAlias(ctx context.Context, req *logical.Request, data *framework.FieldData) (response *logical.Response, retErr error) { + caKeyAlias, err := b.loadCAKeyAlias(ctx, req.Storage) //)req.Storage.Get(ctx, PATH_CAKEYALIAS) + if err != nil { + return nil, err + } + + if caKeyAlias != nil || len(b.pkcs11client.HsmConfig.KeyLabel) > 0 { + response = &logical.Response{ + Data: map[string]interface{}{}, + } + if caKeyAlias == nil || caKeyAlias.Value == nil { + response.Data[FIELD_KEYALIAS] = b.pkcs11client.HsmConfig.KeyLabel + } else { + response.Data[FIELD_KEYALIAS] = string(caKeyAlias.Value) + } + } else { + return nil, fmt.Errorf("Unable to read ca key alias") + } + return +} + +func (b *HsmPkiBackend) loadCAKeyAlias(ctx context.Context, storage logical.Storage) (*logical.StorageEntry, error) { + caKeyAlias, err := storage.Get(ctx, PATH_CAKEYALIAS) + if err != nil { + return nil, err + } + return caKeyAlias, nil +} + +const pathFetchCAKeyAliasHelpSyn = ` +Fetch the currently configured CA's key alias +` + +const pathFetchCAKeyAliasHelpDesc = ` +This allows the correct identification of the key pair on the HSM. +` diff --git a/pkg/hsmpki/hsmpath_intermediate.go b/pkg/hsmpki/hsmpath_intermediate.go new file mode 100644 index 0000000..36b8dd0 --- /dev/null +++ b/pkg/hsmpki/hsmpath_intermediate.go @@ -0,0 +1,190 @@ +package hsmpki + +import ( + "context" + "crypto" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" + "github.com/mode51software/vaultplugin-hsmpki/pkg/pki" + "strings" +) + +func pathSetSignedIntermediate(b *HsmPkiBackend) *framework.Path { + ret := &framework.Path{ + Pattern: PATH_SETSIGNEDINTERMEDIATE, + + Fields: map[string]*framework.FieldSchema{ + "certificate": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `PEM-format certificate. This must be a CA +certificate with a public key and a key alias that matches a private key in the HSM. +.`, + }, + "key_alias": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `The key alias of the private key in the HSM. +Providing this will override the key alias that can be set in the configuration file. +.`, + }, + "hash_algo": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `The hash algorithm to use. +For RSA and ECDSA the options are SHA-256, SHA-384 or SHA-512. +.`, + }, + "verify": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `Check that an HSM object exists with the configured key alias. +.`, + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathSetSignedIntermediate, + }, + + HelpSynopsis: pathSetSignedIntermediateHelpSyn, + HelpDescription: pathSetSignedIntermediateHelpDesc, + } + + return ret +} + +func (b *HsmPkiBackend) pathSetSignedIntermediate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + + b.checkPkcs11ConnectionSync() + + cert := data.Get("certificate").(string) + + if cert == "" { + return logical.ErrorResponse("no certificate provided in the \"certificate\" parameter"), nil + } + + inputBundle, err := certutil.ParsePEMBundle(cert) + if err != nil { + switch err.(type) { + case errutil.InternalError: + return nil, err + default: + return logical.ErrorResponse(err.Error()), nil + } + } + + if inputBundle.Certificate == nil { + return logical.ErrorResponse("supplied certificate could not be successfully parsed"), nil + } + + // HSM the certBundle will now be empty because the Intermediate CA hasn't been generated using Vault + + cb := &certutil.CertBundle{} + + // no local private key + + if !inputBundle.Certificate.IsCA { + return logical.ErrorResponse("the given certificate is not marked for CA use and cannot be used with this backend"), nil + } + + if err := inputBundle.Verify(); err != nil { + return nil, errwrap.Wrapf("verification of parsed bundle failed: {{err}}", err) + } + + cb, err = inputBundle.ToCertBundle() + if err != nil { + return nil, errwrap.Wrapf("error converting raw values into cert bundle: {{err}}", err) + } + + entry, err := logical.StorageEntryJSON("config/ca_bundle", cb) + if err != nil { + return nil, err + } + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, err + } + + entry.Key = PATH_CERTS + pki.NormalizeSerial(cb.SerialNumber) + entry.Value = inputBundle.CertificateBytes + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, err + } + + // For ease of later use, also store just the certificate at a known + // location + entry.Key = PATH_CA + entry.Value = inputBundle.CertificateBytes + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, err + } + + inCaKeyAlias := data.Get(FIELD_KEYALIAS).(string) + entry.Key = PATH_CAKEYALIAS + + if inCaKeyAlias == "" { + // use the key label provided in the conf file + entry.Value = []byte(b.pkcs11client.HsmConfig.KeyLabel) + if err = b.storeEntry(ctx, entry, &req.Storage); err != nil { + return nil, err + } + if len(b.pkcs11client.HsmConfig.KeyLabel) == 0 { + return nil, errwrap.Wrapf("Either set a key_label in the plugin's conf file or pass in an HSM key label using key_label", nil) + } + b.cachedCAConfig.caKeyAlias = b.pkcs11client.HsmConfig.KeyLabel + + } else { + + entry.Value = []byte(inCaKeyAlias) + if err = b.storeEntry(ctx, entry, &req.Storage); err != nil { + return nil, err + } + b.cachedCAConfig.caKeyAlias = inCaKeyAlias + } + + inHashAlgo := data.Get(FIELD_HASHALGO).(string) + var hashAlgoId crypto.Hash + if inHashAlgo != "" { + ucHashAlgo := strings.ToUpper(inHashAlgo) + switch ucHashAlgo { + case "SHA-256": + hashAlgoId = crypto.SHA256 + case "SHA-384": + hashAlgoId = crypto.SHA384 + case "SHA-512": + hashAlgoId = crypto.SHA512 + default: + hashAlgoId = 0 + } + } + entry.Key = PATH_HASHALGO + hashAlgoByte := make([]byte, 1) + hashAlgoByte[0] = byte(hashAlgoId) + entry.Value = hashAlgoByte + if err = b.storeEntry(ctx, entry, &req.Storage); err != nil { + return nil, err + } + b.cachedCAConfig.hashAlgo = hashAlgoId + + // TODO: Build a fresh CRL + err = buildCRL(ctx, b, req, true) + + return nil, err +} + +func (b *HsmPkiBackend) storeEntry(ctx context.Context, entry *logical.StorageEntry, storage *logical.Storage) error { + if err := (*storage).Put(ctx, entry); err != nil { + return err + } + return nil +} + +const pathSetSignedIntermediateHelpSyn = ` +Provide the signed intermediate CA cert. +` + +const pathSetSignedIntermediateHelpDesc = ` +See the API documentation for more information. +` diff --git a/pkg/hsmpki/hsmpath_issue_sign.go b/pkg/hsmpki/hsmpath_issue_sign.go new file mode 100644 index 0000000..c1df229 --- /dev/null +++ b/pkg/hsmpki/hsmpath_issue_sign.go @@ -0,0 +1,279 @@ +package hsmpki + +import ( + "context" + "encoding/base64" + "fmt" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/helper/consts" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" + "github.com/mode51software/vaultplugin-hsmpki/pkg/pki" + "time" +) + +func pathIssue(b *HsmPkiBackend) *framework.Path { + ret := &framework.Path{ + Pattern: "issue/" + framework.GenericNameRegex("role"), + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathIssue, + }, + + HelpSynopsis: pathIssueHelpSyn, + HelpDescription: pathIssueHelpDesc, + } + + ret.Fields = pki.AddNonCACommonFields(map[string]*framework.FieldSchema{}) + return ret +} + +func pathSign(b *HsmPkiBackend) *framework.Path { + ret := &framework.Path{ + Pattern: "sign/" + framework.GenericNameRegex("role"), + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathSign, + }, + + HelpSynopsis: pathSignHelpSyn, + HelpDescription: pathSignHelpDesc, + } + + ret.Fields = pki.AddNonCACommonFields(map[string]*framework.FieldSchema{}) + + ret.Fields["csr"] = &framework.FieldSchema{ + Type: framework.TypeString, + Default: "", + Description: `PEM-format CSR to be signed.`, + } + + return ret +} + +// pathIssue issues a certificate and private key from given parameters, +// subject to role restrictions +func (b *HsmPkiBackend) pathIssue(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + roleName := data.Get("role").(string) + + // Get the role + role, err := b.pkiBackend.GetRole(ctx, req.Storage, roleName) + if err != nil { + return nil, err + } + if role == nil { + return logical.ErrorResponse(fmt.Sprintf("unknown role: %s", roleName)), nil + } + + if role.KeyType == "any" { + return logical.ErrorResponse("role key type \"any\" not allowed for issuing certificates, only signing"), nil + } + + return b.pathIssueSignCert(ctx, req, data, role, false, false) +} + +// pathSign issues a certificate from a submitted CSR, subject to role +// restrictions +func (b *HsmPkiBackend) pathSign(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + + roleName := data.Get("role").(string) + + // Get the role + role, err := b.pkiBackend.GetRole(ctx, req.Storage, roleName) + if err != nil { + return nil, err + } + if role == nil { + return logical.ErrorResponse(fmt.Sprintf("unknown role: %s", roleName)), nil + } + + return b.pathIssueSignCert(ctx, req, data, role, true, false) +} + +func (b *HsmPkiBackend) pathIssueSignCert(ctx context.Context, req *logical.Request, data *framework.FieldData, role *pki.RoleEntry, useCSR, useCSRValues bool) (*logical.Response, error) { + + b.checkPkcs11ConnectionSync() + + // If storing the certificate and on a performance standby, forward this request on to the primary + if !role.NoStore && b.pkiBackend.Backend.System().ReplicationState().HasState(consts.ReplicationPerformanceStandby) { + return nil, logical.ErrReadOnly + } + + format := pki.GetFormat(data) + if format == "" { + return logical.ErrorResponse( + `the "format" path parameter must be "pem", "der", or "pem_bundle"`), nil + } + + var caErr error + signingBundle, caErr := pki.FetchCAInfo(ctx, req) + switch caErr.(type) { + case errutil.UserError: + return nil, errutil.UserError{Err: fmt.Sprintf( + "could not fetch the CA certificate (was one set?): %s", caErr)} + case errutil.InternalError: + return nil, errutil.InternalError{Err: fmt.Sprintf( + "error fetching CA certificate: %s", caErr)} + } + + input := &pki.InputBundleA{ + Req: req, + ApiData: data, + Role: role, + } + + var parsedBundle *certutil.ParsedCertBundle + var err error + + if useCSR { + b.pkiBackend.Backend.Logger().Info("do signCert") + parsedBundle, err = signCert(b, input, signingBundle, false, useCSRValues) + } else { + // TODO: generateCert with entropy aug + parsedBundle, err = generateCert(ctx, b, input, signingBundle, false) + } + if err != nil { + switch err.(type) { + case errutil.UserError: + return logical.ErrorResponse(err.Error()), nil + case errutil.InternalError: + return nil, err + default: + return nil, errwrap.Wrapf("error signing/generating certificate: {{err}}", err) + } + } + + signingCB, err := signingBundle.ToCertBundle() + if err != nil { + return nil, errwrap.Wrapf("error converting raw signing bundle to cert bundle: {{err}}", err) + } + + cb, err := parsedBundle.ToCertBundle() + if err != nil { + return nil, errwrap.Wrapf("error converting raw cert bundle to cert bundle: {{err}}", err) + } + + respData := map[string]interface{}{ + "expiration": int64(parsedBundle.Certificate.NotAfter.Unix()), + "serial_number": cb.SerialNumber, + } + + switch format { + case "pem": + respData["issuing_ca"] = signingCB.Certificate + respData["certificate"] = cb.Certificate + if cb.CAChain != nil && len(cb.CAChain) > 0 { + respData["ca_chain"] = cb.CAChain + } + if !useCSR { + respData["private_key"] = cb.PrivateKey + respData["private_key_type"] = cb.PrivateKeyType + } + + case "pem_bundle": + respData["issuing_ca"] = signingCB.Certificate + respData["certificate"] = cb.ToPEMBundle() + if cb.CAChain != nil && len(cb.CAChain) > 0 { + respData["ca_chain"] = cb.CAChain + } + if !useCSR { + respData["private_key"] = cb.PrivateKey + respData["private_key_type"] = cb.PrivateKeyType + } + + case "der": + respData["certificate"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes) + respData["issuing_ca"] = base64.StdEncoding.EncodeToString(signingBundle.CertificateBytes) + + var caChain []string + for _, caCert := range parsedBundle.CAChain { + caChain = append(caChain, base64.StdEncoding.EncodeToString(caCert.Bytes)) + } + if caChain != nil && len(caChain) > 0 { + respData["ca_chain"] = caChain + } + + if !useCSR { + respData["private_key"] = base64.StdEncoding.EncodeToString(parsedBundle.PrivateKeyBytes) + respData["private_key_type"] = cb.PrivateKeyType + } + } + + var resp *logical.Response + switch { + case role.GenerateLease == nil: + return nil, fmt.Errorf("generate lease in role is nil") + case *role.GenerateLease == false: + // If lease generation is disabled do not populate `Secret` field in + // the response + resp = &logical.Response{ + Data: respData, + } + default: + + resp = b.HsmBackend.Secret(pki.SecretCertsType).Response( + respData, + map[string]interface{}{ + "serial_number": cb.SerialNumber, + }) + resp.Secret.TTL = parsedBundle.Certificate.NotAfter.Sub(time.Now()) + + } + + if data.Get("private_key_format").(string) == "pkcs8" { + err = pki.ConvertRespToPKCS8(resp) + if err != nil { + return nil, err + } + } + + if !role.NoStore { + err = req.Storage.Put(ctx, &logical.StorageEntry{ + Key: "certs/" + pki.NormalizeSerial(cb.SerialNumber), + Value: parsedBundle.CertificateBytes, + }) + if err != nil { + return nil, errwrap.Wrapf("unable to store certificate locally: {{err}}", err) + } + } + + if useCSR { + if role.UseCSRCommonName && data.Get("common_name").(string) != "" { + resp.AddWarning("the common_name field was provided but the role is set with \"use_csr_common_name\" set to true") + } + if role.UseCSRSANs && data.Get("alt_names").(string) != "" { + resp.AddWarning("the alt_names field was provided but the role is set with \"use_csr_sans\" set to true") + } + } + + return resp, nil +} + +const pathIssueHelpSyn = ` +Request a certificate using a certain role with the provided details. +` + +const pathIssueHelpDesc = ` +This path allows requesting a certificate to be issued according to the +policy of the given role. The certificate will only be issued if the +requested details are allowed by the role policy. + +This path returns a certificate and a private key. If you want a workflow +that does not expose a private key, generate a CSR locally and use the +sign path instead. +` + +const pathSignHelpSyn = ` +Request certificates using a certain role with the provided details. +` + +const pathSignHelpDesc = ` +This path allows requesting certificates to be issued according to the +policy of the given role. The certificate will only be issued if the +requested common name is allowed by the role policy. + +This path requires a CSR; if you want Vault to generate a private key +for you, use the issue path instead. +` diff --git a/pkg/hsmpki/hsmpath_revoke.go b/pkg/hsmpki/hsmpath_revoke.go new file mode 100644 index 0000000..4253ce8 --- /dev/null +++ b/pkg/hsmpki/hsmpath_revoke.go @@ -0,0 +1,101 @@ +package hsmpki + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/consts" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" +) + +func pathRevoke(b *HsmPkiBackend) *framework.Path { + return &framework.Path{ + Pattern: `revoke`, + Fields: map[string]*framework.FieldSchema{ + "serial_number": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `Certificate serial number, in colon- or +hyphen-separated octal`, + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathRevokeWrite, + }, + + HelpSynopsis: pathRevokeHelpSyn, + HelpDescription: pathRevokeHelpDesc, + } +} + +func pathRotateCRL(b *HsmPkiBackend) *framework.Path { + return &framework.Path{ + Pattern: `crl/rotate`, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathRotateCRLRead, + }, + + HelpSynopsis: pathRotateCRLHelpSyn, + HelpDescription: pathRotateCRLHelpDesc, + } +} + +func (b *HsmPkiBackend) pathRevokeWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + serial := data.Get("serial_number").(string) + if len(serial) == 0 { + return logical.ErrorResponse("The serial number must be provided"), nil + } + + if b.pkiBackend.Backend.System().ReplicationState().HasState(consts.ReplicationPerformanceStandby) { + return nil, logical.ErrReadOnly + } + + // We store and identify by lowercase colon-separated hex, but other + // utilities use dashes and/or uppercase, so normalize + serial = strings.Replace(strings.ToLower(serial), "-", ":", -1) + + b.pkiBackend.GetRevokeStorageLock().Lock() + defer b.pkiBackend.GetRevokeStorageLock().Unlock() + + return revokeCert(ctx, b, req, serial, false) +} + +func (b *HsmPkiBackend) pathRotateCRLRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + b.pkiBackend.GetRevokeStorageLock().RLock() + defer b.pkiBackend.GetRevokeStorageLock().RUnlock() + + crlErr := buildCRL(ctx, b, req, false) + switch crlErr.(type) { + case errutil.UserError: + return logical.ErrorResponse(fmt.Sprintf("Error during CRL building: %s", crlErr)), nil + case errutil.InternalError: + return nil, errwrap.Wrapf("error encountered during CRL building: {{err}}", crlErr) + default: + return &logical.Response{ + Data: map[string]interface{}{ + "success": true, + }, + }, nil + } +} + +const pathRevokeHelpSyn = ` +Revoke a certificate by serial number. +` + +const pathRevokeHelpDesc = ` +This allows certificates to be revoked using its serial number. A root token is required. +` + +const pathRotateCRLHelpSyn = ` +Force a rebuild of the CRL. +` + +const pathRotateCRLHelpDesc = ` +Force a rebuild of the CRL. This can be used to remove expired certificates from it if no certificates have been revoked. A root token is required. +` diff --git a/pkg/hsmpki/hsmpki_config.go b/pkg/hsmpki/hsmpki_config.go new file mode 100644 index 0000000..d595994 --- /dev/null +++ b/pkg/hsmpki/hsmpki_config.go @@ -0,0 +1,83 @@ +package hsmpki + +import ( + "errors" + "github.com/mode51software/pkcs11helper/pkg/pkcs11client" + "strconv" +) + +type HsmPkiConfig struct { + + // the PKCS#11 client library file + Lib string + + // the slot ID on the HSM + SlotId string `hcl:"slot_id"` + + // the slot's PIN + Pin string + + // the HSM key label + KeyLabel string `hcl:"key_label"` + + // connection timeout seconds + ConnectTimeoutS string `hcl:"connect_timeout_s"` + + // function timeout seconds + ReadTimeoutS string `hcl:"read_timeout_s"` +} + +type HsmConfigCompat struct { + // the HSM's client PKCS#11 library + Lib string + + // the HSM slot ID + SlotId uint `json:"slot_id"` + + // the slot pin + Pin string + + // a key label + KeyLabel string `json:"key_label"` + + // connection timeout seconds + ConnectTimeoutS uint `json:"connect_timeout_s"` + + // function timeout seconds + ReadTimeoutS uint `json:"read_timeout_s"` +} + +// only check the presence of the client lib +// the slot could b 0, the pin could be blank and the key label could be set dynamically +func (h *HsmPkiConfig) ValidateConfig() error { + if len(h.Lib) == 0 { + return errors.New("Please specify the path of the PKCS#11 client library") + } + return nil +} + +func (h *HsmPkiConfig) ConvertHsmConfig(hsmConfig *HsmConfigCompat) { + + hsmConfig.Lib = h.Lib + slotId, _ := strconv.ParseUint(h.SlotId, 10, 32) + hsmConfig.SlotId = uint(slotId) + hsmConfig.Pin = h.Pin + connectTimeoutS, _ := strconv.ParseUint(h.ConnectTimeoutS, 10, 32) + hsmConfig.ConnectTimeoutS = uint(connectTimeoutS) + readTimeoutS, _ := strconv.ParseUint(h.ReadTimeoutS, 10, 32) + hsmConfig.ReadTimeoutS = uint(readTimeoutS) +} + +func (h *HsmPkiConfig) ConvertToHsmConfig() (hsmConfig *pkcs11client.HsmConfig) { + hsmConfig = &(pkcs11client.HsmConfig{}) + hsmConfig.Lib = h.Lib + slotId, _ := strconv.ParseUint(h.SlotId, 10, 32) + hsmConfig.SlotId = uint(slotId) + hsmConfig.Pin = h.Pin + hsmConfig.KeyLabel = h.KeyLabel + connectTimeoutS, _ := strconv.ParseUint(h.ConnectTimeoutS, 10, 32) + hsmConfig.ConnectTimeoutS = uint(connectTimeoutS) + readTimeoutS, _ := strconv.ParseUint(h.ReadTimeoutS, 10, 32) + hsmConfig.ReadTimeoutS = uint(readTimeoutS) + return +} diff --git a/pkg/pki/backend.go b/pkg/pki/backend.go new file mode 100644 index 0000000..a6ce1c3 --- /dev/null +++ b/pkg/pki/backend.go @@ -0,0 +1,107 @@ +package pki + +import ( + "sync" + "time" + + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" +) + +/* +// Factory creates a new backend implementing the logical.Backend interface +func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) { + b := Backend(conf) + if err := b.Setup(ctx, conf); err != nil { + return nil, err + } + return b, nil +} + +// Backend returns a new Backend framework struct +func Backend(conf *logical.BackendConfig) *backend { + var b backend + b.Backend = &framework.Backend{ + Help: strings.TrimSpace(backendHelp), + + PathsSpecial: &logical.Paths{ + Unauthenticated: []string{ + "cert/*", + "ca/pem", + "ca_chain", + "ca", + "crl/pem", + "crl", + }, + + LocalStorage: []string{ + "revoked/", + "crl", + "certs/", + }, + + Root: []string{ + "root", + "root/sign-self-issued", + }, + + SealWrapStorage: []string{ + "config/ca_bundle", + }, + }, + + Paths: []*framework.Path{ + pathListRoles(&b), + pathRoles(&b), + pathGenerateRoot(&b), + pathSignIntermediate(&b), + pathSignSelfIssued(&b), + pathDeleteRoot(&b), + pathGenerateIntermediate(&b), + pathSetSignedIntermediate(&b), + pathConfigCA(&b), + pathConfigCRL(&b), + pathConfigURLs(&b), + pathSignVerbatim(&b), + pathSign(&b), + pathIssue(&b), + pathRotateCRL(&b), + pathFetchCA(&b), + pathFetchCAChain(&b), + pathFetchCRL(&b), + pathFetchCRLViaCertPath(&b), + pathFetchValid(&b), + pathFetchListCerts(&b), + pathRevoke(&b), + pathTidy(&b), + }, + + Secrets: []*framework.Secret{ + secretCerts(&b), + }, + + BackendType: logical.TypeLogical, + } + + b.crlLifetime = time.Hour * 72 + b.tidyCASGuard = new(uint32) + b.storage = conf.StorageView + + return &b +}*/ + +type backend struct { + *framework.Backend + + storage logical.Storage + crlLifetime time.Duration + revokeStorageLock sync.RWMutex + tidyCASGuard *uint32 +} + +const backendHelp = ` +The PKI backend dynamically generates X509 server and client certificates. + +After mounting this backend, configure the CA using the "pem_bundle" endpoint within +the "config/" path. +` diff --git a/pkg/pki/ca_util.go b/pkg/pki/ca_util.go new file mode 100644 index 0000000..8692486 --- /dev/null +++ b/pkg/pki/ca_util.go @@ -0,0 +1,62 @@ +package pki + +import ( + "time" + + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/logical" +) + +func (b *backend) getGenerationParams( + data *framework.FieldData, +) (exported bool, format string, role *roleEntry, errorResp *logical.Response) { + exportedStr := data.Get("exported").(string) + switch exportedStr { + case "exported": + exported = true + case "internal": + default: + errorResp = logical.ErrorResponse( + `the "exported" path parameter must be "internal" or "exported"`) + return + } + + format = getFormat(data) + if format == "" { + errorResp = logical.ErrorResponse( + `the "format" path parameter must be "pem", "der", "der_pkcs", or "pem_bundle"`) + return + } + + role = &roleEntry{ + TTL: time.Duration(data.Get("ttl").(int)) * time.Second, + KeyType: data.Get("key_type").(string), + KeyBits: data.Get("key_bits").(int), + AllowLocalhost: true, + AllowAnyName: true, + AllowIPSANs: true, + EnforceHostnames: false, + AllowedURISANs: []string{"*"}, + AllowedOtherSANs: []string{"*"}, + AllowedSerialNumbers: []string{"*"}, + OU: data.Get("ou").([]string), + Organization: data.Get("organization").([]string), + Country: data.Get("country").([]string), + Locality: data.Get("locality").([]string), + Province: data.Get("province").([]string), + StreetAddress: data.Get("street_address").([]string), + PostalCode: data.Get("postal_code").([]string), + } + + if role.KeyType == "rsa" && role.KeyBits < 2048 { + errorResp = logical.ErrorResponse("RSA keys < 2048 bits are unsafe and not supported") + return + } + + if err := certutil.ValidateKeyTypeLength(role.KeyType, role.KeyBits); err != nil { + errorResp = logical.ErrorResponse(err.Error()) + } + + return +} diff --git a/pkg/pki/cert_util.go b/pkg/pki/cert_util.go new file mode 100644 index 0000000..2a2c9b9 --- /dev/null +++ b/pkg/pki/cert_util.go @@ -0,0 +1,1291 @@ +package pki + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/base64" + "encoding/pem" + "fmt" + "net" + "net/url" + "regexp" + "strconv" + "strings" + "time" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/helper/strutil" + "github.com/hashicorp/vault/sdk/logical" + "github.com/ryanuber/go-glob" + "golang.org/x/crypto/cryptobyte" + cbbasn1 "golang.org/x/crypto/cryptobyte/asn1" + "golang.org/x/net/idna" +) + +type inputBundle struct { + role *roleEntry + req *logical.Request + apiData *framework.FieldData +} + +var ( + // A note on hostnameRegex: although we set the StrictDomainName option + // when doing the idna conversion, this appears to only affect output, not + // input, so it will allow e.g. host^123.example.com straight through. So + // we still need to use this to check the output. + hostnameRegex = regexp.MustCompile(`^(\*\.)?(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])\.?$`) + oidExtensionBasicConstraints = []int{2, 5, 29, 19} + oidExtensionSubjectAltName = []int{2, 5, 29, 17} +) + +func oidInExtensions(oid asn1.ObjectIdentifier, extensions []pkix.Extension) bool { + for _, e := range extensions { + if e.Id.Equal(oid) { + return true + } + } + return false +} + +func getFormat(data *framework.FieldData) string { + format := data.Get("format").(string) + switch format { + case "pem": + case "der": + case "pem_bundle": + default: + format = "" + } + return format +} + +// Fetches the CA info. Unlike other certificates, the CA info is stored +// in the backend as a CertBundle, because we are storing its private key +func fetchCAInfo(ctx context.Context, req *logical.Request) (*certutil.CAInfoBundle, error) { + bundleEntry, err := req.Storage.Get(ctx, "config/ca_bundle") + if err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch local CA certificate/key: %v", err)} + } + if bundleEntry == nil { + return nil, errutil.UserError{Err: "backend must be configured with a CA certificate/key"} + } + + var bundle certutil.CertBundle + if err := bundleEntry.DecodeJSON(&bundle); err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("unable to decode local CA certificate/key: %v", err)} + } + + parsedBundle, err := bundle.ToParsedCertBundle() + if err != nil { + return nil, errutil.InternalError{Err: err.Error()} + } + + if parsedBundle.Certificate == nil { + return nil, errutil.InternalError{Err: "stored CA information not able to be parsed"} + } + + caInfo := &certutil.CAInfoBundle{*parsedBundle, nil} + + entries, err := getURLs(ctx, req) + if err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch URL information: %v", err)} + } + if entries == nil { + entries = &certutil.URLEntries{ + IssuingCertificates: []string{}, + CRLDistributionPoints: []string{}, + OCSPServers: []string{}, + } + } + caInfo.URLs = entries + + return caInfo, nil +} + +// Allows fetching certificates from the backend; it handles the slightly +// separate pathing for CA, CRL, and revoked certificates. +func fetchCertBySerial(ctx context.Context, req *logical.Request, prefix, serial string) (*logical.StorageEntry, error) { + var path, legacyPath string + var err error + var certEntry *logical.StorageEntry + + hyphenSerial := normalizeSerial(serial) + colonSerial := strings.Replace(strings.ToLower(serial), "-", ":", -1) + + switch { + // Revoked goes first as otherwise ca/crl get hardcoded paths which fail if + // we actually want revocation info + case strings.HasPrefix(prefix, "revoked/"): + legacyPath = "revoked/" + colonSerial + path = "revoked/" + hyphenSerial + case serial == "ca": + path = "ca" + case serial == "crl": + path = "crl" + default: + legacyPath = "certs/" + colonSerial + path = "certs/" + hyphenSerial + } + + certEntry, err = req.Storage.Get(ctx, path) + if err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("error fetching certificate %s: %s", serial, err)} + } + if certEntry != nil { + if certEntry.Value == nil || len(certEntry.Value) == 0 { + return nil, errutil.InternalError{Err: fmt.Sprintf("returned certificate bytes for serial %s were empty", serial)} + } + return certEntry, nil + } + + // If legacyPath is unset, it's going to be a CA or CRL; return immediately + if legacyPath == "" { + return nil, nil + } + + // Retrieve the old-style path. We disregard errors here because they + // always manifest on windows, and thus the initial check for a revoked + // cert fails would return an error when the cert isn't revoked, preventing + // the happy path from working. + certEntry, _ = req.Storage.Get(ctx, legacyPath) + if certEntry == nil { + return nil, nil + } + if certEntry.Value == nil || len(certEntry.Value) == 0 { + return nil, errutil.InternalError{Err: fmt.Sprintf("returned certificate bytes for serial %s were empty", serial)} + } + + // Update old-style paths to new-style paths + certEntry.Key = path + if err = req.Storage.Put(ctx, certEntry); err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("error saving certificate with serial %s to new location", serial)} + } + if err = req.Storage.Delete(ctx, legacyPath); err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("error deleting certificate with serial %s from old location", serial)} + } + + return certEntry, nil +} + +// Given a set of requested names for a certificate, verifies that all of them +// match the various toggles set in the role for controlling issuance. +// If one does not pass, it is returned in the string argument. +func validateNames(b *backend, data *inputBundle, names []string) string { + for _, name := range names { + sanitizedName := name + emailDomain := name + isEmail := false + isWildcard := false + + // If it has an @, assume it is an email address and separate out the + // user from the hostname portion so that we can act on the hostname. + // Note that this matches behavior from the alt_names parameter. If it + // ends up being problematic for users, I guess that could be separated + // into dns_names and email_names in the future to be explicit, but I + // don't think this is likely. + if strings.Contains(name, "@") { + splitEmail := strings.Split(name, "@") + if len(splitEmail) != 2 { + return name + } + sanitizedName = splitEmail[1] + emailDomain = splitEmail[1] + isEmail = true + } + + // If we have an asterisk as the first part of the domain name, mark it + // as wildcard and set the sanitized name to the remainder of the + // domain + if strings.HasPrefix(sanitizedName, "*.") { + sanitizedName = sanitizedName[2:] + isWildcard = true + } + + // Email addresses using wildcard domain names do not make sense + if isEmail && isWildcard { + return name + } + + // AllowAnyName is checked after this because EnforceHostnames still + // applies when allowing any name. Also, we check the sanitized name to + // ensure that we are not either checking a full email address or a + // wildcard prefix. + if data.role.EnforceHostnames { + p := idna.New( + idna.StrictDomainName(true), + idna.VerifyDNSLength(true), + ) + converted, err := p.ToASCII(sanitizedName) + if err != nil { + return name + } + if !hostnameRegex.MatchString(converted) { + return name + } + } + + // Self-explanatory + if data.role.AllowAnyName { + continue + } + + // The following blocks all work the same basic way: + // 1) If a role allows a certain class of base (localhost, token + // display name, role-configured domains), perform further tests + // + // 2) If there is a perfect match on either the name itself or it's an + // email address with a perfect match on the hostname portion, allow it + // + // 3) If subdomains are allowed, we check based on the sanitized name; + // note that if not a wildcard, will be equivalent to the email domain + // for email checks, and we already checked above for both a wildcard + // and email address being present in the same name + // 3a) First we check for a non-wildcard subdomain, as in . + // 3b) Then we check if it's a wildcard and the base domain is a match + // + // Variances are noted in-line + + if data.role.AllowLocalhost { + if name == "localhost" || + name == "localdomain" || + (isEmail && emailDomain == "localhost") || + (isEmail && emailDomain == "localdomain") { + continue + } + + if data.role.AllowSubdomains { + // It is possible, if unlikely, to have a subdomain of "localhost" + if strings.HasSuffix(sanitizedName, ".localhost") || + (isWildcard && sanitizedName == "localhost") { + continue + } + + // A subdomain of "localdomain" is also not entirely uncommon + if strings.HasSuffix(sanitizedName, ".localdomain") || + (isWildcard && sanitizedName == "localdomain") { + continue + } + } + } + + if data.role.AllowTokenDisplayName { + if name == data.req.DisplayName { + continue + } + + if data.role.AllowSubdomains { + if isEmail { + // If it's an email address, we need to parse the token + // display name in order to do a proper comparison of the + // subdomain + if strings.Contains(data.req.DisplayName, "@") { + splitDisplay := strings.Split(data.req.DisplayName, "@") + if len(splitDisplay) == 2 { + // Compare the sanitized name against the hostname + // portion of the email address in the broken + // display name + if strings.HasSuffix(sanitizedName, "."+splitDisplay[1]) { + continue + } + } + } + } + + if strings.HasSuffix(sanitizedName, "."+data.req.DisplayName) || + (isWildcard && sanitizedName == data.req.DisplayName) { + continue + } + } + } + + if len(data.role.AllowedDomains) > 0 { + valid := false + for _, currDomain := range data.role.AllowedDomains { + // If there is, say, a trailing comma, ignore it + if currDomain == "" { + continue + } + + if data.role.AllowedDomainsTemplate { + isTemplate, _ := framework.ValidateIdentityTemplate(currDomain) + if isTemplate && data.req.EntityID != "" { + tmpCurrDomain, err := framework.PopulateIdentityTemplate(currDomain, data.req.EntityID, b.System()) + if err != nil { + continue + } + + currDomain = tmpCurrDomain + } + } + + // First, allow an exact match of the base domain if that role flag + // is enabled + if data.role.AllowBareDomains && + (name == currDomain || + (isEmail && emailDomain == currDomain)) { + valid = true + break + } + + if data.role.AllowSubdomains { + if strings.HasSuffix(sanitizedName, "."+currDomain) || + (isWildcard && sanitizedName == currDomain) { + valid = true + break + } + } + + if data.role.AllowGlobDomains && + strings.Contains(currDomain, "*") && + glob.Glob(currDomain, name) { + valid = true + break + } + } + + if valid { + continue + } + } + + return name + } + + return "" +} + +// validateOtherSANs checks if the values requested are allowed. If an OID +// isn't allowed, it will be returned as the first string. If a value isn't +// allowed, it will be returned as the second string. Empty strings + error +// means everything is okay. +func validateOtherSANs(data *inputBundle, requested map[string][]string) (string, string, error) { + if len(data.role.AllowedOtherSANs) == 1 && data.role.AllowedOtherSANs[0] == "*" { + // Anything is allowed + return "", "", nil + } + + allowed, err := parseOtherSANs(data.role.AllowedOtherSANs) + if err != nil { + return "", "", errwrap.Wrapf("error parsing role's allowed SANs: {{err}}", err) + } + for oid, names := range requested { + for _, name := range names { + allowedNames, ok := allowed[oid] + if !ok { + return oid, "", nil + } + + valid := false + for _, allowedName := range allowedNames { + if glob.Glob(allowedName, name) { + valid = true + break + } + } + + if !valid { + return oid, name, nil + } + } + } + + return "", "", nil +} + +func parseOtherSANs(others []string) (map[string][]string, error) { + result := map[string][]string{} + for _, other := range others { + splitOther := strings.SplitN(other, ";", 2) + if len(splitOther) != 2 { + return nil, fmt.Errorf("expected a semicolon in other SAN %q", other) + } + splitType := strings.SplitN(splitOther[1], ":", 2) + if len(splitType) != 2 { + return nil, fmt.Errorf("expected a colon in other SAN %q", other) + } + switch { + case strings.EqualFold(splitType[0], "utf8"): + case strings.EqualFold(splitType[0], "utf-8"): + default: + return nil, fmt.Errorf("only utf8 other SANs are supported; found non-supported type in other SAN %q", other) + } + result[splitOther[0]] = append(result[splitOther[0]], splitType[1]) + } + + return result, nil +} + +func validateSerialNumber(data *inputBundle, serialNumber string) string { + valid := false + if len(data.role.AllowedSerialNumbers) > 0 { + for _, currSerialNumber := range data.role.AllowedSerialNumbers { + if currSerialNumber == "" { + continue + } + + if (strings.Contains(currSerialNumber, "*") && + glob.Glob(currSerialNumber, serialNumber)) || + currSerialNumber == serialNumber { + valid = true + break + } + } + } + if !valid { + return serialNumber + } else { + return "" + } +} + +func generateCert(ctx context.Context, + b *backend, + input *inputBundle, + caSign *certutil.CAInfoBundle, + isCA bool) (*certutil.ParsedCertBundle, error) { + + if input.role == nil { + return nil, errutil.InternalError{Err: "no role found in data bundle"} + } + + if input.role.KeyType == "rsa" && input.role.KeyBits < 2048 { + return nil, errutil.UserError{Err: "RSA keys < 2048 bits are unsafe and not supported"} + } + + data, err := generateCreationBundle(b, input, caSign, nil) + if err != nil { + return nil, err + } + if data.Params == nil { + return nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"} + } + + if isCA { + data.Params.IsCA = isCA + data.Params.PermittedDNSDomains = input.apiData.Get("permitted_dns_domains").([]string) + + if data.SigningBundle == nil { + // Generating a self-signed root certificate + entries, err := getURLs(ctx, input.req) + if err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch URL information: %v", err)} + } + if entries == nil { + entries = &certutil.URLEntries{ + IssuingCertificates: []string{}, + CRLDistributionPoints: []string{}, + OCSPServers: []string{}, + } + } + data.Params.URLs = entries + + if input.role.MaxPathLength == nil { + data.Params.MaxPathLength = -1 + } else { + data.Params.MaxPathLength = *input.role.MaxPathLength + } + } + } + + parsedBundle, err := certutil.CreateCertificate(data) + if err != nil { + return nil, err + } + + return parsedBundle, nil +} + +// N.B.: This is only meant to be used for generating intermediate CAs. +// It skips some sanity checks. +func generateIntermediateCSR(b *backend, input *inputBundle) (*certutil.ParsedCSRBundle, error) { + creation, err := generateCreationBundle(b, input, nil, nil) + if err != nil { + return nil, err + } + if creation.Params == nil { + return nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"} + } + + addBasicConstraints := input.apiData != nil && input.apiData.Get("add_basic_constraints").(bool) + parsedBundle, err := certutil.CreateCSR(creation, addBasicConstraints) + if err != nil { + return nil, err + } + + return parsedBundle, nil +} + +func signCert(b *backend, + data *inputBundle, + caSign *certutil.CAInfoBundle, + isCA bool, + useCSRValues bool) (*certutil.ParsedCertBundle, error) { + + if data.role == nil { + return nil, errutil.InternalError{Err: "no role found in data bundle"} + } + + csrString := data.apiData.Get("csr").(string) + if csrString == "" { + return nil, errutil.UserError{Err: fmt.Sprintf("\"csr\" is empty")} + } + + pemBytes := []byte(csrString) + pemBlock, pemBytes := pem.Decode(pemBytes) + if pemBlock == nil { + return nil, errutil.UserError{Err: "csr contains no data"} + } + csr, err := x509.ParseCertificateRequest(pemBlock.Bytes) + if err != nil { + return nil, errutil.UserError{Err: fmt.Sprintf("certificate request could not be parsed: %v", err)} + } + + switch data.role.KeyType { + case "rsa": + // Verify that the key matches the role type + if csr.PublicKeyAlgorithm != x509.RSA { + return nil, errutil.UserError{Err: fmt.Sprintf( + "role requires keys of type %s", + data.role.KeyType)} + } + pubKey, ok := csr.PublicKey.(*rsa.PublicKey) + if !ok { + return nil, errutil.UserError{Err: "could not parse CSR's public key"} + } + + // Verify that the key is at least 2048 bits + if pubKey.N.BitLen() < 2048 { + return nil, errutil.UserError{Err: "RSA keys < 2048 bits are unsafe and not supported"} + } + + // Verify that the bit size is at least the size specified in the role + if pubKey.N.BitLen() < data.role.KeyBits { + return nil, errutil.UserError{Err: fmt.Sprintf( + "role requires a minimum of a %d-bit key, but CSR's key is %d bits", + data.role.KeyBits, + pubKey.N.BitLen())} + } + + case "ec": + // Verify that the key matches the role type + if csr.PublicKeyAlgorithm != x509.ECDSA { + return nil, errutil.UserError{Err: fmt.Sprintf( + "role requires keys of type %s", + data.role.KeyType)} + } + pubKey, ok := csr.PublicKey.(*ecdsa.PublicKey) + if !ok { + return nil, errutil.UserError{Err: "could not parse CSR's public key"} + } + + // Verify that the bit size is at least the size specified in the role + if pubKey.Params().BitSize < data.role.KeyBits { + return nil, errutil.UserError{Err: fmt.Sprintf( + "role requires a minimum of a %d-bit key, but CSR's key is %d bits", + data.role.KeyBits, + pubKey.Params().BitSize)} + } + + case "any": + // We only care about running RSA < 2048 bit checks, so if not RSA + // break out + if csr.PublicKeyAlgorithm != x509.RSA { + break + } + + // Run RSA < 2048 bit checks + pubKey, ok := csr.PublicKey.(*rsa.PublicKey) + if !ok { + return nil, errutil.UserError{Err: "could not parse CSR's public key"} + } + if pubKey.N.BitLen() < 2048 { + return nil, errutil.UserError{Err: "RSA keys < 2048 bits are unsafe and not supported"} + } + + } + + creation, err := generateCreationBundle(b, data, caSign, csr) + if err != nil { + return nil, err + } + if creation.Params == nil { + return nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"} + } + + creation.Params.IsCA = isCA + creation.Params.UseCSRValues = useCSRValues + + if isCA { + creation.Params.PermittedDNSDomains = data.apiData.Get("permitted_dns_domains").([]string) + } + + parsedBundle, err := certutil.SignCertificate(creation) + if err != nil { + return nil, err + } + + return parsedBundle, nil +} + +// otherNameRaw describes a name related to a certificate which is not in one +// of the standard name formats. RFC 5280, 4.2.1.6: +// OtherName ::= SEQUENCE { +// type-id OBJECT IDENTIFIER, +// value [0] EXPLICIT ANY DEFINED BY type-id } +type otherNameRaw struct { + TypeID asn1.ObjectIdentifier + Value asn1.RawValue +} + +type otherNameUtf8 struct { + oid string + value string +} + +// ExtractUTF8String returns the UTF8 string contained in the Value, or an error +// if none is present. +func (oraw *otherNameRaw) extractUTF8String() (*otherNameUtf8, error) { + svalue := cryptobyte.String(oraw.Value.Bytes) + var outTag cbbasn1.Tag + var val cryptobyte.String + read := svalue.ReadAnyASN1(&val, &outTag) + + if read && outTag == asn1.TagUTF8String { + return &otherNameUtf8{oid: oraw.TypeID.String(), value: string(val)}, nil + } + return nil, fmt.Errorf("no UTF-8 string found in OtherName") +} + +func (o otherNameUtf8) String() string { + return fmt.Sprintf("%s;%s:%s", o.oid, "UTF-8", o.value) +} + +func getOtherSANsFromX509Extensions(exts []pkix.Extension) ([]otherNameUtf8, error) { + var ret []otherNameUtf8 + for _, ext := range exts { + if !ext.Id.Equal(oidExtensionSubjectAltName) { + continue + } + err := forEachSAN(ext.Value, func(tag int, data []byte) error { + if tag != 0 { + return nil + } + + var other otherNameRaw + _, err := asn1.UnmarshalWithParams(data, &other, "tag:0") + if err != nil { + return errwrap.Wrapf("could not parse requested other SAN: {{err}}", err) + } + val, err := other.extractUTF8String() + if err != nil { + return err + } + ret = append(ret, *val) + return nil + }) + if err != nil { + return nil, err + } + } + + return ret, nil +} + +func forEachSAN(extension []byte, callback func(tag int, data []byte) error) error { + // RFC 5280, 4.2.1.6 + + // SubjectAltName ::= GeneralNames + // + // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName + // + // GeneralName ::= CHOICE { + // otherName [0] OtherName, + // rfc822Name [1] IA5String, + // dNSName [2] IA5String, + // x400Address [3] ORAddress, + // directoryName [4] Name, + // ediPartyName [5] EDIPartyName, + // uniformResourceIdentifier [6] IA5String, + // iPAddress [7] OCTET STRING, + // registeredID [8] OBJECT IDENTIFIER } + var seq asn1.RawValue + rest, err := asn1.Unmarshal(extension, &seq) + if err != nil { + return err + } else if len(rest) != 0 { + return fmt.Errorf("x509: trailing data after X.509 extension") + } + if !seq.IsCompound || seq.Tag != 16 || seq.Class != 0 { + return asn1.StructuralError{Msg: "bad SAN sequence"} + } + + rest = seq.Bytes + for len(rest) > 0 { + var v asn1.RawValue + rest, err = asn1.Unmarshal(rest, &v) + if err != nil { + return err + } + + if err := callback(v.Tag, v.FullBytes); err != nil { + return err + } + } + + return nil +} + +// generateCreationBundle is a shared function that reads parameters supplied +// from the various endpoints and generates a CreationParameters with the +// parameters that can be used to issue or sign +func generateCreationBundle(b *backend, data *inputBundle, caSign *certutil.CAInfoBundle, csr *x509.CertificateRequest) (*certutil.CreationBundle, error) { + // Read in names -- CN, DNS and email addresses + var cn string + var ridSerialNumber string + dnsNames := []string{} + emailAddresses := []string{} + { + if csr != nil && data.role.UseCSRCommonName { + cn = csr.Subject.CommonName + } + if cn == "" { + cn = data.apiData.Get("common_name").(string) + if cn == "" && data.role.RequireCN { + return nil, errutil.UserError{Err: `the common_name field is required, or must be provided in a CSR with "use_csr_common_name" set to true, unless "require_cn" is set to false`} + } + } + + ridSerialNumber = data.apiData.Get("serial_number").(string) + + // only take serial number from CSR if one was not supplied via API + if ridSerialNumber == "" && csr != nil { + ridSerialNumber = csr.Subject.SerialNumber + } + + if csr != nil && data.role.UseCSRSANs { + dnsNames = csr.DNSNames + emailAddresses = csr.EmailAddresses + } + + if cn != "" && !data.apiData.Get("exclude_cn_from_sans").(bool) { + if strings.Contains(cn, "@") { + // Note: emails are not disallowed if the role's email protection + // flag is false, because they may well be included for + // informational purposes; it is up to the verifying party to + // ensure that email addresses in a subject alternate name can be + // used for the purpose for which they are presented + emailAddresses = append(emailAddresses, cn) + } else { + // Only add to dnsNames if it's actually a DNS name but convert + // idn first + p := idna.New( + idna.StrictDomainName(true), + idna.VerifyDNSLength(true), + ) + converted, err := p.ToASCII(cn) + if err != nil { + return nil, errutil.UserError{Err: err.Error()} + } + if hostnameRegex.MatchString(converted) { + dnsNames = append(dnsNames, converted) + } + } + } + + if csr == nil || !data.role.UseCSRSANs { + cnAltRaw, ok := data.apiData.GetOk("alt_names") + if ok { + cnAlt := strutil.ParseDedupLowercaseAndSortStrings(cnAltRaw.(string), ",") + for _, v := range cnAlt { + if strings.Contains(v, "@") { + emailAddresses = append(emailAddresses, v) + } else { + // Only add to dnsNames if it's actually a DNS name but + // convert idn first + p := idna.New( + idna.StrictDomainName(true), + idna.VerifyDNSLength(true), + ) + converted, err := p.ToASCII(v) + if err != nil { + return nil, errutil.UserError{Err: err.Error()} + } + if hostnameRegex.MatchString(converted) { + dnsNames = append(dnsNames, converted) + } + } + } + } + } + + // Check the CN. This ensures that the CN is checked even if it's + // excluded from SANs. + if cn != "" { + badName := validateNames(b, data, []string{cn}) + if len(badName) != 0 { + return nil, errutil.UserError{Err: fmt.Sprintf( + "common name %s not allowed by this role", badName)} + } + } + + if ridSerialNumber != "" { + badName := validateSerialNumber(data, ridSerialNumber) + if len(badName) != 0 { + return nil, errutil.UserError{Err: fmt.Sprintf( + "serial_number %s not allowed by this role", badName)} + } + } + + // Check for bad email and/or DNS names + badName := validateNames(b, data, dnsNames) + if len(badName) != 0 { + return nil, errutil.UserError{Err: fmt.Sprintf( + "subject alternate name %s not allowed by this role", badName)} + } + + badName = validateNames(b, data, emailAddresses) + if len(badName) != 0 { + return nil, errutil.UserError{Err: fmt.Sprintf( + "email address %s not allowed by this role", badName)} + } + } + + // otherSANsInput has the same format as the other_sans HTTP param in the + // Vault PKI API: it is a list of strings of the form ;: + // where must be UTF8/UTF-8. + var otherSANsInput []string + // otherSANs is the output of parseOtherSANs(otherSANsInput): its keys are + // the value, its values are of the form [, ] + var otherSANs map[string][]string + if sans := data.apiData.Get("other_sans").([]string); len(sans) > 0 { + otherSANsInput = sans + } + if data.role.UseCSRSANs && csr != nil && len(csr.Extensions) > 0 { + others, err := getOtherSANsFromX509Extensions(csr.Extensions) + if err != nil { + return nil, errutil.UserError{Err: errwrap.Wrapf("could not parse requested other SAN: {{err}}", err).Error()} + } + for _, other := range others { + otherSANsInput = append(otherSANsInput, other.String()) + } + } + if len(otherSANsInput) > 0 { + requested, err := parseOtherSANs(otherSANsInput) + if err != nil { + return nil, errutil.UserError{Err: errwrap.Wrapf("could not parse requested other SAN: {{err}}", err).Error()} + } + badOID, badName, err := validateOtherSANs(data, requested) + switch { + case err != nil: + return nil, errutil.UserError{Err: err.Error()} + case len(badName) > 0: + return nil, errutil.UserError{Err: fmt.Sprintf( + "other SAN %s not allowed for OID %s by this role", badName, badOID)} + case len(badOID) > 0: + return nil, errutil.UserError{Err: fmt.Sprintf( + "other SAN OID %s not allowed by this role", badOID)} + default: + otherSANs = requested + } + } + + // Get and verify any IP SANs + ipAddresses := []net.IP{} + { + if csr != nil && data.role.UseCSRSANs { + if len(csr.IPAddresses) > 0 { + if !data.role.AllowIPSANs { + return nil, errutil.UserError{Err: fmt.Sprintf( + "IP Subject Alternative Names are not allowed in this role, but was provided some via CSR")} + } + ipAddresses = csr.IPAddresses + } + } else { + ipAlt := data.apiData.Get("ip_sans").([]string) + if len(ipAlt) > 0 { + if !data.role.AllowIPSANs { + return nil, errutil.UserError{Err: fmt.Sprintf( + "IP Subject Alternative Names are not allowed in this role, but was provided %s", ipAlt)} + } + for _, v := range ipAlt { + parsedIP := net.ParseIP(v) + if parsedIP == nil { + return nil, errutil.UserError{Err: fmt.Sprintf( + "the value '%s' is not a valid IP address", v)} + } + ipAddresses = append(ipAddresses, parsedIP) + } + } + } + } + + URIs := []*url.URL{} + { + if csr != nil && data.role.UseCSRSANs { + if len(csr.URIs) > 0 { + if len(data.role.AllowedURISANs) == 0 { + return nil, errutil.UserError{Err: fmt.Sprintf( + "URI Subject Alternative Names are not allowed in this role, but were provided via CSR"), + } + } + + // validate uri sans + for _, uri := range csr.URIs { + valid := false + for _, allowed := range data.role.AllowedURISANs { + validURI := glob.Glob(allowed, uri.String()) + if validURI { + valid = true + break + } + } + + if !valid { + return nil, errutil.UserError{Err: fmt.Sprintf( + "URI Subject Alternative Names were provided via CSR which are not valid for this role"), + } + } + + URIs = append(URIs, uri) + } + } + } else { + uriAlt := data.apiData.Get("uri_sans").([]string) + if len(uriAlt) > 0 { + if len(data.role.AllowedURISANs) == 0 { + return nil, errutil.UserError{Err: fmt.Sprintf( + "URI Subject Alternative Names are not allowed in this role, but were provided via the API"), + } + } + + for _, uri := range uriAlt { + valid := false + for _, allowed := range data.role.AllowedURISANs { + validURI := glob.Glob(allowed, uri) + if validURI { + valid = true + break + } + } + + if !valid { + return nil, errutil.UserError{Err: fmt.Sprintf( + "URI Subject Alternative Names were provided via the API which are not valid for this role"), + } + } + + parsedURI, err := url.Parse(uri) + if parsedURI == nil || err != nil { + return nil, errutil.UserError{Err: fmt.Sprintf( + "the provided URI Subject Alternative Name '%s' is not a valid URI", uri), + } + } + + URIs = append(URIs, parsedURI) + } + } + } + } + + // Most of these could also be RemoveDuplicateStable, or even + // leave duplicates in, but OU is the one most likely to be duplicated. + subject := pkix.Name{ + CommonName: cn, + SerialNumber: ridSerialNumber, + Country: strutil.RemoveDuplicates(data.role.Country, false), + Organization: strutil.RemoveDuplicates(data.role.Organization, false), + OrganizationalUnit: strutil.RemoveDuplicatesStable(data.role.OU, false), + Locality: strutil.RemoveDuplicates(data.role.Locality, false), + Province: strutil.RemoveDuplicates(data.role.Province, false), + StreetAddress: strutil.RemoveDuplicates(data.role.StreetAddress, false), + PostalCode: strutil.RemoveDuplicates(data.role.PostalCode, false), + } + + // Get the TTL and verify it against the max allowed + var ttl time.Duration + var maxTTL time.Duration + var notAfter time.Time + { + ttl = time.Duration(data.apiData.Get("ttl").(int)) * time.Second + + if ttl == 0 && data.role.TTL > 0 { + ttl = data.role.TTL + } + + if data.role.MaxTTL > 0 { + maxTTL = data.role.MaxTTL + } + + if ttl == 0 { + ttl = b.System().DefaultLeaseTTL() + } + if maxTTL == 0 { + maxTTL = b.System().MaxLeaseTTL() + } + if ttl > maxTTL { + ttl = maxTTL + } + + notAfter = time.Now().Add(ttl) + + // If it's not self-signed, verify that the issued certificate won't be + // valid past the lifetime of the CA certificate + if caSign != nil && + notAfter.After(caSign.Certificate.NotAfter) && !data.role.AllowExpirationPastCA { + + return nil, errutil.UserError{Err: fmt.Sprintf( + "cannot satisfy request, as TTL would result in notAfter %s that is beyond the expiration of the CA certificate at %s", notAfter.Format(time.RFC3339Nano), caSign.Certificate.NotAfter.Format(time.RFC3339Nano))} + } + } + + creation := &certutil.CreationBundle{ + Params: &certutil.CreationParameters{ + Subject: subject, + DNSNames: strutil.RemoveDuplicates(dnsNames, false), + EmailAddresses: strutil.RemoveDuplicates(emailAddresses, false), + IPAddresses: ipAddresses, + URIs: URIs, + OtherSANs: otherSANs, + KeyType: data.role.KeyType, + KeyBits: data.role.KeyBits, + NotAfter: notAfter, + KeyUsage: x509.KeyUsage(parseKeyUsages(data.role.KeyUsage)), + ExtKeyUsage: parseExtKeyUsages(data.role), + ExtKeyUsageOIDs: data.role.ExtKeyUsageOIDs, + PolicyIdentifiers: data.role.PolicyIdentifiers, + BasicConstraintsValidForNonCA: data.role.BasicConstraintsValidForNonCA, + NotBeforeDuration: data.role.NotBeforeDuration, + }, + SigningBundle: caSign, + CSR: csr, + } + + // Don't deal with URLs or max path length if it's self-signed, as these + // normally come from the signing bundle + if caSign == nil { + return creation, nil + } + + // This will have been read in from the getURLs function + creation.Params.URLs = caSign.URLs + + // If the max path length in the role is not nil, it was specified at + // generation time with the max_path_length parameter; otherwise derive it + // from the signing certificate + if data.role.MaxPathLength != nil { + creation.Params.MaxPathLength = *data.role.MaxPathLength + } else { + switch { + case caSign.Certificate.MaxPathLen < 0: + creation.Params.MaxPathLength = -1 + case caSign.Certificate.MaxPathLen == 0 && + caSign.Certificate.MaxPathLenZero: + // The signing function will ensure that we do not issue a CA cert + creation.Params.MaxPathLength = 0 + default: + // If this takes it to zero, we handle this case later if + // necessary + creation.Params.MaxPathLength = caSign.Certificate.MaxPathLen - 1 + } + } + + return creation, nil +} + +func convertRespToPKCS8(resp *logical.Response) error { + privRaw, ok := resp.Data["private_key"] + if !ok { + return nil + } + priv, ok := privRaw.(string) + if !ok { + return fmt.Errorf("error converting response to pkcs8: could not parse original value as string") + } + + privKeyTypeRaw, ok := resp.Data["private_key_type"] + if !ok { + return fmt.Errorf("error converting response to pkcs8: %q not found in response", "private_key_type") + } + privKeyType, ok := privKeyTypeRaw.(certutil.PrivateKeyType) + if !ok { + return fmt.Errorf("error converting response to pkcs8: could not parse original type value as string") + } + + var keyData []byte + var pemUsed bool + var err error + var signer crypto.Signer + + block, _ := pem.Decode([]byte(priv)) + if block == nil { + keyData, err = base64.StdEncoding.DecodeString(priv) + if err != nil { + return errwrap.Wrapf("error converting response to pkcs8: error decoding original value: {{err}}", err) + } + } else { + keyData = block.Bytes + pemUsed = true + } + + switch privKeyType { + case certutil.RSAPrivateKey: + signer, err = x509.ParsePKCS1PrivateKey(keyData) + case certutil.ECPrivateKey: + signer, err = x509.ParseECPrivateKey(keyData) + default: + return fmt.Errorf("unknown private key type %q", privKeyType) + } + if err != nil { + return errwrap.Wrapf("error converting response to pkcs8: error parsing previous key: {{err}}", err) + } + + keyData, err = x509.MarshalPKCS8PrivateKey(signer) + if err != nil { + return errwrap.Wrapf("error converting response to pkcs8: error marshaling pkcs8 key: {{err}}", err) + } + + if pemUsed { + block.Type = "PRIVATE KEY" + block.Bytes = keyData + resp.Data["private_key"] = strings.TrimSpace(string(pem.EncodeToMemory(block))) + } else { + resp.Data["private_key"] = base64.StdEncoding.EncodeToString(keyData) + } + + return nil +} + +func handleOtherCSRSANs(in *x509.CertificateRequest, sans map[string][]string) error { + certTemplate := &x509.Certificate{ + DNSNames: in.DNSNames, + IPAddresses: in.IPAddresses, + EmailAddresses: in.EmailAddresses, + URIs: in.URIs, + } + if err := handleOtherSANs(certTemplate, sans); err != nil { + return err + } + if len(certTemplate.ExtraExtensions) > 0 { + for _, v := range certTemplate.ExtraExtensions { + in.ExtraExtensions = append(in.ExtraExtensions, v) + } + } + return nil +} + +func handleOtherSANs(in *x509.Certificate, sans map[string][]string) error { + // If other SANs is empty we return which causes normal Go stdlib parsing + // of the other SAN types + if len(sans) == 0 { + return nil + } + + var rawValues []asn1.RawValue + + // We need to generate an IMPLICIT sequence for compatibility with OpenSSL + // -- it's an open question what the default for RFC 5280 actually is, see + // https://github.com/openssl/openssl/issues/5091 -- so we have to use + // cryptobyte because using the asn1 package's marshaling always produces + // an EXPLICIT sequence. Note that asn1 is way too magical according to + // agl, and cryptobyte is modeled after the CBB/CBS bits that agl put into + // boringssl. + for oid, vals := range sans { + for _, val := range vals { + var b cryptobyte.Builder + oidStr, err := stringToOid(oid) + if err != nil { + return err + } + b.AddASN1ObjectIdentifier(oidStr) + b.AddASN1(cbbasn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { + b.AddASN1(cbbasn1.UTF8String, func(b *cryptobyte.Builder) { + b.AddBytes([]byte(val)) + }) + }) + m, err := b.Bytes() + if err != nil { + return err + } + rawValues = append(rawValues, asn1.RawValue{Tag: 0, Class: 2, IsCompound: true, Bytes: m}) + } + } + + // If other SANs is empty we return which causes normal Go stdlib parsing + // of the other SAN types + if len(rawValues) == 0 { + return nil + } + + // Append any existing SANs, sans marshalling + rawValues = append(rawValues, marshalSANs(in.DNSNames, in.EmailAddresses, in.IPAddresses, in.URIs)...) + + // Marshal and add to ExtraExtensions + ext := pkix.Extension{ + // This is the defined OID for subjectAltName + Id: asn1.ObjectIdentifier(oidExtensionSubjectAltName), + } + var err error + ext.Value, err = asn1.Marshal(rawValues) + if err != nil { + return err + } + in.ExtraExtensions = append(in.ExtraExtensions, ext) + + return nil +} + +// Note: Taken from the Go source code since it's not public, and used in the +// modified function below (which also uses these consts upstream) +const ( + nameTypeOther = 0 + nameTypeEmail = 1 + nameTypeDNS = 2 + nameTypeURI = 6 + nameTypeIP = 7 +) + +// Note: Taken from the Go source code since it's not public, plus changed to not marshal +// marshalSANs marshals a list of addresses into a the contents of an X.509 +// SubjectAlternativeName extension. +func marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL) []asn1.RawValue { + var rawValues []asn1.RawValue + for _, name := range dnsNames { + rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeDNS, Class: 2, Bytes: []byte(name)}) + } + for _, email := range emailAddresses { + rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeEmail, Class: 2, Bytes: []byte(email)}) + } + for _, rawIP := range ipAddresses { + // If possible, we always want to encode IPv4 addresses in 4 bytes. + ip := rawIP.To4() + if ip == nil { + ip = rawIP + } + rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeIP, Class: 2, Bytes: ip}) + } + for _, uri := range uris { + rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeURI, Class: 2, Bytes: []byte(uri.String())}) + } + return rawValues +} + +func stringToOid(in string) (asn1.ObjectIdentifier, error) { + split := strings.Split(in, ".") + ret := make(asn1.ObjectIdentifier, 0, len(split)) + for _, v := range split { + i, err := strconv.Atoi(v) + if err != nil { + return nil, err + } + ret = append(ret, i) + } + return asn1.ObjectIdentifier(ret), nil +} diff --git a/pkg/pki/crl_util.go b/pkg/pki/crl_util.go new file mode 100644 index 0000000..9e046ff --- /dev/null +++ b/pkg/pki/crl_util.go @@ -0,0 +1,245 @@ +package pki + +import ( + "context" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "errors" + "fmt" + "strings" + "time" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" +) + +type revocationInfo struct { + CertificateBytes []byte `json:"certificate_bytes"` + RevocationTime int64 `json:"revocation_time"` + RevocationTimeUTC time.Time `json:"revocation_time_utc"` +} + +// Revokes a cert, and tries to be smart about error recovery +func revokeCert(ctx context.Context, b *backend, req *logical.Request, serial string, fromLease bool) (*logical.Response, error) { + // As this backend is self-contained and this function does not hook into + // third parties to manage users or resources, if the mount is tainted, + // revocation doesn't matter anyways -- the CRL that would be written will + // be immediately blown away by the view being cleared. So we can simply + // fast path a successful exit. + if b.System().Tainted() { + return nil, nil + } + + signingBundle, caErr := fetchCAInfo(ctx, req) + switch caErr.(type) { + case errutil.UserError: + return logical.ErrorResponse(fmt.Sprintf("could not fetch the CA certificate: %s", caErr)), nil + case errutil.InternalError: + return nil, fmt.Errorf("error fetching CA certificate: %s", caErr) + } + if signingBundle == nil { + return nil, errors.New("CA info not found") + } + colonSerial := strings.Replace(strings.ToLower(serial), "-", ":", -1) + if colonSerial == certutil.GetHexFormatted(signingBundle.Certificate.SerialNumber.Bytes(), ":") { + return logical.ErrorResponse("adding CA to CRL is not allowed"), nil + } + + alreadyRevoked := false + var revInfo revocationInfo + + revEntry, err := fetchCertBySerial(ctx, req, "revoked/", serial) + if err != nil { + switch err.(type) { + case errutil.UserError: + return logical.ErrorResponse(err.Error()), nil + case errutil.InternalError: + return nil, err + } + } + if revEntry != nil { + // Set the revocation info to the existing values + alreadyRevoked = true + err = revEntry.DecodeJSON(&revInfo) + if err != nil { + return nil, fmt.Errorf("error decoding existing revocation info") + } + } + + if !alreadyRevoked { + certEntry, err := fetchCertBySerial(ctx, req, "certs/", serial) + if err != nil { + switch err.(type) { + case errutil.UserError: + return logical.ErrorResponse(err.Error()), nil + case errutil.InternalError: + return nil, err + } + } + if certEntry == nil { + if fromLease { + // We can't write to revoked/ or update the CRL anyway because we don't have the cert, + // and there's no reason to expect this will work on a subsequent + // retry. Just give up and let the lease get deleted. + b.Logger().Warn("expired certificate revoke failed because not found in storage, treating as success", "serial", serial) + return nil, nil + } + return logical.ErrorResponse(fmt.Sprintf("certificate with serial %s not found", serial)), nil + } + + cert, err := x509.ParseCertificate(certEntry.Value) + if err != nil { + return nil, errwrap.Wrapf("error parsing certificate: {{err}}", err) + } + if cert == nil { + return nil, fmt.Errorf("got a nil certificate") + } + + // Add a little wiggle room because leases are stored with a second + // granularity + if cert.NotAfter.Before(time.Now().Add(2 * time.Second)) { + return nil, nil + } + + // Compatibility: Don't revoke CAs if they had leases. New CAs going + // forward aren't issued leases. + if cert.IsCA && fromLease { + return nil, nil + } + + currTime := time.Now() + revInfo.CertificateBytes = certEntry.Value + revInfo.RevocationTime = currTime.Unix() + revInfo.RevocationTimeUTC = currTime.UTC() + + revEntry, err = logical.StorageEntryJSON("revoked/"+normalizeSerial(serial), revInfo) + if err != nil { + return nil, fmt.Errorf("error creating revocation entry") + } + + err = req.Storage.Put(ctx, revEntry) + if err != nil { + return nil, fmt.Errorf("error saving revoked certificate to new location") + } + + } + + crlErr := buildCRL(ctx, b, req, false) + switch crlErr.(type) { + case errutil.UserError: + return logical.ErrorResponse(fmt.Sprintf("Error during CRL building: %s", crlErr)), nil + case errutil.InternalError: + return nil, errwrap.Wrapf("error encountered during CRL building: {{err}}", crlErr) + } + + resp := &logical.Response{ + Data: map[string]interface{}{ + "revocation_time": revInfo.RevocationTime, + }, + } + if !revInfo.RevocationTimeUTC.IsZero() { + resp.Data["revocation_time_rfc3339"] = revInfo.RevocationTimeUTC.Format(time.RFC3339Nano) + } + return resp, nil +} + +// Builds a CRL by going through the list of revoked certificates and building +// a new CRL with the stored revocation times and serial numbers. +func buildCRL(ctx context.Context, b *backend, req *logical.Request, forceNew bool) error { + crlInfo, err := b.CRL(ctx, req.Storage) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error fetching CRL config information: %s", err)} + } + + crlLifetime := b.crlLifetime + var revokedCerts []pkix.RevokedCertificate + var revInfo revocationInfo + var revokedSerials []string + + if crlInfo != nil { + if crlInfo.Expiry != "" { + crlDur, err := time.ParseDuration(crlInfo.Expiry) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error parsing CRL duration of %s", crlInfo.Expiry)} + } + crlLifetime = crlDur + } + + if crlInfo.Disable { + if !forceNew { + return nil + } + goto WRITE + } + } + + revokedSerials, err = req.Storage.List(ctx, "revoked/") + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error fetching list of revoked certs: %s", err)} + } + + for _, serial := range revokedSerials { + revokedEntry, err := req.Storage.Get(ctx, "revoked/"+serial) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("unable to fetch revoked cert with serial %s: %s", serial, err)} + } + if revokedEntry == nil { + return errutil.InternalError{Err: fmt.Sprintf("revoked certificate entry for serial %s is nil", serial)} + } + if revokedEntry.Value == nil || len(revokedEntry.Value) == 0 { + // TODO: In this case, remove it and continue? How likely is this to + // happen? Alternately, could skip it entirely, or could implement a + // delete function so that there is a way to remove these + return errutil.InternalError{Err: fmt.Sprintf("found revoked serial but actual certificate is empty")} + } + + err = revokedEntry.DecodeJSON(&revInfo) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error decoding revocation entry for serial %s: %s", serial, err)} + } + + revokedCert, err := x509.ParseCertificate(revInfo.CertificateBytes) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("unable to parse stored revoked certificate with serial %s: %s", serial, err)} + } + + // NOTE: We have to change this to UTC time because the CRL standard + // mandates it but Go will happily encode the CRL without this. + newRevCert := pkix.RevokedCertificate{ + SerialNumber: revokedCert.SerialNumber, + } + if !revInfo.RevocationTimeUTC.IsZero() { + newRevCert.RevocationTime = revInfo.RevocationTimeUTC + } else { + newRevCert.RevocationTime = time.Unix(revInfo.RevocationTime, 0).UTC() + } + revokedCerts = append(revokedCerts, newRevCert) + } + +WRITE: + signingBundle, caErr := fetchCAInfo(ctx, req) + switch caErr.(type) { + case errutil.UserError: + return errutil.UserError{Err: fmt.Sprintf("could not fetch the CA certificate: %s", caErr)} + case errutil.InternalError: + return errutil.InternalError{Err: fmt.Sprintf("error fetching CA certificate: %s", caErr)} + } + + crlBytes, err := signingBundle.Certificate.CreateCRL(rand.Reader, signingBundle.PrivateKey, revokedCerts, time.Now(), time.Now().Add(crlLifetime)) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error creating new CRL: %s", err)} + } + + err = req.Storage.Put(ctx, &logical.StorageEntry{ + Key: "crl", + Value: crlBytes, + }) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error storing CRL: %s", err)} + } + + return nil +} diff --git a/pkg/pki/fields.go b/pkg/pki/fields.go new file mode 100644 index 0000000..c67e39e --- /dev/null +++ b/pkg/pki/fields.go @@ -0,0 +1,288 @@ +package pki + +import "github.com/hashicorp/vault/sdk/framework" + +// addIssueAndSignCommonFields adds fields common to both CA and non-CA issuing +// and signing +func addIssueAndSignCommonFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema { + fields["exclude_cn_from_sans"] = &framework.FieldSchema{ + Type: framework.TypeBool, + Default: false, + Description: `If true, the Common Name will not be +included in DNS or Email Subject Alternate Names. +Defaults to false (CN is included).`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Exclude Common Name from Subject Alternative Names (SANs)", + }, + } + + fields["format"] = &framework.FieldSchema{ + Type: framework.TypeString, + Default: "pem", + Description: `Format for returned data. Can be "pem", "der", +or "pem_bundle". If "pem_bundle" any private +key and issuing cert will be appended to the +certificate pem. Defaults to "pem".`, + AllowedValues: []interface{}{"pem", "der", "pem_bundle"}, + DisplayAttrs: &framework.DisplayAttributes{ + Value: "pem", + }, + } + + fields["private_key_format"] = &framework.FieldSchema{ + Type: framework.TypeString, + Default: "der", + Description: `Format for the returned private key. +Generally the default will be controlled by the "format" +parameter as either base64-encoded DER or PEM-encoded DER. +However, this can be set to "pkcs8" to have the returned +private key contain base64-encoded pkcs8 or PEM-encoded +pkcs8 instead. Defaults to "der".`, + AllowedValues: []interface{}{"", "der", "pem", "pkcs8"}, + DisplayAttrs: &framework.DisplayAttributes{ + Value: "der", + }, + } + + fields["ip_sans"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `The requested IP SANs, if any, in a +comma-delimited list`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "IP Subject Alternative Names (SANs)", + }, + } + + fields["uri_sans"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `The requested URI SANs, if any, in a +comma-delimited list.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "URI Subject Alternative Names (SANs)", + }, + } + + fields["other_sans"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `Requested other SANs, in an array with the format +;UTF8: for each entry.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Other SANs", + }, + } + + return fields +} + +// addNonCACommonFields adds fields with help text specific to non-CA +// certificate issuing and signing +func addNonCACommonFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema { + fields = addIssueAndSignCommonFields(fields) + + fields["role"] = &framework.FieldSchema{ + Type: framework.TypeString, + Description: `The desired role with configuration for this +request`, + } + + fields["common_name"] = &framework.FieldSchema{ + Type: framework.TypeString, + Description: `The requested common name; if you want more than +one, specify the alternative names in the +alt_names map. If email protection is enabled +in the role, this may be an email address.`, + } + + fields["alt_names"] = &framework.FieldSchema{ + Type: framework.TypeString, + Description: `The requested Subject Alternative Names, if any, +in a comma-delimited list. If email protection +is enabled for the role, this may contain +email addresses.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "DNS/Email Subject Alternative Names (SANs)", + }, + } + + fields["serial_number"] = &framework.FieldSchema{ + Type: framework.TypeString, + Description: `The requested serial number, if any. If you want +more than one, specify alternative names in +the alt_names map using OID 2.5.4.5.`, + } + + fields["ttl"] = &framework.FieldSchema{ + Type: framework.TypeDurationSecond, + Description: `The requested Time To Live for the certificate; +sets the expiration date. If not specified +the role default, backend default, or system +default TTL is used, in that order. Cannot +be larger than the role max TTL.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "TTL", + }, + } + + return fields +} + +// addCACommonFields adds fields with help text specific to CA +// certificate issuing and signing +func addCACommonFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema { + fields = addIssueAndSignCommonFields(fields) + + fields["alt_names"] = &framework.FieldSchema{ + Type: framework.TypeString, + Description: `The requested Subject Alternative Names, if any, +in a comma-delimited list. May contain both +DNS names and email addresses.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "DNS/Email Subject Alternative Names (SANs)", + }, + } + + fields["common_name"] = &framework.FieldSchema{ + Type: framework.TypeString, + Description: `The requested common name; if you want more than +one, specify the alternative names in the alt_names +map. If not specified when signing, the common +name will be taken from the CSR; other names +must still be specified in alt_names or ip_sans.`, + } + + fields["ttl"] = &framework.FieldSchema{ + Type: framework.TypeDurationSecond, + Description: `The requested Time To Live for the certificate; +sets the expiration date. If not specified +the role default, backend default, or system +default TTL is used, in that order. Cannot +be larger than the mount max TTL. Note: +this only has an effect when generating +a CA cert or signing a CA cert, not when +generating a CSR for an intermediate CA.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "TTL", + }, + } + + fields["ou"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, OU (OrganizationalUnit) will be set to +this value.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "OU (Organizational Unit)", + }, + } + + fields["organization"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, O (Organization) will be set to +this value.`, + } + + fields["country"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Country will be set to +this value.`, + } + + fields["locality"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Locality will be set to +this value.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Locality/City", + }, + } + + fields["province"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Province will be set to +this value.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Province/State", + }, + } + + fields["street_address"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Street Address will be set to +this value.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Street Address", + }, + } + + fields["postal_code"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Postal Code will be set to +this value.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Postal Code", + }, + } + + fields["serial_number"] = &framework.FieldSchema{ + Type: framework.TypeString, + Description: `The requested serial number, if any. If you want +more than one, specify alternative names in +the alt_names map using OID 2.5.4.5.`, + } + + return fields +} + +// addCAKeyGenerationFields adds fields with help text specific to CA key +// generation and exporting +func addCAKeyGenerationFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema { + fields["exported"] = &framework.FieldSchema{ + Type: framework.TypeString, + Description: `Must be "internal" or "exported". If set to +"exported", the generated private key will be +returned. This is your *only* chance to retrieve +the private key!`, + } + + fields["key_bits"] = &framework.FieldSchema{ + Type: framework.TypeInt, + Default: 2048, + Description: `The number of bits to use. You will almost +certainly want to change this if you adjust +the key_type.`, + DisplayAttrs: &framework.DisplayAttributes{ + Value: 2048, + }, + } + + fields["key_type"] = &framework.FieldSchema{ + Type: framework.TypeString, + Default: "rsa", + Description: `The type of key to use; defaults to RSA. "rsa" +and "ec" are the only valid values.`, + AllowedValues: []interface{}{"rsa", "ec"}, + DisplayAttrs: &framework.DisplayAttributes{ + Value: "rsa", + }, + } + return fields +} + +// addCAIssueFields adds fields common to CA issuing, e.g. when returning +// an actual certificate +func addCAIssueFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema { + fields["max_path_length"] = &framework.FieldSchema{ + Type: framework.TypeInt, + Default: -1, + Description: "The maximum allowable path length", + } + + fields["permitted_dns_domains"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `Domains for which this certificate is allowed to sign or issue child certificates. If set, all DNS names (subject and alt) on child certs must be exact matches or subsets of the given domains (see https://tools.ietf.org/html/rfc5280#section-4.2.1.10).`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Permitted DNS Domains", + }, + } + + return fields +} diff --git a/pkg/pki/path_config_ca.go b/pkg/pki/path_config_ca.go new file mode 100644 index 0000000..393f2d0 --- /dev/null +++ b/pkg/pki/path_config_ca.go @@ -0,0 +1,138 @@ +package pki + +import ( + "context" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" +) + +func pathConfigCA(b *backend) *framework.Path { + return &framework.Path{ + Pattern: "config/ca", + Fields: map[string]*framework.FieldSchema{ + "pem_bundle": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `PEM-format, concatenated unencrypted +secret key and certificate.`, + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathCAWrite, + }, + + HelpSynopsis: pathConfigCAHelpSyn, + HelpDescription: pathConfigCAHelpDesc, + } +} + +func (b *backend) pathCAWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + pemBundle := data.Get("pem_bundle").(string) + + if pemBundle == "" { + return logical.ErrorResponse("'pem_bundle' was empty"), nil + } + + parsedBundle, err := certutil.ParsePEMBundle(pemBundle) + if err != nil { + switch err.(type) { + case errutil.InternalError: + return nil, err + default: + return logical.ErrorResponse(err.Error()), nil + } + } + + if parsedBundle.PrivateKey == nil { + return logical.ErrorResponse("private key not found in the PEM bundle"), nil + } + + if parsedBundle.PrivateKeyType == certutil.UnknownPrivateKey { + return logical.ErrorResponse("unknown private key found in the PEM bundle"), nil + } + + if parsedBundle.Certificate == nil { + return logical.ErrorResponse("no certificate found in the PEM bundle"), nil + } + + if !parsedBundle.Certificate.IsCA { + return logical.ErrorResponse("the given certificate is not marked for CA use and cannot be used with this backend"), nil + } + + cb, err := parsedBundle.ToCertBundle() + if err != nil { + return nil, errwrap.Wrapf("error converting raw values into cert bundle: {{err}}", err) + } + + entry, err := logical.StorageEntryJSON("config/ca_bundle", cb) + if err != nil { + return nil, err + } + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, err + } + + // For ease of later use, also store just the certificate at a known + // location, plus a fresh CRL + entry.Key = "ca" + entry.Value = parsedBundle.CertificateBytes + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, err + } + + err = buildCRL(ctx, b, req, true) + + return nil, err +} + +const pathConfigCAHelpSyn = ` +Set the CA certificate and private key used for generated credentials. +` + +const pathConfigCAHelpDesc = ` +This sets the CA information used for credentials generated by this +by this mount. This must be a PEM-format, concatenated unencrypted +secret key and certificate. + +For security reasons, the secret key cannot be retrieved later. +` + +const pathConfigCAGenerateHelpSyn = ` +Generate a new CA certificate and private key used for signing. +` + +const pathConfigCAGenerateHelpDesc = ` +This path generates a CA certificate and private key to be used for +credentials generated by this mount. The path can either +end in "internal" or "exported"; this controls whether the +unencrypted private key is exported after generation. This will +be your only chance to export the private key; for security reasons +it cannot be read or exported later. + +If the "type" option is set to "self-signed", the generated +certificate will be a self-signed root CA. Otherwise, this mount +will act as an intermediate CA; a CSR will be returned, to be signed +by your chosen CA (which could be another mount of this backend). +Note that the CRL path will be set to this mount's CRL path; if you +need further customization it is recommended that you create a CSR +separately and get it signed. Either way, use the "config/ca/set" +endpoint to load the signed certificate into Vault. +` + +const pathConfigCASignHelpSyn = ` +Generate a signed CA certificate from a CSR. +` + +const pathConfigCASignHelpDesc = ` +This path generates a CA certificate to be used for credentials +generated by the certificate's destination mount. + +Use the "config/ca/set" endpoint to load the signed certificate +into Vault another Vault mount. +` diff --git a/pkg/pki/path_config_crl.go b/pkg/pki/path_config_crl.go new file mode 100644 index 0000000..988e1d8 --- /dev/null +++ b/pkg/pki/path_config_crl.go @@ -0,0 +1,133 @@ +package pki + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" +) + +// CRLConfig holds basic CRL configuration information +type crlConfig struct { + Expiry string `json:"expiry" mapstructure:"expiry"` + Disable bool `json:"disable"` +} + +func pathConfigCRL(b *backend) *framework.Path { + return &framework.Path{ + Pattern: "config/crl", + Fields: map[string]*framework.FieldSchema{ + "expiry": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `The amount of time the generated CRL should be +valid; defaults to 72 hours`, + Default: "72h", + }, + "disable": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `If set to true, disables generating the CRL entirely.`, + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathCRLRead, + logical.UpdateOperation: b.pathCRLWrite, + }, + + HelpSynopsis: pathConfigCRLHelpSyn, + HelpDescription: pathConfigCRLHelpDesc, + } +} + +func (b *backend) CRL(ctx context.Context, s logical.Storage) (*crlConfig, error) { + entry, err := s.Get(ctx, "config/crl") + if err != nil { + return nil, err + } + if entry == nil { + return nil, nil + } + + var result crlConfig + if err := entry.DecodeJSON(&result); err != nil { + return nil, err + } + + return &result, nil +} + +func (b *backend) pathCRLRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + config, err := b.CRL(ctx, req.Storage) + if err != nil { + return nil, err + } + if config == nil { + return nil, nil + } + + return &logical.Response{ + Data: map[string]interface{}{ + "expiry": config.Expiry, + "disable": config.Disable, + }, + }, nil +} + +func (b *backend) pathCRLWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + config, err := b.CRL(ctx, req.Storage) + if err != nil { + return nil, err + } + if config == nil { + config = &crlConfig{} + } + + if expiryRaw, ok := d.GetOk("expiry"); ok { + expiry := expiryRaw.(string) + _, err := time.ParseDuration(expiry) + if err != nil { + return logical.ErrorResponse(fmt.Sprintf("given expiry could not be decoded: %s", err)), nil + } + config.Expiry = expiry + } + + var oldDisable bool + if disableRaw, ok := d.GetOk("disable"); ok { + oldDisable = config.Disable + config.Disable = disableRaw.(bool) + } + + entry, err := logical.StorageEntryJSON("config/crl", config) + if err != nil { + return nil, err + } + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, err + } + + if oldDisable != config.Disable { + // It wasn't disabled but now it is, rotate + crlErr := buildCRL(ctx, b, req, true) + switch crlErr.(type) { + case errutil.UserError: + return logical.ErrorResponse(fmt.Sprintf("Error during CRL building: %s", crlErr)), nil + case errutil.InternalError: + return nil, errwrap.Wrapf("error encountered during CRL building: {{err}}", crlErr) + } + } + + return nil, nil +} + +const pathConfigCRLHelpSyn = ` +Configure the CRL expiration. +` + +const pathConfigCRLHelpDesc = ` +This endpoint allows configuration of the CRL lifetime. +` diff --git a/pkg/pki/path_config_urls.go b/pkg/pki/path_config_urls.go new file mode 100644 index 0000000..8bdbfee --- /dev/null +++ b/pkg/pki/path_config_urls.go @@ -0,0 +1,156 @@ +package pki + +import ( + "context" + "fmt" + "github.com/asaskevich/govalidator" + "github.com/fatih/structs" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/logical" +) + +func pathConfigURLs(b *backend) *framework.Path { + return &framework.Path{ + Pattern: "config/urls", + Fields: map[string]*framework.FieldSchema{ + "issuing_certificates": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `Comma-separated list of URLs to be used +for the issuing certificate attribute`, + }, + + "crl_distribution_points": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `Comma-separated list of URLs to be used +for the CRL distribution points attribute`, + }, + + "ocsp_servers": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `Comma-separated list of URLs to be used +for the OCSP servers attribute`, + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathWriteURL, + logical.ReadOperation: b.pathReadURL, + }, + + HelpSynopsis: pathConfigURLsHelpSyn, + HelpDescription: pathConfigURLsHelpDesc, + } +} + +func validateURLs(urls []string) string { + for _, curr := range urls { + if !govalidator.IsURL(curr) { + return curr + } + } + + return "" +} + +func getURLs(ctx context.Context, req *logical.Request) (*certutil.URLEntries, error) { + entry, err := req.Storage.Get(ctx, "urls") + if err != nil { + return nil, err + } + if entry == nil { + return nil, nil + } + + var entries certutil.URLEntries + if err := entry.DecodeJSON(&entries); err != nil { + return nil, err + } + + return &entries, nil +} + +func writeURLs(ctx context.Context, req *logical.Request, entries *certutil.URLEntries) error { + entry, err := logical.StorageEntryJSON("urls", entries) + if err != nil { + return err + } + if entry == nil { + return fmt.Errorf("unable to marshal entry into JSON") + } + + err = req.Storage.Put(ctx, entry) + if err != nil { + return err + } + + return nil +} + +func (b *backend) pathReadURL(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + entries, err := getURLs(ctx, req) + if err != nil { + return nil, err + } + if entries == nil { + return nil, nil + } + + resp := &logical.Response{ + Data: structs.New(entries).Map(), + } + + return resp, nil +} + +func (b *backend) pathWriteURL(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + entries, err := getURLs(ctx, req) + if err != nil { + return nil, err + } + if entries == nil { + entries = &certutil.URLEntries{ + IssuingCertificates: []string{}, + CRLDistributionPoints: []string{}, + OCSPServers: []string{}, + } + } + + if urlsInt, ok := data.GetOk("issuing_certificates"); ok { + entries.IssuingCertificates = urlsInt.([]string) + if badURL := validateURLs(entries.IssuingCertificates); badURL != "" { + return logical.ErrorResponse(fmt.Sprintf( + "invalid URL found in issuing certificates: %s", badURL)), nil + } + } + if urlsInt, ok := data.GetOk("crl_distribution_points"); ok { + entries.CRLDistributionPoints = urlsInt.([]string) + if badURL := validateURLs(entries.CRLDistributionPoints); badURL != "" { + return logical.ErrorResponse(fmt.Sprintf( + "invalid URL found in CRL distribution points: %s", badURL)), nil + } + } + if urlsInt, ok := data.GetOk("ocsp_servers"); ok { + entries.OCSPServers = urlsInt.([]string) + if badURL := validateURLs(entries.OCSPServers); badURL != "" { + return logical.ErrorResponse(fmt.Sprintf( + "invalid URL found in OCSP servers: %s", badURL)), nil + } + } + + return nil, writeURLs(ctx, req, entries) +} + +const pathConfigURLsHelpSyn = ` +Set the URLs for the issuing CA, CRL distribution points, and OCSP servers. +` + +const pathConfigURLsHelpDesc = ` +This path allows you to set the issuing CA, CRL distribution points, and +OCSP server URLs that will be encoded into issued certificates. If these +values are not set, no such information will be encoded in the issued +certificates. To delete URLs, simply re-set the appropriate value with an +empty string. + +Multiple URLs can be specified for each type; use commas to separate them. +` diff --git a/pkg/pki/path_fetch.go b/pkg/pki/path_fetch.go new file mode 100644 index 0000000..03b1b04 --- /dev/null +++ b/pkg/pki/path_fetch.go @@ -0,0 +1,276 @@ +package pki + +import ( + "context" + "encoding/pem" + "fmt" + "strings" + + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" +) + +// Returns the CA in raw format +func pathFetchCA(b *backend) *framework.Path { + return &framework.Path{ + Pattern: `ca(/pem)?`, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathFetchRead, + }, + + HelpSynopsis: pathFetchHelpSyn, + HelpDescription: pathFetchHelpDesc, + } +} + +// Returns the CA chain +func pathFetchCAChain(b *backend) *framework.Path { + return &framework.Path{ + Pattern: `(cert/)?ca_chain`, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathFetchRead, + }, + + HelpSynopsis: pathFetchHelpSyn, + HelpDescription: pathFetchHelpDesc, + } +} + +// Returns the CRL in raw format +func pathFetchCRL(b *backend) *framework.Path { + return &framework.Path{ + Pattern: `crl(/pem)?`, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathFetchRead, + }, + + HelpSynopsis: pathFetchHelpSyn, + HelpDescription: pathFetchHelpDesc, + } +} + +// Returns any valid (non-revoked) cert. Since "ca" fits the pattern, this path +// also handles returning the CA cert in a non-raw format. +func pathFetchValid(b *backend) *framework.Path { + return &framework.Path{ + Pattern: `cert/(?P[0-9A-Fa-f-:]+)`, + Fields: map[string]*framework.FieldSchema{ + "serial": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `Certificate serial number, in colon- or +hyphen-separated octal`, + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathFetchRead, + }, + + HelpSynopsis: pathFetchHelpSyn, + HelpDescription: pathFetchHelpDesc, + } +} + +// This returns the CRL in a non-raw format +func pathFetchCRLViaCertPath(b *backend) *framework.Path { + return &framework.Path{ + Pattern: `cert/crl`, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathFetchRead, + }, + + HelpSynopsis: pathFetchHelpSyn, + HelpDescription: pathFetchHelpDesc, + } +} + +// This returns the list of serial numbers for certs +func pathFetchListCerts(b *backend) *framework.Path { + return &framework.Path{ + Pattern: "certs/?$", + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ListOperation: b.pathFetchCertList, + }, + + HelpSynopsis: pathFetchHelpSyn, + HelpDescription: pathFetchHelpDesc, + } +} + +func (b *backend) pathFetchCertList(ctx context.Context, req *logical.Request, data *framework.FieldData) (response *logical.Response, retErr error) { + entries, err := req.Storage.List(ctx, "certs/") + if err != nil { + return nil, err + } + + return logical.ListResponse(entries), nil +} + +func (b *backend) pathFetchRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (response *logical.Response, retErr error) { + var serial, pemType, contentType string + var certEntry, revokedEntry *logical.StorageEntry + var funcErr error + var certificate []byte + var revocationTime int64 + response = &logical.Response{ + Data: map[string]interface{}{}, + } + + // Some of these need to return raw and some non-raw; + // this is basically handled by setting contentType or not. + // Errors don't cause an immediate exit, because the raw + // paths still need to return raw output. + + switch { + case req.Path == "ca" || req.Path == "ca/pem": + serial = "ca" + contentType = "application/pkix-cert" + if req.Path == "ca/pem" { + pemType = "CERTIFICATE" + } + case req.Path == "ca_chain" || req.Path == "cert/ca_chain": + serial = "ca_chain" + if req.Path == "ca_chain" { + contentType = "application/pkix-cert" + } + case req.Path == "crl" || req.Path == "crl/pem": + serial = "crl" + contentType = "application/pkix-crl" + if req.Path == "crl/pem" { + pemType = "X509 CRL" + } + case req.Path == "cert/crl": + serial = "crl" + pemType = "X509 CRL" + default: + serial = data.Get("serial").(string) + pemType = "CERTIFICATE" + } + if len(serial) == 0 { + response = logical.ErrorResponse("The serial number must be provided") + goto reply + } + + if serial == "ca_chain" { + caInfo, err := fetchCAInfo(ctx, req) + switch err.(type) { + case errutil.UserError: + response = logical.ErrorResponse(err.Error()) + goto reply + case errutil.InternalError: + retErr = err + goto reply + } + + caChain := caInfo.GetCAChain() + var certStr string + for _, ca := range caChain { + block := pem.Block{ + Type: "CERTIFICATE", + Bytes: ca.Bytes, + } + certStr = strings.Join([]string{certStr, strings.TrimSpace(string(pem.EncodeToMemory(&block)))}, "\n") + } + certificate = []byte(strings.TrimSpace(certStr)) + goto reply + } + + certEntry, funcErr = fetchCertBySerial(ctx, req, req.Path, serial) + if funcErr != nil { + switch funcErr.(type) { + case errutil.UserError: + response = logical.ErrorResponse(funcErr.Error()) + goto reply + case errutil.InternalError: + retErr = funcErr + goto reply + } + } + if certEntry == nil { + response = nil + goto reply + } + + certificate = certEntry.Value + + if len(pemType) != 0 { + block := pem.Block{ + Type: pemType, + Bytes: certEntry.Value, + } + // This is convoluted on purpose to ensure that we don't have trailing + // newlines via various paths + certificate = []byte(strings.TrimSpace(string(pem.EncodeToMemory(&block)))) + } + + revokedEntry, funcErr = fetchCertBySerial(ctx, req, "revoked/", serial) + if funcErr != nil { + switch funcErr.(type) { + case errutil.UserError: + response = logical.ErrorResponse(funcErr.Error()) + goto reply + case errutil.InternalError: + retErr = funcErr + goto reply + } + } + if revokedEntry != nil { + var revInfo revocationInfo + err := revokedEntry.DecodeJSON(&revInfo) + if err != nil { + return logical.ErrorResponse(fmt.Sprintf("Error decoding revocation entry for serial %s: %s", serial, err)), nil + } + revocationTime = revInfo.RevocationTime + } + +reply: + switch { + case len(contentType) != 0: + response = &logical.Response{ + Data: map[string]interface{}{ + logical.HTTPContentType: contentType, + logical.HTTPRawBody: certificate, + }} + if retErr != nil { + if b.Logger().IsWarn() { + b.Logger().Warn("possible error, but cannot return in raw response. Note that an empty CA probably means none was configured, and an empty CRL is possibly correct", "error", retErr) + } + } + retErr = nil + if len(certificate) > 0 { + response.Data[logical.HTTPStatusCode] = 200 + } else { + response.Data[logical.HTTPStatusCode] = 204 + } + case retErr != nil: + response = nil + return + case response == nil: + return + case response.IsError(): + return response, nil + default: + response.Data["certificate"] = string(certificate) + response.Data["revocation_time"] = revocationTime + } + + return +} + +const pathFetchHelpSyn = ` +Fetch a CA, CRL, CA Chain, or non-revoked certificate. +` + +const pathFetchHelpDesc = ` +This allows certificates to be fetched. If using the fetch/ prefix any non-revoked certificate can be fetched. + +Using "ca" or "crl" as the value fetches the appropriate information in DER encoding. Add "/pem" to either to get PEM encoding. + +Using "ca_chain" as the value fetches the certificate authority trust chain in PEM encoding. +` diff --git a/pkg/pki/path_intermediate.go b/pkg/pki/path_intermediate.go new file mode 100644 index 0000000..2c80145 --- /dev/null +++ b/pkg/pki/path_intermediate.go @@ -0,0 +1,253 @@ +package pki + +import ( + "context" + "encoding/base64" + "fmt" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" +) + +func pathGenerateIntermediate(b *backend) *framework.Path { + ret := &framework.Path{ + Pattern: "intermediate/generate/" + framework.GenericNameRegex("exported"), + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathGenerateIntermediate, + }, + + HelpSynopsis: pathGenerateIntermediateHelpSyn, + HelpDescription: pathGenerateIntermediateHelpDesc, + } + + ret.Fields = addCACommonFields(map[string]*framework.FieldSchema{}) + ret.Fields = addCAKeyGenerationFields(ret.Fields) + ret.Fields["add_basic_constraints"] = &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `Whether to add a Basic Constraints +extension with CA: true. Only needed as a +workaround in some compatibility scenarios +with Active Directory Certificate Services.`, + } + + return ret +} + +func pathSetSignedIntermediate(b *backend) *framework.Path { + ret := &framework.Path{ + Pattern: "intermediate/set-signed", + + Fields: map[string]*framework.FieldSchema{ + "certificate": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `PEM-format certificate. This must be a CA +certificate with a public key matching the +previously-generated key from the generation +endpoint.`, + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathSetSignedIntermediate, + }, + + HelpSynopsis: pathSetSignedIntermediateHelpSyn, + HelpDescription: pathSetSignedIntermediateHelpDesc, + } + + return ret +} + +func (b *backend) pathGenerateIntermediate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + var err error + + exported, format, role, errorResp := b.getGenerationParams(data) + if errorResp != nil { + return errorResp, nil + } + + var resp *logical.Response + input := &inputBundle{ + role: role, + req: req, + apiData: data, + } + parsedBundle, err := generateIntermediateCSR(b, input) + if err != nil { + switch err.(type) { + case errutil.UserError: + return logical.ErrorResponse(err.Error()), nil + default: + return nil, err + } + } + + csrb, err := parsedBundle.ToCSRBundle() + if err != nil { + return nil, errwrap.Wrapf("error converting raw CSR bundle to CSR bundle: {{err}}", err) + } + + resp = &logical.Response{ + Data: map[string]interface{}{}, + } + + switch format { + case "pem": + resp.Data["csr"] = csrb.CSR + if exported { + resp.Data["private_key"] = csrb.PrivateKey + resp.Data["private_key_type"] = csrb.PrivateKeyType + } + + case "pem_bundle": + resp.Data["csr"] = csrb.CSR + if exported { + resp.Data["csr"] = fmt.Sprintf("%s\n%s", csrb.PrivateKey, csrb.CSR) + resp.Data["private_key"] = csrb.PrivateKey + resp.Data["private_key_type"] = csrb.PrivateKeyType + } + + case "der": + resp.Data["csr"] = base64.StdEncoding.EncodeToString(parsedBundle.CSRBytes) + if exported { + resp.Data["private_key"] = base64.StdEncoding.EncodeToString(parsedBundle.PrivateKeyBytes) + resp.Data["private_key_type"] = csrb.PrivateKeyType + } + } + + if data.Get("private_key_format").(string) == "pkcs8" { + err = convertRespToPKCS8(resp) + if err != nil { + return nil, err + } + } + + cb := &certutil.CertBundle{} + cb.PrivateKey = csrb.PrivateKey + cb.PrivateKeyType = csrb.PrivateKeyType + + entry, err := logical.StorageEntryJSON("config/ca_bundle", cb) + if err != nil { + return nil, err + } + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, err + } + + return resp, nil +} + +func (b *backend) pathSetSignedIntermediate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + cert := data.Get("certificate").(string) + + if cert == "" { + return logical.ErrorResponse("no certificate provided in the \"certificate\" parameter"), nil + } + + inputBundle, err := certutil.ParsePEMBundle(cert) + if err != nil { + switch err.(type) { + case errutil.InternalError: + return nil, err + default: + return logical.ErrorResponse(err.Error()), nil + } + } + + if inputBundle.Certificate == nil { + return logical.ErrorResponse("supplied certificate could not be successfully parsed"), nil + } + + cb := &certutil.CertBundle{} + entry, err := req.Storage.Get(ctx, "config/ca_bundle") + if err != nil { + return nil, err + } + if entry == nil { + return logical.ErrorResponse("could not find any existing entry with a private key"), nil + } + + err = entry.DecodeJSON(cb) + if err != nil { + return nil, err + } + + if len(cb.PrivateKey) == 0 || cb.PrivateKeyType == "" { + return logical.ErrorResponse("could not find an existing private key"), nil + } + + parsedCB, err := cb.ToParsedCertBundle() + if err != nil { + return nil, err + } + if parsedCB.PrivateKey == nil { + return nil, fmt.Errorf("saved key could not be parsed successfully") + } + + inputBundle.PrivateKey = parsedCB.PrivateKey + inputBundle.PrivateKeyType = parsedCB.PrivateKeyType + inputBundle.PrivateKeyBytes = parsedCB.PrivateKeyBytes + + if !inputBundle.Certificate.IsCA { + return logical.ErrorResponse("the given certificate is not marked for CA use and cannot be used with this backend"), nil + } + + if err := inputBundle.Verify(); err != nil { + return nil, errwrap.Wrapf("verification of parsed bundle failed: {{err}}", err) + } + + cb, err = inputBundle.ToCertBundle() + if err != nil { + return nil, errwrap.Wrapf("error converting raw values into cert bundle: {{err}}", err) + } + + entry, err = logical.StorageEntryJSON("config/ca_bundle", cb) + if err != nil { + return nil, err + } + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, err + } + + entry.Key = "certs/" + normalizeSerial(cb.SerialNumber) + entry.Value = inputBundle.CertificateBytes + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, err + } + + // For ease of later use, also store just the certificate at a known + // location + entry.Key = "ca" + entry.Value = inputBundle.CertificateBytes + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, err + } + + // Build a fresh CRL + err = buildCRL(ctx, b, req, true) + + return nil, err +} + +const pathGenerateIntermediateHelpSyn = ` +Generate a new CSR and private key used for signing. +` + +const pathGenerateIntermediateHelpDesc = ` +See the API documentation for more information. +` + +const pathSetSignedIntermediateHelpSyn = ` +Provide the signed intermediate CA cert. +` + +const pathSetSignedIntermediateHelpDesc = ` +See the API documentation for more information. +` diff --git a/pkg/pki/path_issue_sign.go b/pkg/pki/path_issue_sign.go new file mode 100644 index 0000000..34959c0 --- /dev/null +++ b/pkg/pki/path_issue_sign.go @@ -0,0 +1,368 @@ +package pki + +import ( + "context" + "encoding/base64" + "fmt" + "time" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/helper/consts" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" +) + +func pathIssue(b *backend) *framework.Path { + ret := &framework.Path{ + Pattern: "issue/" + framework.GenericNameRegex("role"), + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathIssue, + }, + + HelpSynopsis: pathIssueHelpSyn, + HelpDescription: pathIssueHelpDesc, + } + + ret.Fields = addNonCACommonFields(map[string]*framework.FieldSchema{}) + return ret +} + +func pathSign(b *backend) *framework.Path { + ret := &framework.Path{ + Pattern: "sign/" + framework.GenericNameRegex("role"), + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathSign, + }, + + HelpSynopsis: pathSignHelpSyn, + HelpDescription: pathSignHelpDesc, + } + + ret.Fields = addNonCACommonFields(map[string]*framework.FieldSchema{}) + + ret.Fields["csr"] = &framework.FieldSchema{ + Type: framework.TypeString, + Default: "", + Description: `PEM-format CSR to be signed.`, + } + + return ret +} + +func pathSignVerbatim(b *backend) *framework.Path { + ret := &framework.Path{ + Pattern: "sign-verbatim" + framework.OptionalParamRegex("role"), + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathSignVerbatim, + }, + + HelpSynopsis: pathSignHelpSyn, + HelpDescription: pathSignHelpDesc, + } + + ret.Fields = addNonCACommonFields(map[string]*framework.FieldSchema{}) + + ret.Fields["csr"] = &framework.FieldSchema{ + Type: framework.TypeString, + Default: "", + Description: `PEM-format CSR to be signed. Values will be +taken verbatim from the CSR, except for +basic constraints.`, + } + + ret.Fields["key_usage"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Default: []string{"DigitalSignature", "KeyAgreement", "KeyEncipherment"}, + Description: `A comma-separated string or list of key usages (not extended +key usages). Valid values can be found at +https://golang.org/pkg/crypto/x509/#KeyUsage +-- simply drop the "KeyUsage" part of the name. +To remove all key usages from being set, set +this value to an empty list.`, + } + + ret.Fields["ext_key_usage"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Default: []string{}, + Description: `A comma-separated string or list of extended key usages. Valid values can be found at +https://golang.org/pkg/crypto/x509/#ExtKeyUsage +-- simply drop the "ExtKeyUsage" part of the name. +To remove all key usages from being set, set +this value to an empty list.`, + } + + ret.Fields["ext_key_usage_oids"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `A comma-separated string or list of extended key usage oids.`, + } + + return ret +} + +// pathIssue issues a certificate and private key from given parameters, +// subject to role restrictions +func (b *backend) pathIssue(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + roleName := data.Get("role").(string) + + // Get the role + role, err := b.getRole(ctx, req.Storage, roleName) + if err != nil { + return nil, err + } + if role == nil { + return logical.ErrorResponse(fmt.Sprintf("unknown role: %s", roleName)), nil + } + + if role.KeyType == "any" { + return logical.ErrorResponse("role key type \"any\" not allowed for issuing certificates, only signing"), nil + } + + return b.pathIssueSignCert(ctx, req, data, role, false, false) +} + +// pathSign issues a certificate from a submitted CSR, subject to role +// restrictions +func (b *backend) pathSign(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + roleName := data.Get("role").(string) + + // Get the role + role, err := b.getRole(ctx, req.Storage, roleName) + if err != nil { + return nil, err + } + if role == nil { + return logical.ErrorResponse(fmt.Sprintf("unknown role: %s", roleName)), nil + } + + return b.pathIssueSignCert(ctx, req, data, role, true, false) +} + +// pathSignVerbatim issues a certificate from a submitted CSR, *not* subject to +// role restrictions +func (b *backend) pathSignVerbatim(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + + roleName := data.Get("role").(string) + + // Get the role if one was specified + role, err := b.getRole(ctx, req.Storage, roleName) + if err != nil { + return nil, err + } + + entry := &roleEntry{ + AllowLocalhost: true, + AllowAnyName: true, + AllowIPSANs: true, + EnforceHostnames: false, + KeyType: "any", + UseCSRCommonName: true, + UseCSRSANs: true, + AllowedURISANs: []string{"*"}, + AllowedSerialNumbers: []string{"*"}, + GenerateLease: new(bool), + KeyUsage: data.Get("key_usage").([]string), + ExtKeyUsage: data.Get("ext_key_usage").([]string), + ExtKeyUsageOIDs: data.Get("ext_key_usage_oids").([]string), + } + + *entry.GenerateLease = false + + if role != nil { + if role.TTL > 0 { + entry.TTL = role.TTL + } + if role.MaxTTL > 0 { + entry.MaxTTL = role.MaxTTL + } + if role.GenerateLease != nil { + *entry.GenerateLease = *role.GenerateLease + } + entry.NoStore = role.NoStore + } + + return b.pathIssueSignCert(ctx, req, data, entry, true, true) +} + +func (b *backend) pathIssueSignCert(ctx context.Context, req *logical.Request, data *framework.FieldData, role *roleEntry, useCSR, useCSRValues bool) (*logical.Response, error) { + // If storing the certificate and on a performance standby, forward this request on to the primary + if !role.NoStore && b.System().ReplicationState().HasState(consts.ReplicationPerformanceStandby) { + return nil, logical.ErrReadOnly + } + + format := getFormat(data) + if format == "" { + return logical.ErrorResponse( + `the "format" path parameter must be "pem", "der", or "pem_bundle"`), nil + } + + var caErr error + signingBundle, caErr := fetchCAInfo(ctx, req) + switch caErr.(type) { + case errutil.UserError: + return nil, errutil.UserError{Err: fmt.Sprintf( + "could not fetch the CA certificate (was one set?): %s", caErr)} + case errutil.InternalError: + return nil, errutil.InternalError{Err: fmt.Sprintf( + "error fetching CA certificate: %s", caErr)} + } + + input := &inputBundle{ + req: req, + apiData: data, + role: role, + } + var parsedBundle *certutil.ParsedCertBundle + var err error + if useCSR { + parsedBundle, err = signCert(b, input, signingBundle, false, useCSRValues) + } else { + parsedBundle, err = generateCert(ctx, b, input, signingBundle, false) + } + if err != nil { + switch err.(type) { + case errutil.UserError: + return logical.ErrorResponse(err.Error()), nil + case errutil.InternalError: + return nil, err + default: + return nil, errwrap.Wrapf("error signing/generating certificate: {{err}}", err) + } + } + + signingCB, err := signingBundle.ToCertBundle() + if err != nil { + return nil, errwrap.Wrapf("error converting raw signing bundle to cert bundle: {{err}}", err) + } + + cb, err := parsedBundle.ToCertBundle() + if err != nil { + return nil, errwrap.Wrapf("error converting raw cert bundle to cert bundle: {{err}}", err) + } + + respData := map[string]interface{}{ + "expiration": int64(parsedBundle.Certificate.NotAfter.Unix()), + "serial_number": cb.SerialNumber, + } + + switch format { + case "pem": + respData["issuing_ca"] = signingCB.Certificate + respData["certificate"] = cb.Certificate + if cb.CAChain != nil && len(cb.CAChain) > 0 { + respData["ca_chain"] = cb.CAChain + } + if !useCSR { + respData["private_key"] = cb.PrivateKey + respData["private_key_type"] = cb.PrivateKeyType + } + + case "pem_bundle": + respData["issuing_ca"] = signingCB.Certificate + respData["certificate"] = cb.ToPEMBundle() + if cb.CAChain != nil && len(cb.CAChain) > 0 { + respData["ca_chain"] = cb.CAChain + } + if !useCSR { + respData["private_key"] = cb.PrivateKey + respData["private_key_type"] = cb.PrivateKeyType + } + + case "der": + respData["certificate"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes) + respData["issuing_ca"] = base64.StdEncoding.EncodeToString(signingBundle.CertificateBytes) + + var caChain []string + for _, caCert := range parsedBundle.CAChain { + caChain = append(caChain, base64.StdEncoding.EncodeToString(caCert.Bytes)) + } + if caChain != nil && len(caChain) > 0 { + respData["ca_chain"] = caChain + } + + if !useCSR { + respData["private_key"] = base64.StdEncoding.EncodeToString(parsedBundle.PrivateKeyBytes) + respData["private_key_type"] = cb.PrivateKeyType + } + } + + var resp *logical.Response + switch { + case role.GenerateLease == nil: + return nil, fmt.Errorf("generate lease in role is nil") + case *role.GenerateLease == false: + // If lease generation is disabled do not populate `Secret` field in + // the response + resp = &logical.Response{ + Data: respData, + } + default: + + resp = b.Secret(SecretCertsType).Response( + respData, + map[string]interface{}{ + "serial_number": cb.SerialNumber, + }) + resp.Secret.TTL = parsedBundle.Certificate.NotAfter.Sub(time.Now()) + + } + + if data.Get("private_key_format").(string) == "pkcs8" { + err = convertRespToPKCS8(resp) + if err != nil { + return nil, err + } + } + + if !role.NoStore { + err = req.Storage.Put(ctx, &logical.StorageEntry{ + Key: "certs/" + normalizeSerial(cb.SerialNumber), + Value: parsedBundle.CertificateBytes, + }) + if err != nil { + return nil, errwrap.Wrapf("unable to store certificate locally: {{err}}", err) + } + } + + if useCSR { + if role.UseCSRCommonName && data.Get("common_name").(string) != "" { + resp.AddWarning("the common_name field was provided but the role is set with \"use_csr_common_name\" set to true") + } + if role.UseCSRSANs && data.Get("alt_names").(string) != "" { + resp.AddWarning("the alt_names field was provided but the role is set with \"use_csr_sans\" set to true") + } + } + + return resp, nil +} + +const pathIssueHelpSyn = ` +Request a certificate using a certain role with the provided details. +` + +const pathIssueHelpDesc = ` +This path allows requesting a certificate to be issued according to the +policy of the given role. The certificate will only be issued if the +requested details are allowed by the role policy. + +This path returns a certificate and a private key. If you want a workflow +that does not expose a private key, generate a CSR locally and use the +sign path instead. +` + +const pathSignHelpSyn = ` +Request certificates using a certain role with the provided details. +` + +const pathSignHelpDesc = ` +This path allows requesting certificates to be issued according to the +policy of the given role. The certificate will only be issued if the +requested common name is allowed by the role policy. + +This path requires a CSR; if you want Vault to generate a private key +for you, use the issue path instead. +` diff --git a/pkg/pki/path_revoke.go b/pkg/pki/path_revoke.go new file mode 100644 index 0000000..1906e24 --- /dev/null +++ b/pkg/pki/path_revoke.go @@ -0,0 +1,101 @@ +package pki + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/consts" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" +) + +func pathRevoke(b *backend) *framework.Path { + return &framework.Path{ + Pattern: `revoke`, + Fields: map[string]*framework.FieldSchema{ + "serial_number": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `Certificate serial number, in colon- or +hyphen-separated octal`, + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathRevokeWrite, + }, + + HelpSynopsis: pathRevokeHelpSyn, + HelpDescription: pathRevokeHelpDesc, + } +} + +func pathRotateCRL(b *backend) *framework.Path { + return &framework.Path{ + Pattern: `crl/rotate`, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathRotateCRLRead, + }, + + HelpSynopsis: pathRotateCRLHelpSyn, + HelpDescription: pathRotateCRLHelpDesc, + } +} + +func (b *backend) pathRevokeWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + serial := data.Get("serial_number").(string) + if len(serial) == 0 { + return logical.ErrorResponse("The serial number must be provided"), nil + } + + if b.System().ReplicationState().HasState(consts.ReplicationPerformanceStandby) { + return nil, logical.ErrReadOnly + } + + // We store and identify by lowercase colon-separated hex, but other + // utilities use dashes and/or uppercase, so normalize + serial = strings.Replace(strings.ToLower(serial), "-", ":", -1) + + b.revokeStorageLock.Lock() + defer b.revokeStorageLock.Unlock() + + return revokeCert(ctx, b, req, serial, false) +} + +func (b *backend) pathRotateCRLRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + b.revokeStorageLock.RLock() + defer b.revokeStorageLock.RUnlock() + + crlErr := buildCRL(ctx, b, req, false) + switch crlErr.(type) { + case errutil.UserError: + return logical.ErrorResponse(fmt.Sprintf("Error during CRL building: %s", crlErr)), nil + case errutil.InternalError: + return nil, errwrap.Wrapf("error encountered during CRL building: {{err}}", crlErr) + default: + return &logical.Response{ + Data: map[string]interface{}{ + "success": true, + }, + }, nil + } +} + +const pathRevokeHelpSyn = ` +Revoke a certificate by serial number. +` + +const pathRevokeHelpDesc = ` +This allows certificates to be revoked using its serial number. A root token is required. +` + +const pathRotateCRLHelpSyn = ` +Force a rebuild of the CRL. +` + +const pathRotateCRLHelpDesc = ` +Force a rebuild of the CRL. This can be used to remove expired certificates from it if no certificates have been revoked. A root token is required. +` diff --git a/pkg/pki/path_roles.go b/pkg/pki/path_roles.go new file mode 100644 index 0000000..3a33310 --- /dev/null +++ b/pkg/pki/path_roles.go @@ -0,0 +1,838 @@ +package pki + +import ( + "context" + "crypto/x509" + "fmt" + "github.com/hashicorp/errwrap" + "strings" + "time" + + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/helper/consts" + "github.com/hashicorp/vault/sdk/helper/parseutil" + "github.com/hashicorp/vault/sdk/logical" +) + +func pathListRoles(b *backend) *framework.Path { + return &framework.Path{ + Pattern: "roles/?$", + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ListOperation: b.pathRoleList, + }, + + HelpSynopsis: pathListRolesHelpSyn, + HelpDescription: pathListRolesHelpDesc, + } +} + +func pathRoles(b *backend) *framework.Path { + return &framework.Path{ + Pattern: "roles/" + framework.GenericNameRegex("name"), + Fields: map[string]*framework.FieldSchema{ + "backend": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Backend Type", + }, + + "name": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Name of the role", + }, + + "ttl": &framework.FieldSchema{ + Type: framework.TypeDurationSecond, + Description: `The lease duration if no specific lease duration is +requested. The lease duration controls the expiration +of certificates issued by this backend. Defaults to +the value of max_ttl.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "TTL", + }, + }, + + "max_ttl": &framework.FieldSchema{ + Type: framework.TypeDurationSecond, + Description: "The maximum allowed lease duration", + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Max TTL", + }, + }, + + "allow_localhost": &framework.FieldSchema{ + Type: framework.TypeBool, + Default: true, + Description: `Whether to allow "localhost" as a valid common +name in a request`, + DisplayAttrs: &framework.DisplayAttributes{ + Value: true, + }, + }, + + "allowed_domains": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, clients can request certificates for +subdomains directly beneath these domains, including +the wildcard subdomains. See the documentation for more +information. This parameter accepts a comma-separated +string or list of domains.`, + }, + "allowed_domains_template": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `If set, Allowed domains can be specified using identity template policies. + Non-templated domains are also permitted.`, + Default: false, + }, + "allow_bare_domains": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `If set, clients can request certificates +for the base domains themselves, e.g. "example.com". +This is a separate option as in some cases this can +be considered a security threat.`, + }, + + "allow_subdomains": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `If set, clients can request certificates for +subdomains of the CNs allowed by the other role options, +including wildcard subdomains. See the documentation for +more information.`, + }, + + "allow_glob_domains": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `If set, domains specified in "allowed_domains" +can include glob patterns, e.g. "ftp*.example.com". See +the documentation for more information.`, + }, + + "allow_any_name": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `If set, clients can request certificates for +any CN they like. See the documentation for more +information.`, + }, + + "enforce_hostnames": &framework.FieldSchema{ + Type: framework.TypeBool, + Default: true, + Description: `If set, only valid host names are allowed for +CN and SANs. Defaults to true.`, + DisplayAttrs: &framework.DisplayAttributes{ + Value: true, + }, + }, + + "allow_ip_sans": &framework.FieldSchema{ + Type: framework.TypeBool, + Default: true, + Description: `If set, IP Subject Alternative Names are allowed. +Any valid IP is accepted.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Allow IP Subject Alternative Names", + Value: true, + }, + }, + + "allowed_uri_sans": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, an array of allowed URIs to put in the URI Subject Alternative Names. +Any valid URI is accepted, these values support globbing.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Allowed URI Subject Alternative Names", + }, + }, + + "allowed_other_sans": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, an array of allowed other names to put in SANs. These values support globbing and must be in the format ;:. Currently only "utf8" is a valid type. All values, including globbing values, must use this syntax, with the exception being a single "*" which allows any OID and any value (but type must still be utf8).`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Allowed Other Subject Alternative Names", + }, + }, + + "allowed_serial_numbers": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, an array of allowed serial numbers to put in Subject. These values support globbing.`, + }, + + "server_flag": &framework.FieldSchema{ + Type: framework.TypeBool, + Default: true, + Description: `If set, certificates are flagged for server auth use. +Defaults to true.`, + DisplayAttrs: &framework.DisplayAttributes{ + Value: true, + }, + }, + + "client_flag": &framework.FieldSchema{ + Type: framework.TypeBool, + Default: true, + Description: `If set, certificates are flagged for client auth use. +Defaults to true.`, + DisplayAttrs: &framework.DisplayAttributes{ + Value: true, + }, + }, + + "code_signing_flag": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `If set, certificates are flagged for code signing +use. Defaults to false.`, + }, + + "email_protection_flag": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `If set, certificates are flagged for email +protection use. Defaults to false.`, + }, + + "key_type": &framework.FieldSchema{ + Type: framework.TypeString, + Default: "rsa", + Description: `The type of key to use; defaults to RSA. "rsa" +and "ec" are the only valid values.`, + AllowedValues: []interface{}{"rsa", "ec"}, + }, + + "key_bits": &framework.FieldSchema{ + Type: framework.TypeInt, + Default: 2048, + Description: `The number of bits to use. You will almost +certainly want to change this if you adjust +the key_type.`, + }, + + "key_usage": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Default: []string{"DigitalSignature", "KeyAgreement", "KeyEncipherment"}, + Description: `A comma-separated string or list of key usages (not extended +key usages). Valid values can be found at +https://golang.org/pkg/crypto/x509/#KeyUsage +-- simply drop the "KeyUsage" part of the name. +To remove all key usages from being set, set +this value to an empty list.`, + DisplayAttrs: &framework.DisplayAttributes{ + Value: "DigitalSignature,KeyAgreement,KeyEncipherment", + }, + }, + + "ext_key_usage": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Default: []string{}, + Description: `A comma-separated string or list of extended key usages. Valid values can be found at +https://golang.org/pkg/crypto/x509/#ExtKeyUsage +-- simply drop the "ExtKeyUsage" part of the name. +To remove all key usages from being set, set +this value to an empty list.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Extended Key Usage", + }, + }, + + "ext_key_usage_oids": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `A comma-separated string or list of extended key usage oids.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Extended Key Usage OIDs", + }, + }, + + "use_csr_common_name": &framework.FieldSchema{ + Type: framework.TypeBool, + Default: true, + Description: `If set, when used with a signing profile, +the common name in the CSR will be used. This +does *not* include any requested Subject Alternative +Names. Defaults to true.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Use CSR Common Name", + Value: true, + }, + }, + + "use_csr_sans": &framework.FieldSchema{ + Type: framework.TypeBool, + Default: true, + Description: `If set, when used with a signing profile, +the SANs in the CSR will be used. This does *not* +include the Common Name (cn). Defaults to true.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Use CSR Subject Alternative Names", + Value: true, + }, + }, + + "ou": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, OU (OrganizationalUnit) will be set to +this value in certificates issued by this role.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Organizational Unit", + }, + }, + + "organization": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, O (Organization) will be set to +this value in certificates issued by this role.`, + }, + + "country": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Country will be set to +this value in certificates issued by this role.`, + }, + + "locality": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Locality will be set to +this value in certificates issued by this role.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Locality/City", + }, + }, + + "province": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Province will be set to +this value in certificates issued by this role.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Province/State", + }, + }, + + "street_address": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Street Address will be set to +this value in certificates issued by this role.`, + }, + + "postal_code": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Postal Code will be set to +this value in certificates issued by this role.`, + }, + + "generate_lease": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: ` +If set, certificates issued/signed against this role will have Vault leases +attached to them. Defaults to "false". Certificates can be added to the CRL by +"vault revoke " when certificates are associated with leases. It can +also be done using the "pki/revoke" endpoint. However, when lease generation is +disabled, invoking "pki/revoke" would be the only way to add the certificates +to the CRL. When large number of certificates are generated with long +lifetimes, it is recommended that lease generation be disabled, as large amount of +leases adversely affect the startup time of Vault.`, + }, + + "no_store": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: ` +If set, certificates issued/signed against this role will not be stored in the +storage backend. This can improve performance when issuing large numbers of +certificates. However, certificates issued in this way cannot be enumerated +or revoked, so this option is recommended only for certificates that are +non-sensitive, or extremely short-lived. This option implies a value of "false" +for "generate_lease".`, + }, + + "require_cn": &framework.FieldSchema{ + Type: framework.TypeBool, + Default: true, + Description: `If set to false, makes the 'common_name' field optional while generating a certificate.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Require Common Name", + }, + }, + + "policy_identifiers": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `A comma-separated string or list of policy oids.`, + }, + + "basic_constraints_valid_for_non_ca": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `Mark Basic Constraints valid when issuing non-CA certificates.`, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Basic Constraints Valid for Non-CA", + }, + }, + "not_before_duration": &framework.FieldSchema{ + Type: framework.TypeDurationSecond, + Default: 30, + Description: `The duration before now the cert needs to be created / signed.`, + DisplayAttrs: &framework.DisplayAttributes{ + Value: 30, + }, + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathRoleRead, + logical.UpdateOperation: b.pathRoleCreate, + logical.DeleteOperation: b.pathRoleDelete, + }, + + HelpSynopsis: pathRoleHelpSyn, + HelpDescription: pathRoleHelpDesc, + } +} + +func (b *backend) getRole(ctx context.Context, s logical.Storage, n string) (*roleEntry, error) { + entry, err := s.Get(ctx, "role/"+n) + 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 + } + + // Migrate existing saved entries and save back if changed + modified := false + if len(result.DeprecatedTTL) == 0 && len(result.Lease) != 0 { + result.DeprecatedTTL = result.Lease + result.Lease = "" + modified = true + } + if result.TTL == 0 && len(result.DeprecatedTTL) != 0 { + parsed, err := parseutil.ParseDurationSecond(result.DeprecatedTTL) + if err != nil { + return nil, err + } + result.TTL = parsed + result.DeprecatedTTL = "" + modified = true + } + if len(result.DeprecatedMaxTTL) == 0 && len(result.LeaseMax) != 0 { + result.DeprecatedMaxTTL = result.LeaseMax + result.LeaseMax = "" + modified = true + } + if result.MaxTTL == 0 && len(result.DeprecatedMaxTTL) != 0 { + parsed, err := parseutil.ParseDurationSecond(result.DeprecatedMaxTTL) + if err != nil { + return nil, err + } + result.MaxTTL = parsed + result.DeprecatedMaxTTL = "" + modified = true + } + if result.AllowBaseDomain { + result.AllowBaseDomain = false + result.AllowBareDomains = true + modified = true + } + if result.AllowedDomainsOld != "" { + result.AllowedDomains = strings.Split(result.AllowedDomainsOld, ",") + result.AllowedDomainsOld = "" + modified = true + } + if result.AllowedBaseDomain != "" { + found := false + for _, v := range result.AllowedDomains { + if v == result.AllowedBaseDomain { + found = true + break + } + } + if !found { + result.AllowedDomains = append(result.AllowedDomains, result.AllowedBaseDomain) + } + result.AllowedBaseDomain = "" + modified = true + } + + // Upgrade generate_lease in role + if result.GenerateLease == nil { + // All the new roles will have GenerateLease always set to a value. A + // nil value indicates that this role needs an upgrade. Set it to + // `true` to not alter its current behavior. + result.GenerateLease = new(bool) + *result.GenerateLease = true + modified = true + } + + // Upgrade key usages + if result.KeyUsageOld != "" { + result.KeyUsage = strings.Split(result.KeyUsageOld, ",") + result.KeyUsageOld = "" + modified = true + } + + // Upgrade OU + if result.OUOld != "" { + result.OU = strings.Split(result.OUOld, ",") + result.OUOld = "" + modified = true + } + + // Upgrade Organization + if result.OrganizationOld != "" { + result.Organization = strings.Split(result.OrganizationOld, ",") + result.OrganizationOld = "" + modified = true + } + + if modified && (b.System().LocalMount() || !b.System().ReplicationState().HasState(consts.ReplicationPerformanceSecondary)) { + jsonEntry, err := logical.StorageEntryJSON("role/"+n, &result) + if err != nil { + return nil, err + } + if err := s.Put(ctx, jsonEntry); err != nil { + // Only perform upgrades on replication primary + if !strings.Contains(err.Error(), logical.ErrReadOnly.Error()) { + return nil, err + } + } + } + + return &result, nil +} + +func (b *backend) pathRoleDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + err := req.Storage.Delete(ctx, "role/"+data.Get("name").(string)) + if err != nil { + return nil, err + } + + return nil, nil +} + +func (b *backend) pathRoleRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + roleName := data.Get("name").(string) + if roleName == "" { + return logical.ErrorResponse("missing role name"), nil + } + + role, err := b.getRole(ctx, req.Storage, roleName) + if err != nil { + return nil, err + } + if role == nil { + return nil, nil + } + + resp := &logical.Response{ + Data: role.ToResponseData(), + } + return resp, nil +} + +func (b *backend) pathRoleList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + entries, err := req.Storage.List(ctx, "role/") + if err != nil { + return nil, err + } + + return logical.ListResponse(entries), nil +} + +func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + var err error + name := data.Get("name").(string) + + entry := &roleEntry{ + MaxTTL: time.Duration(data.Get("max_ttl").(int)) * time.Second, + TTL: time.Duration(data.Get("ttl").(int)) * time.Second, + AllowLocalhost: data.Get("allow_localhost").(bool), + AllowedDomains: data.Get("allowed_domains").([]string), + AllowedDomainsTemplate: data.Get("allowed_domains_template").(bool), + AllowBareDomains: data.Get("allow_bare_domains").(bool), + AllowSubdomains: data.Get("allow_subdomains").(bool), + AllowGlobDomains: data.Get("allow_glob_domains").(bool), + AllowAnyName: data.Get("allow_any_name").(bool), + EnforceHostnames: data.Get("enforce_hostnames").(bool), + AllowIPSANs: data.Get("allow_ip_sans").(bool), + AllowedURISANs: data.Get("allowed_uri_sans").([]string), + ServerFlag: data.Get("server_flag").(bool), + ClientFlag: data.Get("client_flag").(bool), + CodeSigningFlag: data.Get("code_signing_flag").(bool), + EmailProtectionFlag: data.Get("email_protection_flag").(bool), + KeyType: data.Get("key_type").(string), + KeyBits: data.Get("key_bits").(int), + UseCSRCommonName: data.Get("use_csr_common_name").(bool), + UseCSRSANs: data.Get("use_csr_sans").(bool), + KeyUsage: data.Get("key_usage").([]string), + ExtKeyUsage: data.Get("ext_key_usage").([]string), + ExtKeyUsageOIDs: data.Get("ext_key_usage_oids").([]string), + OU: data.Get("ou").([]string), + Organization: data.Get("organization").([]string), + Country: data.Get("country").([]string), + Locality: data.Get("locality").([]string), + Province: data.Get("province").([]string), + StreetAddress: data.Get("street_address").([]string), + PostalCode: data.Get("postal_code").([]string), + GenerateLease: new(bool), + NoStore: data.Get("no_store").(bool), + RequireCN: data.Get("require_cn").(bool), + AllowedSerialNumbers: data.Get("allowed_serial_numbers").([]string), + PolicyIdentifiers: data.Get("policy_identifiers").([]string), + BasicConstraintsValidForNonCA: data.Get("basic_constraints_valid_for_non_ca").(bool), + NotBeforeDuration: time.Duration(data.Get("not_before_duration").(int)) * time.Second, + } + + allowedOtherSANs := data.Get("allowed_other_sans").([]string) + switch { + case len(allowedOtherSANs) == 0: + case len(allowedOtherSANs) == 1 && allowedOtherSANs[0] == "*": + default: + _, err := parseOtherSANs(allowedOtherSANs) + if err != nil { + return logical.ErrorResponse(errwrap.Wrapf("error parsing allowed_other_sans: {{err}}", err).Error()), nil + } + } + entry.AllowedOtherSANs = allowedOtherSANs + + // no_store implies generate_lease := false + if entry.NoStore { + *entry.GenerateLease = false + } else { + *entry.GenerateLease = data.Get("generate_lease").(bool) + } + + if entry.KeyType == "rsa" && entry.KeyBits < 2048 { + return logical.ErrorResponse("RSA keys < 2048 bits are unsafe and not supported"), nil + } + + if entry.MaxTTL > 0 && entry.TTL > entry.MaxTTL { + return logical.ErrorResponse( + `"ttl" value must be less than "max_ttl" value`, + ), nil + } + + if err := certutil.ValidateKeyTypeLength(entry.KeyType, entry.KeyBits); err != nil { + return logical.ErrorResponse(err.Error()), nil + } + + if len(entry.ExtKeyUsageOIDs) > 0 { + for _, oidstr := range entry.ExtKeyUsageOIDs { + _, err := certutil.StringToOid(oidstr) + if err != nil { + return logical.ErrorResponse(fmt.Sprintf("%q could not be parsed as a valid oid for an extended key usage", oidstr)), nil + } + } + } + + if len(entry.PolicyIdentifiers) > 0 { + for _, oidstr := range entry.PolicyIdentifiers { + _, err := certutil.StringToOid(oidstr) + if err != nil { + return logical.ErrorResponse(fmt.Sprintf("%q could not be parsed as a valid oid for a policy identifier", oidstr)), nil + } + } + } + + // Store it + jsonEntry, err := logical.StorageEntryJSON("role/"+name, entry) + if err != nil { + return nil, err + } + if err := req.Storage.Put(ctx, jsonEntry); err != nil { + return nil, err + } + + return nil, nil +} + +func parseKeyUsages(input []string) int { + var parsedKeyUsages x509.KeyUsage + for _, k := range input { + switch strings.ToLower(strings.TrimSpace(k)) { + case "digitalsignature": + parsedKeyUsages |= x509.KeyUsageDigitalSignature + case "contentcommitment": + parsedKeyUsages |= x509.KeyUsageContentCommitment + case "keyencipherment": + parsedKeyUsages |= x509.KeyUsageKeyEncipherment + case "dataencipherment": + parsedKeyUsages |= x509.KeyUsageDataEncipherment + case "keyagreement": + parsedKeyUsages |= x509.KeyUsageKeyAgreement + case "certsign": + parsedKeyUsages |= x509.KeyUsageCertSign + case "crlsign": + parsedKeyUsages |= x509.KeyUsageCRLSign + case "encipheronly": + parsedKeyUsages |= x509.KeyUsageEncipherOnly + case "decipheronly": + parsedKeyUsages |= x509.KeyUsageDecipherOnly + } + } + + return int(parsedKeyUsages) +} + +func parseExtKeyUsages(role *roleEntry) certutil.CertExtKeyUsage { + var parsedKeyUsages certutil.CertExtKeyUsage + + if role.ServerFlag { + parsedKeyUsages |= certutil.ServerAuthExtKeyUsage + } + + if role.ClientFlag { + parsedKeyUsages |= certutil.ClientAuthExtKeyUsage + } + + if role.CodeSigningFlag { + parsedKeyUsages |= certutil.CodeSigningExtKeyUsage + } + + if role.EmailProtectionFlag { + parsedKeyUsages |= certutil.EmailProtectionExtKeyUsage + } + + for _, k := range role.ExtKeyUsage { + switch strings.ToLower(strings.TrimSpace(k)) { + case "any": + parsedKeyUsages |= certutil.AnyExtKeyUsage + case "serverauth": + parsedKeyUsages |= certutil.ServerAuthExtKeyUsage + case "clientauth": + parsedKeyUsages |= certutil.ClientAuthExtKeyUsage + case "codesigning": + parsedKeyUsages |= certutil.CodeSigningExtKeyUsage + case "emailprotection": + parsedKeyUsages |= certutil.EmailProtectionExtKeyUsage + case "ipsecendsystem": + parsedKeyUsages |= certutil.IpsecEndSystemExtKeyUsage + case "ipsectunnel": + parsedKeyUsages |= certutil.IpsecTunnelExtKeyUsage + case "ipsecuser": + parsedKeyUsages |= certutil.IpsecUserExtKeyUsage + case "timestamping": + parsedKeyUsages |= certutil.TimeStampingExtKeyUsage + case "ocspsigning": + parsedKeyUsages |= certutil.OcspSigningExtKeyUsage + case "microsoftservergatedcrypto": + parsedKeyUsages |= certutil.MicrosoftServerGatedCryptoExtKeyUsage + case "netscapeservergatedcrypto": + parsedKeyUsages |= certutil.NetscapeServerGatedCryptoExtKeyUsage + } + } + + return parsedKeyUsages +} + +type roleEntry struct { + LeaseMax string `json:"lease_max"` + Lease string `json:"lease"` + DeprecatedMaxTTL string `json:"max_ttl" mapstructure:"max_ttl"` + DeprecatedTTL string `json:"ttl" mapstructure:"ttl"` + TTL time.Duration `json:"ttl_duration" mapstructure:"ttl_duration"` + MaxTTL time.Duration `json:"max_ttl_duration" mapstructure:"max_ttl_duration"` + AllowLocalhost bool `json:"allow_localhost" mapstructure:"allow_localhost"` + AllowedBaseDomain string `json:"allowed_base_domain" mapstructure:"allowed_base_domain"` + AllowedDomainsOld string `json:"allowed_domains,omitempty"` + AllowedDomains []string `json:"allowed_domains_list" mapstructure:"allowed_domains"` + AllowedDomainsTemplate bool `json:"allowed_domains_template"` + AllowBaseDomain bool `json:"allow_base_domain"` + AllowBareDomains bool `json:"allow_bare_domains" mapstructure:"allow_bare_domains"` + AllowTokenDisplayName bool `json:"allow_token_displayname" mapstructure:"allow_token_displayname"` + AllowSubdomains bool `json:"allow_subdomains" mapstructure:"allow_subdomains"` + AllowGlobDomains bool `json:"allow_glob_domains" mapstructure:"allow_glob_domains"` + AllowAnyName bool `json:"allow_any_name" mapstructure:"allow_any_name"` + EnforceHostnames bool `json:"enforce_hostnames" mapstructure:"enforce_hostnames"` + AllowIPSANs bool `json:"allow_ip_sans" mapstructure:"allow_ip_sans"` + ServerFlag bool `json:"server_flag" mapstructure:"server_flag"` + ClientFlag bool `json:"client_flag" mapstructure:"client_flag"` + CodeSigningFlag bool `json:"code_signing_flag" mapstructure:"code_signing_flag"` + EmailProtectionFlag bool `json:"email_protection_flag" mapstructure:"email_protection_flag"` + UseCSRCommonName bool `json:"use_csr_common_name" mapstructure:"use_csr_common_name"` + UseCSRSANs bool `json:"use_csr_sans" mapstructure:"use_csr_sans"` + KeyType string `json:"key_type" mapstructure:"key_type"` + KeyBits int `json:"key_bits" mapstructure:"key_bits"` + MaxPathLength *int `json:",omitempty" mapstructure:"max_path_length"` + KeyUsageOld string `json:"key_usage,omitempty"` + KeyUsage []string `json:"key_usage_list" mapstructure:"key_usage"` + ExtKeyUsage []string `json:"extended_key_usage_list" mapstructure:"extended_key_usage"` + OUOld string `json:"ou,omitempty"` + OU []string `json:"ou_list" mapstructure:"ou"` + OrganizationOld string `json:"organization,omitempty"` + Organization []string `json:"organization_list" mapstructure:"organization"` + Country []string `json:"country" mapstructure:"country"` + Locality []string `json:"locality" mapstructure:"locality"` + Province []string `json:"province" mapstructure:"province"` + StreetAddress []string `json:"street_address" mapstructure:"street_address"` + PostalCode []string `json:"postal_code" mapstructure:"postal_code"` + GenerateLease *bool `json:"generate_lease,omitempty"` + NoStore bool `json:"no_store" mapstructure:"no_store"` + RequireCN bool `json:"require_cn" mapstructure:"require_cn"` + AllowedOtherSANs []string `json:"allowed_other_sans" mapstructure:"allowed_other_sans"` + AllowedSerialNumbers []string `json:"allowed_serial_numbers" mapstructure:"allowed_serial_numbers"` + AllowedURISANs []string `json:"allowed_uri_sans" mapstructure:"allowed_uri_sans"` + PolicyIdentifiers []string `json:"policy_identifiers" mapstructure:"policy_identifiers"` + ExtKeyUsageOIDs []string `json:"ext_key_usage_oids" mapstructure:"ext_key_usage_oids"` + BasicConstraintsValidForNonCA bool `json:"basic_constraints_valid_for_non_ca" mapstructure:"basic_constraints_valid_for_non_ca"` + NotBeforeDuration time.Duration `json:"not_before_duration" mapstructure:"not_before_duration"` + + // Used internally for signing intermediates + AllowExpirationPastCA bool +} + +func (r *roleEntry) ToResponseData() map[string]interface{} { + responseData := map[string]interface{}{ + "ttl": int64(r.TTL.Seconds()), + "max_ttl": int64(r.MaxTTL.Seconds()), + "allow_localhost": r.AllowLocalhost, + "allowed_domains": r.AllowedDomains, + "allowed_domains_template": r.AllowedDomainsTemplate, + "allow_bare_domains": r.AllowBareDomains, + "allow_token_displayname": r.AllowTokenDisplayName, + "allow_subdomains": r.AllowSubdomains, + "allow_glob_domains": r.AllowGlobDomains, + "allow_any_name": r.AllowAnyName, + "enforce_hostnames": r.EnforceHostnames, + "allow_ip_sans": r.AllowIPSANs, + "server_flag": r.ServerFlag, + "client_flag": r.ClientFlag, + "code_signing_flag": r.CodeSigningFlag, + "email_protection_flag": r.EmailProtectionFlag, + "use_csr_common_name": r.UseCSRCommonName, + "use_csr_sans": r.UseCSRSANs, + "key_type": r.KeyType, + "key_bits": r.KeyBits, + "key_usage": r.KeyUsage, + "ext_key_usage": r.ExtKeyUsage, + "ext_key_usage_oids": r.ExtKeyUsageOIDs, + "ou": r.OU, + "organization": r.Organization, + "country": r.Country, + "locality": r.Locality, + "province": r.Province, + "street_address": r.StreetAddress, + "postal_code": r.PostalCode, + "no_store": r.NoStore, + "allowed_other_sans": r.AllowedOtherSANs, + "allowed_serial_numbers": r.AllowedSerialNumbers, + "allowed_uri_sans": r.AllowedURISANs, + "require_cn": r.RequireCN, + "policy_identifiers": r.PolicyIdentifiers, + "basic_constraints_valid_for_non_ca": r.BasicConstraintsValidForNonCA, + "not_before_duration": int64(r.NotBeforeDuration.Seconds()), + } + if r.MaxPathLength != nil { + responseData["max_path_length"] = r.MaxPathLength + } + if r.GenerateLease != nil { + responseData["generate_lease"] = r.GenerateLease + } + return responseData +} + +const pathListRolesHelpSyn = `List the existing roles in this backend` + +const pathListRolesHelpDesc = `Roles will be listed by the role name.` + +const pathRoleHelpSyn = `Manage the roles that can be created with this backend.` + +const pathRoleHelpDesc = `This path lets you manage the roles that can be created with this backend.` diff --git a/pkg/pki/path_root.go b/pkg/pki/path_root.go new file mode 100644 index 0000000..ec94909 --- /dev/null +++ b/pkg/pki/path_root.go @@ -0,0 +1,486 @@ +package pki + +import ( + "context" + "crypto/rand" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "reflect" + "strings" + "time" + + "github.com/hashicorp/vault/sdk/helper/certutil" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/errutil" + "github.com/hashicorp/vault/sdk/logical" +) + +func pathGenerateRoot(b *backend) *framework.Path { + ret := &framework.Path{ + Pattern: "root/generate/" + framework.GenericNameRegex("exported"), + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathCAGenerateRoot, + }, + + HelpSynopsis: pathGenerateRootHelpSyn, + HelpDescription: pathGenerateRootHelpDesc, + } + + ret.Fields = addCACommonFields(map[string]*framework.FieldSchema{}) + ret.Fields = addCAKeyGenerationFields(ret.Fields) + ret.Fields = addCAIssueFields(ret.Fields) + + return ret +} + +func pathDeleteRoot(b *backend) *framework.Path { + ret := &framework.Path{ + Pattern: "root", + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.DeleteOperation: b.pathCADeleteRoot, + }, + + HelpSynopsis: pathDeleteRootHelpSyn, + HelpDescription: pathDeleteRootHelpDesc, + } + + return ret +} + +func pathSignIntermediate(b *backend) *framework.Path { + ret := &framework.Path{ + Pattern: "root/sign-intermediate", + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathCASignIntermediate, + }, + + HelpSynopsis: pathSignIntermediateHelpSyn, + HelpDescription: pathSignIntermediateHelpDesc, + } + + ret.Fields = addCACommonFields(map[string]*framework.FieldSchema{}) + ret.Fields = addCAIssueFields(ret.Fields) + + ret.Fields["csr"] = &framework.FieldSchema{ + Type: framework.TypeString, + Default: "", + Description: `PEM-format CSR to be signed.`, + } + + ret.Fields["use_csr_values"] = &framework.FieldSchema{ + Type: framework.TypeBool, + Default: false, + Description: `If true, then: +1) Subject information, including names and alternate +names, will be preserved from the CSR rather than +using values provided in the other parameters to +this path; +2) Any key usages requested in the CSR will be +added to the basic set of key usages used for CA +certs signed by this path; for instance, +the non-repudiation flag.`, + } + + return ret +} + +func pathSignSelfIssued(b *backend) *framework.Path { + ret := &framework.Path{ + Pattern: "root/sign-self-issued", + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathCASignSelfIssued, + }, + + Fields: map[string]*framework.FieldSchema{ + "certificate": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `PEM-format self-issued certificate to be signed.`, + }, + }, + + HelpSynopsis: pathSignSelfIssuedHelpSyn, + HelpDescription: pathSignSelfIssuedHelpDesc, + } + + return ret +} + +func (b *backend) pathCADeleteRoot(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + return nil, req.Storage.Delete(ctx, "config/ca_bundle") +} + +func (b *backend) pathCAGenerateRoot(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + var err error + + entry, err := req.Storage.Get(ctx, "config/ca_bundle") + if err != nil { + return nil, err + } + if entry != nil { + resp := &logical.Response{} + resp.AddWarning(fmt.Sprintf("Refusing to generate a root certificate over an existing root certificate. If you really want to destroy the original root certificate, please issue a delete against %sroot.", req.MountPoint)) + return resp, nil + } + + exported, format, role, errorResp := b.getGenerationParams(data) + if errorResp != nil { + return errorResp, nil + } + + maxPathLengthIface, ok := data.GetOk("max_path_length") + if ok { + maxPathLength := maxPathLengthIface.(int) + role.MaxPathLength = &maxPathLength + } + + input := &inputBundle{ + req: req, + apiData: data, + role: role, + } + parsedBundle, err := generateCert(ctx, b, input, nil, true) + if err != nil { + switch err.(type) { + case errutil.UserError: + return logical.ErrorResponse(err.Error()), nil + case errutil.InternalError: + return nil, err + } + } + + cb, err := parsedBundle.ToCertBundle() + if err != nil { + return nil, errwrap.Wrapf("error converting raw cert bundle to cert bundle: {{err}}", err) + } + + resp := &logical.Response{ + Data: map[string]interface{}{ + "expiration": int64(parsedBundle.Certificate.NotAfter.Unix()), + "serial_number": cb.SerialNumber, + }, + } + + switch format { + case "pem": + resp.Data["certificate"] = cb.Certificate + resp.Data["issuing_ca"] = cb.Certificate + if exported { + resp.Data["private_key"] = cb.PrivateKey + resp.Data["private_key_type"] = cb.PrivateKeyType + } + + case "pem_bundle": + resp.Data["issuing_ca"] = cb.Certificate + + if exported { + resp.Data["private_key"] = cb.PrivateKey + resp.Data["private_key_type"] = cb.PrivateKeyType + resp.Data["certificate"] = fmt.Sprintf("%s\n%s", cb.PrivateKey, cb.Certificate) + } else { + resp.Data["certificate"] = cb.Certificate + } + + case "der": + resp.Data["certificate"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes) + resp.Data["issuing_ca"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes) + if exported { + resp.Data["private_key"] = base64.StdEncoding.EncodeToString(parsedBundle.PrivateKeyBytes) + resp.Data["private_key_type"] = cb.PrivateKeyType + } + } + + if data.Get("private_key_format").(string) == "pkcs8" { + err = convertRespToPKCS8(resp) + if err != nil { + return nil, err + } + } + + // Store it as the CA bundle + entry, err = logical.StorageEntryJSON("config/ca_bundle", cb) + if err != nil { + return nil, err + } + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, err + } + + // Also store it as just the certificate identified by serial number, so it + // can be revoked + err = req.Storage.Put(ctx, &logical.StorageEntry{ + Key: "certs/" + normalizeSerial(cb.SerialNumber), + Value: parsedBundle.CertificateBytes, + }) + if err != nil { + return nil, errwrap.Wrapf("unable to store certificate locally: {{err}}", err) + } + + // For ease of later use, also store just the certificate at a known + // location + entry.Key = "ca" + entry.Value = parsedBundle.CertificateBytes + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, err + } + + // Build a fresh CRL + err = buildCRL(ctx, b, req, true) + if err != nil { + return nil, err + } + + if parsedBundle.Certificate.MaxPathLen == 0 { + resp.AddWarning("Max path length of the generated certificate is zero. This certificate cannot be used to issue intermediate CA certificates.") + } + + return resp, nil +} + +func (b *backend) pathCASignIntermediate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + var err error + + format := getFormat(data) + if format == "" { + return logical.ErrorResponse( + `The "format" path parameter must be "pem" or "der"`, + ), nil + } + + role := &roleEntry{ + OU: data.Get("ou").([]string), + Organization: data.Get("organization").([]string), + Country: data.Get("country").([]string), + Locality: data.Get("locality").([]string), + Province: data.Get("province").([]string), + StreetAddress: data.Get("street_address").([]string), + PostalCode: data.Get("postal_code").([]string), + TTL: time.Duration(data.Get("ttl").(int)) * time.Second, + AllowLocalhost: true, + AllowAnyName: true, + AllowIPSANs: true, + EnforceHostnames: false, + KeyType: "any", + AllowedURISANs: []string{"*"}, + AllowedSerialNumbers: []string{"*"}, + AllowExpirationPastCA: true, + } + + if cn := data.Get("common_name").(string); len(cn) == 0 { + role.UseCSRCommonName = true + } + + var caErr error + signingBundle, caErr := fetchCAInfo(ctx, req) + switch caErr.(type) { + case errutil.UserError: + return nil, errutil.UserError{Err: fmt.Sprintf( + "could not fetch the CA certificate (was one set?): %s", caErr)} + case errutil.InternalError: + return nil, errutil.InternalError{Err: fmt.Sprintf( + "error fetching CA certificate: %s", caErr)} + } + + useCSRValues := data.Get("use_csr_values").(bool) + + maxPathLengthIface, ok := data.GetOk("max_path_length") + if ok { + maxPathLength := maxPathLengthIface.(int) + role.MaxPathLength = &maxPathLength + } + + input := &inputBundle{ + req: req, + apiData: data, + role: role, + } + parsedBundle, err := signCert(b, input, signingBundle, true, useCSRValues) + if err != nil { + switch err.(type) { + case errutil.UserError: + return logical.ErrorResponse(err.Error()), nil + case errutil.InternalError: + return nil, err + } + } + + if err := parsedBundle.Verify(); err != nil { + return nil, errwrap.Wrapf("verification of parsed bundle failed: {{err}}", err) + } + + signingCB, err := signingBundle.ToCertBundle() + if err != nil { + return nil, errwrap.Wrapf("error converting raw signing bundle to cert bundle: {{err}}", err) + } + + cb, err := parsedBundle.ToCertBundle() + if err != nil { + return nil, errwrap.Wrapf("error converting raw cert bundle to cert bundle: {{err}}", err) + } + + resp := &logical.Response{ + Data: map[string]interface{}{ + "expiration": int64(parsedBundle.Certificate.NotAfter.Unix()), + "serial_number": cb.SerialNumber, + }, + } + + if signingBundle.Certificate.NotAfter.Before(parsedBundle.Certificate.NotAfter) { + resp.AddWarning("The expiration time for the signed certificate is after the CA's expiration time. If the new certificate is not treated as a root, validation paths with the certificate past the issuing CA's expiration time will fail.") + } + + switch format { + case "pem": + resp.Data["certificate"] = cb.Certificate + resp.Data["issuing_ca"] = signingCB.Certificate + if cb.CAChain != nil && len(cb.CAChain) > 0 { + resp.Data["ca_chain"] = cb.CAChain + } + + case "pem_bundle": + resp.Data["certificate"] = cb.ToPEMBundle() + resp.Data["issuing_ca"] = signingCB.Certificate + if cb.CAChain != nil && len(cb.CAChain) > 0 { + resp.Data["ca_chain"] = cb.CAChain + } + + case "der": + resp.Data["certificate"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes) + resp.Data["issuing_ca"] = base64.StdEncoding.EncodeToString(signingBundle.CertificateBytes) + + var caChain []string + for _, caCert := range parsedBundle.CAChain { + caChain = append(caChain, base64.StdEncoding.EncodeToString(caCert.Bytes)) + } + if caChain != nil && len(caChain) > 0 { + resp.Data["ca_chain"] = cb.CAChain + } + } + + err = req.Storage.Put(ctx, &logical.StorageEntry{ + Key: "certs/" + normalizeSerial(cb.SerialNumber), + Value: parsedBundle.CertificateBytes, + }) + if err != nil { + return nil, errwrap.Wrapf("unable to store certificate locally: {{err}}", err) + } + + if parsedBundle.Certificate.MaxPathLen == 0 { + resp.AddWarning("Max path length of the signed certificate is zero. This certificate cannot be used to issue intermediate CA certificates.") + } + + return resp, nil +} + +func (b *backend) pathCASignSelfIssued(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + var err error + + certPem := data.Get("certificate").(string) + block, _ := pem.Decode([]byte(certPem)) + if block == nil || len(block.Bytes) == 0 { + return logical.ErrorResponse("certificate could not be PEM-decoded"), nil + } + certs, err := x509.ParseCertificates(block.Bytes) + if err != nil { + return logical.ErrorResponse(fmt.Sprintf("error parsing certificate: %s", err)), nil + } + if len(certs) != 1 { + return logical.ErrorResponse(fmt.Sprintf("%d certificates found in PEM file, expected 1", len(certs))), nil + } + + cert := certs[0] + if !cert.IsCA { + return logical.ErrorResponse("given certificate is not a CA certificate"), nil + } + if !reflect.DeepEqual(cert.Issuer, cert.Subject) { + return logical.ErrorResponse("given certificate is not self-issued"), nil + } + + var caErr error + signingBundle, caErr := fetchCAInfo(ctx, req) + switch caErr.(type) { + case errutil.UserError: + return nil, errutil.UserError{Err: fmt.Sprintf( + "could not fetch the CA certificate (was one set?): %s", caErr)} + case errutil.InternalError: + return nil, errutil.InternalError{Err: fmt.Sprintf( + "error fetching CA certificate: %s", caErr)} + } + + signingCB, err := signingBundle.ToCertBundle() + if err != nil { + return nil, errwrap.Wrapf("error converting raw signing bundle to cert bundle: {{err}}", err) + } + + urls := &certutil.URLEntries{} + if signingBundle.URLs != nil { + urls = signingBundle.URLs + } + cert.IssuingCertificateURL = urls.IssuingCertificates + cert.CRLDistributionPoints = urls.CRLDistributionPoints + cert.OCSPServer = urls.OCSPServers + + newCert, err := x509.CreateCertificate(rand.Reader, cert, signingBundle.Certificate, cert.PublicKey, signingBundle.PrivateKey) + if err != nil { + return nil, errwrap.Wrapf("error signing self-issued certificate: {{err}}", err) + } + if len(newCert) == 0 { + return nil, fmt.Errorf("nil cert was created when signing self-issued certificate") + } + pemCert := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: newCert, + }) + + return &logical.Response{ + Data: map[string]interface{}{ + "certificate": strings.TrimSpace(string(pemCert)), + "issuing_ca": signingCB.Certificate, + }, + }, nil +} + +const pathGenerateRootHelpSyn = ` +Generate a new CA certificate and private key used for signing. +` + +const pathGenerateRootHelpDesc = ` +See the API documentation for more information. +` + +const pathDeleteRootHelpSyn = ` +Deletes the root CA key to allow a new one to be generated. +` + +const pathDeleteRootHelpDesc = ` +See the API documentation for more information. +` + +const pathSignIntermediateHelpSyn = ` +Issue an intermediate CA certificate based on the provided CSR. +` + +const pathSignIntermediateHelpDesc = ` +see the API documentation for more information. +` + +const pathSignSelfIssuedHelpSyn = ` +Signs another CA's self-issued certificate. +` + +const pathSignSelfIssuedHelpDesc = ` +Signs another CA's self-issued certificate. This is most often used for rolling roots; unless you know you need this you probably want to use sign-intermediate instead. + +Note that this is a very privileged operation and should be extremely restricted in terms of who is allowed to use it. All values will be taken directly from the incoming certificate and only verification that it is self-issued will be performed. + +Configured URLs for CRLs/OCSP/etc. will be copied over and the issuer will be this mount's CA cert. Other than that, all other values will be used verbatim. +` diff --git a/pkg/pki/path_tidy.go b/pkg/pki/path_tidy.go new file mode 100644 index 0000000..2a03984 --- /dev/null +++ b/pkg/pki/path_tidy.go @@ -0,0 +1,244 @@ +package pki + +import ( + "context" + "crypto/x509" + "fmt" + "net/http" + "sync/atomic" + "time" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/consts" + "github.com/hashicorp/vault/sdk/logical" +) + +func pathTidy(b *backend) *framework.Path { + return &framework.Path{ + Pattern: "tidy", + Fields: map[string]*framework.FieldSchema{ + "tidy_cert_store": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `Set to true to enable tidying up +the certificate store`, + }, + + "tidy_revocation_list": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `Deprecated; synonym for 'tidy_revoked_certs`, + }, + + "tidy_revoked_certs": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `Set to true to expire all revoked +and expired certificates, removing them both from the CRL and from storage. The +CRL will be rotated if this causes any values to be removed.`, + }, + + "safety_buffer": &framework.FieldSchema{ + Type: framework.TypeDurationSecond, + Description: `The amount of extra time that must have passed +beyond certificate expiration before it is removed +from the backend storage and/or revocation list. +Defaults to 72 hours.`, + Default: 259200, //72h, but TypeDurationSecond currently requires defaults to be int + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathTidyWrite, + }, + + HelpSynopsis: pathTidyHelpSyn, + HelpDescription: pathTidyHelpDesc, + } +} + +func (b *backend) pathTidyWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + // If we are a performance standby forward the request to the active node + if b.System().ReplicationState().HasState(consts.ReplicationPerformanceStandby) { + return nil, logical.ErrReadOnly + } + + safetyBuffer := d.Get("safety_buffer").(int) + tidyCertStore := d.Get("tidy_cert_store").(bool) + tidyRevokedCerts := d.Get("tidy_revoked_certs").(bool) + tidyRevocationList := d.Get("tidy_revocation_list").(bool) + + if safetyBuffer < 1 { + return logical.ErrorResponse("safety_buffer must be greater than zero"), nil + } + + bufferDuration := time.Duration(safetyBuffer) * time.Second + + if !atomic.CompareAndSwapUint32(b.tidyCASGuard, 0, 1) { + resp := &logical.Response{} + resp.AddWarning("Tidy operation already in progress.") + return resp, nil + } + + // Tests using framework will screw up the storage so make a locally + // scoped req to hold a reference + req = &logical.Request{ + Storage: req.Storage, + } + + go func() { + defer atomic.StoreUint32(b.tidyCASGuard, 0) + + // Don't cancel when the original client request goes away + ctx = context.Background() + + logger := b.Logger().Named("tidy") + + doTidy := func() error { + if tidyCertStore { + serials, err := req.Storage.List(ctx, "certs/") + if err != nil { + return errwrap.Wrapf("error fetching list of certs: {{err}}", err) + } + + for _, serial := range serials { + certEntry, err := req.Storage.Get(ctx, "certs/"+serial) + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("error fetching certificate %q: {{err}}", serial), err) + } + + if certEntry == nil { + logger.Warn("certificate entry is nil; tidying up since it is no longer useful for any server operations", "serial", serial) + if err := req.Storage.Delete(ctx, "certs/"+serial); err != nil { + return errwrap.Wrapf(fmt.Sprintf("error deleting nil entry with serial %s: {{err}}", serial), err) + } + continue + } + + if certEntry.Value == nil || len(certEntry.Value) == 0 { + logger.Warn("certificate entry has no value; tidying up since it is no longer useful for any server operations", "serial", serial) + if err := req.Storage.Delete(ctx, "certs/"+serial); err != nil { + return errwrap.Wrapf(fmt.Sprintf("error deleting entry with nil value with serial %s: {{err}}", serial), err) + } + continue + } + + cert, err := x509.ParseCertificate(certEntry.Value) + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("unable to parse stored certificate with serial %q: {{err}}", serial), err) + } + + if time.Now().After(cert.NotAfter.Add(bufferDuration)) { + if err := req.Storage.Delete(ctx, "certs/"+serial); err != nil { + return errwrap.Wrapf(fmt.Sprintf("error deleting serial %q from storage: {{err}}", serial), err) + } + } + } + } + + if tidyRevokedCerts || tidyRevocationList { + b.revokeStorageLock.Lock() + defer b.revokeStorageLock.Unlock() + + tidiedRevoked := false + + revokedSerials, err := req.Storage.List(ctx, "revoked/") + if err != nil { + return errwrap.Wrapf("error fetching list of revoked certs: {{err}}", err) + } + + var revInfo revocationInfo + for _, serial := range revokedSerials { + revokedEntry, err := req.Storage.Get(ctx, "revoked/"+serial) + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("unable to fetch revoked cert with serial %q: {{err}}", serial), err) + } + + if revokedEntry == nil { + logger.Warn("revoked entry is nil; tidying up since it is no longer useful for any server operations", "serial", serial) + if err := req.Storage.Delete(ctx, "revoked/"+serial); err != nil { + return errwrap.Wrapf(fmt.Sprintf("error deleting nil revoked entry with serial %s: {{err}}", serial), err) + } + continue + } + + if revokedEntry.Value == nil || len(revokedEntry.Value) == 0 { + logger.Warn("revoked entry has nil value; tidying up since it is no longer useful for any server operations", "serial", serial) + if err := req.Storage.Delete(ctx, "revoked/"+serial); err != nil { + return errwrap.Wrapf(fmt.Sprintf("error deleting revoked entry with nil value with serial %s: {{err}}", serial), err) + } + continue + } + + err = revokedEntry.DecodeJSON(&revInfo) + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("error decoding revocation entry for serial %q: {{err}}", serial), err) + } + + revokedCert, err := x509.ParseCertificate(revInfo.CertificateBytes) + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("unable to parse stored revoked certificate with serial %q: {{err}}", serial), err) + } + + // Remove the matched certificate entries from revoked/ and + // cert/ paths. We compare against both the NotAfter time + // within the cert itself and the time from the revocation + // entry, and perform tidy if either one tells us that the + // certificate has already been revoked. + now := time.Now() + if now.After(revokedCert.NotAfter.Add(bufferDuration)) || now.After(revInfo.RevocationTimeUTC.Add(bufferDuration)) { + if err := req.Storage.Delete(ctx, "revoked/"+serial); err != nil { + return errwrap.Wrapf(fmt.Sprintf("error deleting serial %q from revoked list: {{err}}", serial), err) + } + if err := req.Storage.Delete(ctx, "certs/"+serial); err != nil { + return errwrap.Wrapf(fmt.Sprintf("error deleting serial %q from store when tidying revoked: {{err}}", serial), err) + } + tidiedRevoked = true + } + } + + if tidiedRevoked { + if err := buildCRL(ctx, b, req, false); err != nil { + return err + } + } + } + + return nil + } + + if err := doTidy(); err != nil { + logger.Error("error running tidy", "error", err) + return + } + }() + + resp := &logical.Response{} + resp.AddWarning("Tidy operation successfully started. Any information from the operation will be printed to Vault's server logs.") + return logical.RespondWithStatusCode(resp, req, http.StatusAccepted) +} + +const pathTidyHelpSyn = ` +Tidy up the backend by removing expired certificates, revocation information, +or both. +` + +const pathTidyHelpDesc = ` +This endpoint allows expired certificates and/or revocation information to be +removed from the backend, freeing up storage and shortening CRLs. + +For safety, this function is a noop if called without parameters; cleanup from +normal certificate storage must be enabled with 'tidy_cert_store' and cleanup +from revocation information must be enabled with 'tidy_revocation_list'. + +The 'safety_buffer' parameter is useful to ensure that clock skew amongst your +hosts cannot lead to a certificate being removed from the CRL while it is still +considered valid by other hosts (for instance, if their clocks are a few +minutes behind). The 'safety_buffer' parameter can be an integer number of +seconds or a string duration like "72h". + +All certificates and/or revocation information currently stored in the backend +will be checked when this endpoint is hit. The expiration of the +certificate/revocation information of each certificate being held in +certificate storage or in revocation information will then be checked. If the +current time, minus the value of 'safety_buffer', is greater than the +expiration, it will be removed. +` diff --git a/pkg/pki/pki_api.go b/pkg/pki/pki_api.go new file mode 100644 index 0000000..ea9685b --- /dev/null +++ b/pkg/pki/pki_api.go @@ -0,0 +1,179 @@ +package pki + +import ( + "context" + "crypto/x509" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" + "github.com/hashicorp/vault/sdk/logical" + "sync" + "time" +) + +type PkiBackend struct { + Backend backend +} + +func (b *PkiBackend) GetRole(ctx context.Context, s logical.Storage, n string) (*RoleEntry, error) { + role, err := b.Backend.getRole(ctx, s, n) + retRole := RoleEntry{role} + return &retRole, err +} + +func AddNonCACommonFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema { + return addNonCACommonFields(fields) +} + +func AddCACommonFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema { + return addCACommonFields(fields) +} + +func AddCAKeyGenerationFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema { + return addCAKeyGenerationFields(fields) +} + +func GetFormat(data *framework.FieldData) string { + return getFormat(data) +} + +func FetchCAInfo(ctx context.Context, req *logical.Request) (*certutil.CAInfoBundle, error) { + return fetchCAInfo(ctx, req) +} + +func FetchCertBySerial(ctx context.Context, req *logical.Request, prefix, serial string) (*logical.StorageEntry, error) { + return fetchCertBySerial(ctx, req, prefix, serial) +} + +func ConvertRespToPKCS8(resp *logical.Response) error { + return convertRespToPKCS8(resp) +} + +func NormalizeSerial(serial string) string { + return normalizeSerial(serial) +} + +func BuildCRL(ctx context.Context, b *PkiBackend, req *logical.Request, forceNew bool) error { + return buildCRL(ctx, &b.Backend, req, forceNew) +} + +//func GenerateConvertedCreationBundle(b *backend, data *interface{}, caSign *certutil.CAInfoBundle, csr *x509.CertificateRequest) (*certutil.CreationBundle, error) { +// dataBundle := (*data).(inputBundle) +// return GenerateCreationBundle(b, &dataBundle, caSign, csr) +//} + +func GenerateConvertedCreationBundle(b *backend, data *InputBundleA, caSign *certutil.CAInfoBundle, csr *x509.CertificateRequest) (*certutil.CreationBundle, error) { + dataBundle := inputBundle{ + role: data.Role.roleEntry, + req: data.Req, + apiData: data.ApiData, + } + return GenerateCreationBundle(b, &dataBundle, caSign, csr) +} + +func GenerateCreationBundle(b *backend, data *inputBundle, caSign *certutil.CAInfoBundle, csr *x509.CertificateRequest) (*certutil.CreationBundle, error) { + return generateCreationBundle(b, data, caSign, csr) +} + +func GetURLs(ctx context.Context, req *logical.Request) (*certutil.URLEntries, error) { + return getURLs(ctx, req) +} + +func PathListRoles(b *backend) *framework.Path { + return pathListRoles(b) +} + +func PathRoles(b *backend) *framework.Path { + return pathRoles(b) +} + +func (b *backend) PathRoleCreate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + return b.pathRoleCreate(ctx, req, data) +} + +func PathConfigCRL(b *backend) *framework.Path { + return pathConfigCRL(b) +} + +func (b *backend) PathCRLWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + return b.pathCRLWrite(ctx, req, d) +} + +func PathFetchCRL(b *backend) *framework.Path { + return pathFetchCRL(b) +} + +func (b *backend) PathFetchRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (response *logical.Response, retErr error) { + return b.pathFetchRead(ctx, req, data) +} + +func PathFetchCRLViaCertPath(b *backend) *framework.Path { + return pathFetchCRLViaCertPath(b) +} + +func PathFetchCAChain(b *backend) *framework.Path { + return pathFetchCAChain(b) +} + +//func PathRevoke(b *backend) *framework.Path { +// return pathRevoke(b) +//} + +func PathFetchListCerts(b *backend) *framework.Path { + return pathFetchListCerts(b) +} + +func PathFetchValid(b *backend) *framework.Path { + return pathFetchValid(b) +} + +func PathFetchCA(b *backend) *framework.Path { + return pathFetchCA(b) +} + +func SecretCerts(b *backend) *framework.Secret { + return secretCerts(b) +} + +func (b *PkiBackend) SetCrlLifetime(crlLifetime time.Duration) { + b.Backend.crlLifetime = crlLifetime +} + +func (b *PkiBackend) GetCrlLifetime() time.Duration { + return b.Backend.crlLifetime +} + +func (b *PkiBackend) SetStorage(storage logical.Storage) { + b.Backend.storage = storage +} + +func (b *PkiBackend) GetStorage() logical.Storage { + return b.Backend.storage +} + +func (b *PkiBackend) CreateTidyCASGuard() { + b.Backend.tidyCASGuard = new(uint32) +} + +func (b *PkiBackend) GetTidyCASGuard() *uint32 { + return b.Backend.tidyCASGuard +} + +func (b *PkiBackend) GetRevokeStorageLock() *sync.RWMutex { + return &b.Backend.revokeStorageLock +} + +type RoleEntry struct { + *roleEntry +} + +type InputBundleA struct { + Role *RoleEntry + Req *logical.Request + ApiData *framework.FieldData +} + +//func (*InputBundleA) + +type InputBundleB struct { + *inputBundle +} diff --git a/pkg/pki/secret_certs.go b/pkg/pki/secret_certs.go new file mode 100644 index 0000000..a76ff9a --- /dev/null +++ b/pkg/pki/secret_certs.go @@ -0,0 +1,51 @@ +package pki + +import ( + "context" + "fmt" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" +) + +// SecretCertsType is the name used to identify this type +const SecretCertsType = "pki" + +func secretCerts(b *backend) *framework.Secret { + return &framework.Secret{ + Type: SecretCertsType, + Fields: map[string]*framework.FieldSchema{ + "certificate": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `The PEM-encoded concatenated certificate and +issuing certificate authority`, + }, + "private_key": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The PEM-encoded private key for the certificate", + }, + "serial": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `The serial number of the certificate, for handy +reference`, + }, + }, + + Revoke: b.secretCredsRevoke, + } +} + +func (b *backend) secretCredsRevoke(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + if req.Secret == nil { + return nil, fmt.Errorf("secret is nil in request") + } + + serialInt, ok := req.Secret.InternalData["serial_number"] + if !ok { + return nil, fmt.Errorf("could not find serial in internal secret data") + } + + b.revokeStorageLock.Lock() + defer b.revokeStorageLock.Unlock() + + return revokeCert(ctx, b, req, serialInt.(string), true) +} diff --git a/pkg/pki/util.go b/pkg/pki/util.go new file mode 100644 index 0000000..3dffb53 --- /dev/null +++ b/pkg/pki/util.go @@ -0,0 +1,7 @@ +package pki + +import "strings" + +func normalizeSerial(serial string) string { + return strings.Replace(strings.ToLower(serial), ":", "-", -1) +}