├── .github ├── CODEOWNERS └── dependabot.yml ├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── PLUGIN-HASHES.md ├── README-ADVANCED.md ├── README.md ├── docker-compose.yaml ├── go.mod ├── go.sum ├── main.go ├── plugin ├── pki │ ├── backend.go │ ├── backend_test.go │ ├── backend_tpp_test.go │ ├── backend_vaas_test.go │ ├── e2e │ │ ├── path_venafi_cert_e2e.go │ │ ├── path_venafi_cert_e2e_test.go │ │ └── util.go │ ├── path_roles.go │ ├── path_roles_test.go │ ├── path_venafi_cert_enroll.go │ ├── path_venafi_cert_enroll_test.go │ ├── path_venafi_cert_read.go │ ├── path_venafi_cert_revoke.go │ ├── path_venafi_fetch.go │ ├── path_venafi_secrets.go │ ├── path_venafi_secrets_test.go │ ├── secret_certs.go │ ├── test_env.go │ ├── util.go │ ├── util_test.go │ ├── vcert.go │ ├── vcert_test.go │ └── vpkierror │ │ └── errors.go └── util │ ├── constants.go │ └── utils.go └── scripts ├── config ├── nginx │ ├── Dockerfile │ ├── cert │ │ ├── cloud-nginx.crt.ctmpl │ │ ├── cloud-nginx.key.ctmpl │ │ ├── fake-nginx.crt.ctmpl │ │ ├── fake-nginx.key.ctmpl │ │ ├── tpp-nginx.crt.ctmpl │ │ └── tpp-nginx.key.ctmpl │ ├── consul-template-cloud.hcl │ ├── consul-template-fake.hcl │ ├── consul-template-tpp.hcl │ ├── consul-template.service │ ├── nginx.conf │ └── nginx.service └── vault │ ├── vault-config-with-consul.hcl │ └── vault-config.hcl.sed └── tools ├── check-certificate.sh ├── nginx.sh ├── vault ├── cubbyhole-wrap-token.sh ├── unwrap-token.sh ├── vault-read.sh ├── vault-write.sh └── wrap-token.sh ├── wait-for-it.sh └── what-is-my-host-ip.sh /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | *.go @rvelaVenafi @marcos-albornoz @luispresuelVenafi @EduardoVV 2 | *.md @jdw2465VEN @tr1ck3r 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | time: "09:00" 8 | day: "monday" 9 | timezone: "America/Inuvik" 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | chain.pem 3 | bundle.pem 4 | credentials 5 | credentials-ext-cloud 6 | credentials-int-cloud 7 | int-token 8 | ext-token 9 | root-token 10 | .idea 11 | !*/** 12 | vendor 13 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: false 3 | modules-download-mode: readonly 4 | 5 | linters: 6 | disable: 7 | - deadcode 8 | - varcheck 9 | enable: 10 | - gosec 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.14.0 (February 17, 2025) 2 | * Adds support for Darwin ARM based systems 3 | * Fixes issues for found CVE's 4 | 5 | # v0.13.0 (February 29, 2023) 6 | * Enables adding a custom timeout for requests by fixing bug below 7 | * Fixes bug for current `server_timeout` role attribute 8 | 9 | # v0.12.1 (January 19, 2023) 10 | * Added `ignore_local_storage` and `min_cert_time_left` new attributes at `issue` path, which 11 | bypasses `prevent-reissue-local` feature, if enabled, and requests the certificate, and handles 12 | certificate time left considered to be valid, respectively 13 | * Fixes bug that wouldn't let to create `venafi` secret in a Vault cluster environment where refresh tokens were provided 14 | * Added more logs for refresh token process 15 | * Starting from release, binaries are signed 16 | 17 | # v0.12.0 (December 27, 2022) 18 | * Added ability to ignore search-certificate in local storage. Fixes behaviour for prevent-reissue features to have certificate default validity. 19 | * Introduced `proactive refresh` feature, which now relies on handling refreshing the `access_token` by passing two refresh tokens in the `venafi` secret (`refresh_token` and `refresh_token_2`) 20 | * Solved scenario when many requests are sent in parallel 21 | * Added flag `ignore_local` in role parameters to always ignore local storage when issuing a certificate 22 | 23 | # v0.11.0 (November 25, 2022) 24 | Added ability to store certificates by hash string 25 | 26 | Improved the prevention of an issuance of the certificate if it exists Vault storage, adding a new feature that bases searching using a hash string 27 | 28 | # v0.10.6 (September 12, 2022) 29 | Adds bug fix for Prevent-reissue feature to work on VaaS 30 | 31 | # v0.10.5 (August 30, 2022) 32 | Added feature in order to prevent an issuance of the certificate if it is already inside Vault storage 33 | 34 | # v.0.10.4 (May 27, 2022) 35 | Fixed a thread locking bug 36 | 37 | # v0.10.3 (May 12, 2022) 38 | Fixed a bug about storing private keys behavior and validation of certificate mismatch 39 | 40 | # v0.10.2 (March 24, 2022) 41 | Fixed issue with revocation while disabling secrets engine 42 | 43 | # v0.10.1 (March 10, 2022) 44 | Fix for a bug with the use of a synchronized block in pathVenafiCertObtain function. 45 | 46 | # v0.10.0 (Feb 8, 2022) 47 | Support for CSR Service generated and Revoke action and changed the default format of private keys. 48 | 49 | # v0.9.1 (May 25, 2021) 50 | Updated to the latest VCert client version (v4.14.2) to address a timing issue that caused certificates requested from Venafi as a Service to fail sporadically. 51 | 52 | # v0.9.0 (February 11, 2021) 53 | 54 | Updated Venafi Cloud integration to use OutagePREDICT instead of DevOpsACCELERATE. 55 | 56 | ## v0.8.3 (December 31, 2020) 57 | 58 | Resolved issue that unintentionally required trust_bundle_file to be specified for Venafi API services secured by certificates issued by non-publicly trusted CAs https://github.com/Venafi/vault-pki-backend-venafi/issues/79. 59 | 60 | Added text file containing SHA256 hash to release assets (zip archives). 61 | 62 | Discontinued darwin 386 (32-bit macOS) releases since support was dropped in Go 1.15 and Vault 1.6.0 63 | 64 | ## v0.8.2 (December 3, 2020) 65 | 66 | Updated credential requirements for Trust Protection Platform to support initialization with only a `refresh_token`. 67 | 68 | Added `ca_chain`, `issuing_ca`, and `expiration` values to the output of `/issue` and `/sign` operations. 69 | 70 | ## v0.8.1 (October 30, 2020) 71 | 72 | Added `zone` role parameter to allow for multiple zones to be used and avoid issues with Trust Protection Platform token refresh. 73 | 74 | ## v0.8.0 (October 21, 2020) 75 | 76 | Added support for requesting specific validity periods using the Vault native `ttl` and `max_ttl` parameters. 77 | 78 | Added support for Trust Protection Platform Custom Fields. 79 | 80 | ## v0.7.1 (August 28, 2020) 81 | 82 | Added support for token authentication with Trust Protection Platform (API Application ID "hashicorp-vault-by-venafi"). 83 | 84 | Deprecated legacy username/password for Trust Protection Platform. 85 | 86 | Discontinued the `apikey`, `tpp_user`, `tpp_password`, `tpp_url`, `cloud_url`, `trust_bundle_file`, and `zone` role settings. 87 | 88 | ## v0.6.4 (July 23, 2020) 89 | 90 | Updated to prevent certificates from being enrolled by Performance Standby (regression) and Performance Secondary (new issue). 91 | 92 | Extended trust bundle option to Venafi Cloud. 93 | 94 | Added Source Application Tagging for Venafi Cloud. 95 | 96 | ## v0.6.2 (March 16, 2020) 97 | 98 | Reverted to no error on attempt to revoke (unsupported) to restore ability to disable backend. 99 | 100 | Introduced `no_store` and `store_by` parameters to replace `store_by_cn` and `store_by_serial` (now deprecated). 101 | 102 | Added Source Application Tagging for Trust Protection Platform. 103 | 104 | ## v0.5.3 (December 18, 2019) 105 | 106 | Resolved issue involving the handling of IP SANs. 107 | 108 | ## v0.5.2 (November 20, 2019) 109 | 110 | Updated to prevent issuing certificate twice with Vault Enterprise Performance Standbys. 111 | 112 | ## v0.5.1 (July 13, 2019) 113 | 114 | Updated to latest VCert-Go library. 115 | 116 | ## v0.4.2 (May 21, 2019) 117 | 118 | Added support for signing externally generated CSRs. 119 | 120 | ## v0.4.1 (April 17, 2019) 121 | 122 | Fixed issue related to Windows. https://github.com/hashicorp/go-plugin/pull/111 123 | 124 | ## v0.4.0 (March 6, 2019) 125 | 126 | Updated CSR generation to populate Subject OU, O, ST, L, and C from Venafi policy. 127 | 128 | ## v0.3.1 (February 7, 2019) 129 | 130 | Initial Release. 131 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM vault:1.0.2 2 | 3 | # /vault/logs is made available to use as a location to store audit logs, if 4 | # desired; /vault/file is made available to use as a location with the file 5 | # storage backend, if desired; the server will be started with /vault/config as 6 | # the configuration directory so you can add additional config files in that 7 | # location. 8 | RUN mkdir -p /tools && \ 9 | mkdir -p /vault/logs /vault/file /vault/config && \ 10 | chown -R vault:vault /vault 11 | 12 | # Expose the logs directory as a volume since there's potentially long-running 13 | # state in there 14 | VOLUME /vault/logs 15 | 16 | # Expose the file directory as a volume since there's potentially long-running 17 | # state in there 18 | VOLUME /vault/file 19 | 20 | ADD bin/linux/venafi-pki-backend /vault_plugin/venafi-pki-backend 21 | 22 | #Add helper scripts 23 | ADD scripts/tools /tools 24 | 25 | #Add consul configs 26 | ADD scripts/config/vault /config 27 | 28 | 29 | # 8200/tcp is the primary interface that applications use to interact with 30 | # Vault. 31 | EXPOSE 8200 32 | 33 | # By default you'll get a single-node development server that stores everything 34 | # in RAM and bootstraps itself. Don't use this configuration for production. 35 | CMD ["server", "-dev"] 36 | 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. "Contributor Version" 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. "Covered Software" 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the terms of 34 | a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. "Larger Work" 41 | 42 | means a work that combines Covered Software with other material, in a 43 | separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | 47 | means this document. 48 | 49 | 1.9. "Licensable" 50 | 51 | means having the right to grant, to the maximum extent possible, whether 52 | at the time of the initial grant or subsequently, any and all of the 53 | rights conveyed by this License. 54 | 55 | 1.10. "Modifications" 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, 60 | deletion from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. "Patent Claims" of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, 67 | process, and apparatus claims, in any patent Licensable by such 68 | Contributor that would be infringed, but for the grant of the License, 69 | by the making, using, selling, offering for sale, having made, import, 70 | or transfer of either its Contributions or its Contributor Version. 71 | 72 | 1.12. "Secondary License" 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. "Source Code Form" 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. "You" (or "Your") 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, "You" includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, "control" means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or 104 | as part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its 108 | Contributions or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution 113 | become effective for each Contribution on the date the Contributor first 114 | distributes such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under 119 | this License. No additional rights or licenses will be implied from the 120 | distribution or licensing of Covered Software under this License. 121 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 122 | Contributor: 123 | 124 | a. for any code that a Contributor has removed from Covered Software; or 125 | 126 | b. for infringements caused by: (i) Your and any other third party's 127 | modifications of Covered Software, or (ii) the combination of its 128 | Contributions with other software (except as part of its Contributor 129 | Version); or 130 | 131 | c. under Patent Claims infringed by Covered Software in the absence of 132 | its Contributions. 133 | 134 | This License does not grant any rights in the trademarks, service marks, 135 | or logos of any Contributor (except as may be necessary to comply with 136 | the notice requirements in Section 3.4). 137 | 138 | 2.4. Subsequent Licenses 139 | 140 | No Contributor makes additional grants as a result of Your choice to 141 | distribute the Covered Software under a subsequent version of this 142 | License (see Section 10.2) or under the terms of a Secondary License (if 143 | permitted under the terms of Section 3.3). 144 | 145 | 2.5. Representation 146 | 147 | Each Contributor represents that the Contributor believes its 148 | Contributions are its original creation(s) or it has sufficient rights to 149 | grant the rights to its Contributions conveyed by this License. 150 | 151 | 2.6. Fair Use 152 | 153 | This License is not intended to limit any rights You have under 154 | applicable copyright doctrines of fair use, fair dealing, or other 155 | equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under 169 | the terms of this License. You must inform recipients that the Source 170 | Code Form of the Covered Software is governed by the terms of this 171 | License, and how they can obtain a copy of this License. You may not 172 | attempt to alter or restrict the recipients' rights in the Source Code 173 | Form. 174 | 175 | 3.2. Distribution of Executable Form 176 | 177 | If You distribute Covered Software in Executable Form then: 178 | 179 | a. such Covered Software must also be made available in Source Code Form, 180 | as described in Section 3.1, and You must inform recipients of the 181 | Executable Form how they can obtain a copy of such Source Code Form by 182 | reasonable means in a timely manner, at a charge no more than the cost 183 | of distribution to the recipient; and 184 | 185 | b. You may distribute such Executable Form under the terms of this 186 | License, or sublicense it under different terms, provided that the 187 | license for the Executable Form does not attempt to limit or alter the 188 | recipients' rights in the Source Code Form under this License. 189 | 190 | 3.3. Distribution of a Larger Work 191 | 192 | You may create and distribute a Larger Work under terms of Your choice, 193 | provided that You also comply with the requirements of this License for 194 | the Covered Software. If the Larger Work is a combination of Covered 195 | Software with a work governed by one or more Secondary Licenses, and the 196 | Covered Software is not Incompatible With Secondary Licenses, this 197 | License permits You to additionally distribute such Covered Software 198 | under the terms of such Secondary License(s), so that the recipient of 199 | the Larger Work may, at their option, further distribute the Covered 200 | Software under the terms of either this License or such Secondary 201 | License(s). 202 | 203 | 3.4. Notices 204 | 205 | You may not remove or alter the substance of any license notices 206 | (including copyright notices, patent notices, disclaimers of warranty, or 207 | limitations of liability) contained within the Source Code Form of the 208 | Covered Software, except that You may alter any license notices to the 209 | extent required to remedy known factual inaccuracies. 210 | 211 | 3.5. Application of Additional Terms 212 | 213 | You may choose to offer, and to charge a fee for, warranty, support, 214 | indemnity or liability obligations to one or more recipients of Covered 215 | Software. However, You may do so only on Your own behalf, and not on 216 | behalf of any Contributor. You must make it absolutely clear that any 217 | such warranty, support, indemnity, or liability obligation is offered by 218 | You alone, and You hereby agree to indemnify every Contributor for any 219 | liability incurred by such Contributor as a result of warranty, support, 220 | indemnity or liability terms You offer. You may include additional 221 | disclaimers of warranty and limitations of liability specific to any 222 | jurisdiction. 223 | 224 | 4. Inability to Comply Due to Statute or Regulation 225 | 226 | If it is impossible for You to comply with any of the terms of this License 227 | with respect to some or all of the Covered Software due to statute, 228 | judicial order, or regulation then You must: (a) comply with the terms of 229 | this License to the maximum extent possible; and (b) describe the 230 | limitations and the code they affect. Such description must be placed in a 231 | text file included with all distributions of the Covered Software under 232 | this License. Except to the extent prohibited by statute or regulation, 233 | such description must be sufficiently detailed for a recipient of ordinary 234 | skill to be able to understand it. 235 | 236 | 5. Termination 237 | 238 | 5.1. The rights granted under this License will terminate automatically if You 239 | fail to comply with any of its terms. However, if You become compliant, 240 | then the rights granted under this License from a particular Contributor 241 | are reinstated (a) provisionally, unless and until such Contributor 242 | explicitly and finally terminates Your grants, and (b) on an ongoing 243 | basis, if such Contributor fails to notify You of the non-compliance by 244 | some reasonable means prior to 60 days after You have come back into 245 | compliance. Moreover, Your grants from a particular Contributor are 246 | reinstated on an ongoing basis if such Contributor notifies You of the 247 | non-compliance by some reasonable means, this is the first time You have 248 | received notice of non-compliance with this License from such 249 | Contributor, and You become compliant prior to 30 days after Your receipt 250 | of the notice. 251 | 252 | 5.2. If You initiate litigation against any entity by asserting a patent 253 | infringement claim (excluding declaratory judgment actions, 254 | counter-claims, and cross-claims) alleging that a Contributor Version 255 | directly or indirectly infringes any patent, then the rights granted to 256 | You by any and all Contributors for the Covered Software under Section 257 | 2.1 of this License shall terminate. 258 | 259 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 260 | license agreements (excluding distributors and resellers) which have been 261 | validly granted by You or Your distributors under this License prior to 262 | termination shall survive termination. 263 | 264 | 6. Disclaimer of Warranty 265 | 266 | Covered Software is provided under this License on an "as is" basis, 267 | without warranty of any kind, either expressed, implied, or statutory, 268 | including, without limitation, warranties that the Covered Software is free 269 | of defects, merchantable, fit for a particular purpose or non-infringing. 270 | The entire risk as to the quality and performance of the Covered Software 271 | is with You. Should any Covered Software prove defective in any respect, 272 | You (not any Contributor) assume the cost of any necessary servicing, 273 | repair, or correction. This disclaimer of warranty constitutes an essential 274 | part of this License. No use of any Covered Software is authorized under 275 | this License except under this disclaimer. 276 | 277 | 7. Limitation of Liability 278 | 279 | Under no circumstances and under no legal theory, whether tort (including 280 | negligence), contract, or otherwise, shall any Contributor, or anyone who 281 | distributes Covered Software as permitted above, be liable to You for any 282 | direct, indirect, special, incidental, or consequential damages of any 283 | character including, without limitation, damages for lost profits, loss of 284 | goodwill, work stoppage, computer failure or malfunction, or any and all 285 | other commercial damages or losses, even if such party shall have been 286 | informed of the possibility of such damages. This limitation of liability 287 | shall not apply to liability for death or personal injury resulting from 288 | such party's negligence to the extent applicable law prohibits such 289 | limitation. Some jurisdictions do not allow the exclusion or limitation of 290 | incidental or consequential damages, so this exclusion and limitation may 291 | not apply to You. 292 | 293 | 8. Litigation 294 | 295 | Any litigation relating to this License may be brought only in the courts 296 | of a jurisdiction where the defendant maintains its principal place of 297 | business and such litigation shall be governed by laws of that 298 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 299 | in this Section shall prevent a party's ability to bring cross-claims or 300 | counter-claims. 301 | 302 | 9. Miscellaneous 303 | 304 | This License represents the complete agreement concerning the subject 305 | matter hereof. If any provision of this License is held to be 306 | unenforceable, such provision shall be reformed only to the extent 307 | necessary to make it enforceable. Any law or regulation which provides that 308 | the language of a contract shall be construed against the drafter shall not 309 | be used to construe this License against a Contributor. 310 | 311 | 312 | 10. Versions of the License 313 | 314 | 10.1. New Versions 315 | 316 | Mozilla Foundation is the license steward. Except as provided in Section 317 | 10.3, no one other than the license steward has the right to modify or 318 | publish new versions of this License. Each version will be given a 319 | distinguishing version number. 320 | 321 | 10.2. Effect of New Versions 322 | 323 | You may distribute the Covered Software under the terms of the version 324 | of the License under which You originally received the Covered Software, 325 | or under the terms of any subsequent version published by the license 326 | steward. 327 | 328 | 10.3. Modified Versions 329 | 330 | If you create software not governed by this License, and you want to 331 | create a new license for such software, you may create and use a 332 | modified version of this License if you rename the license and remove 333 | any references to the name of the license steward (except to note that 334 | such modified license differs from this License). 335 | 336 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 337 | Licenses If You choose to distribute Source Code Form that is 338 | Incompatible With Secondary Licenses under the terms of this version of 339 | the License, the notice described in Exhibit B of this License must be 340 | attached. 341 | 342 | Exhibit A - Source Code Form License Notice 343 | 344 | This Source Code Form is subject to the 345 | terms of the Mozilla Public License, v. 346 | 2.0. If a copy of the MPL was not 347 | distributed with this file, You can 348 | obtain one at 349 | http://mozilla.org/MPL/2.0/. 350 | 351 | If it is not possible or desirable to put the notice in a particular file, 352 | then You may include the notice in a location (such as a LICENSE file in a 353 | relevant directory) where a recipient would be likely to look for such a 354 | notice. 355 | 356 | You may add additional accurate notices of copyright ownership. 357 | 358 | Exhibit B - "Incompatible With Secondary Licenses" Notice 359 | 360 | This Source Code Form is "Incompatible 361 | With Secondary Licenses", as defined by 362 | the Mozilla Public License, v. 2.0. 363 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ###Metadata about this makefile and position 2 | MKFILE_PATH := $(lastword $(MAKEFILE_LIST)) 3 | CURRENT_DIR := $(patsubst %/,%,$(dir $(realpath $(MKFILE_PATH)))) 4 | 5 | ###Build parameters 6 | IMAGE_NAME := vault-venafi 7 | DOCKER_IMAGE := venafi/$(IMAGE_NAME) 8 | BUILD_TAG := build 9 | PLUGIN_NAME := venafi-pki-backend 10 | PLUGIN_DIR := bin 11 | PLUGIN_PATH := $(PLUGIN_DIR)/$(PLUGIN_NAME) 12 | DIST_DIR := bin/dist 13 | GO_BUILD := go build -ldflags '-s -w -extldflags "-static"' -a 14 | VERSION=`git describe --abbrev=0 --tags` 15 | 16 | ifdef BUILD_NUMBER 17 | VERSION:=$(VERSION)+$(BUILD_NUMBER) 18 | endif 19 | 20 | ifdef RELEASE_VERSION 21 | ifneq ($(RELEASE_VERSION),none) 22 | VERSION=$(RELEASE_VERSION) 23 | endif 24 | endif 25 | 26 | ###Demo scripts parameters 27 | VAULT_VERSION := $(shell vault --version|awk '{print $$2}') 28 | VAULT_CONT := $$(docker-compose ps |grep Up|grep vault_1|awk '{print $$1}') 29 | DOCKER_CMD := docker exec -it $(VAULT_CONT) 30 | VAULT_CMD := $(DOCKER_CMD) vault 31 | CT_CMD := consul-template 32 | 33 | MOUNT := venafi-pki 34 | FAKE_VENAFI := fakeVenafi 35 | TPP_VENAFI := tppVenafi 36 | CLOUD_VENAFI := cloudVenafi 37 | TOKEN_VENAFI := tppTokenVenafi 38 | 39 | FAKE_ROLE := fake 40 | TPP_ROLE := tpp 41 | CLOUD_ROLE := cloud 42 | TOKEN_ROLE := tppToken 43 | 44 | ROLE_OPTIONS := generate_lease=true store_by="cn" store_pkey="true" ttl=1h max_ttl=1h 45 | 46 | SHA256 := $$(shasum -a 256 "$(PLUGIN_PATH)" | cut -d' ' -f1) 47 | SHA256_DOCKER_CMD := sha256sum "/vault_plugin/venafi-pki-backend" | cut -d' ' -f1 48 | 49 | CHECK_CERT_CMD := ./scripts/tools/check-certificate.sh 50 | CERT_TMP_FILE := /tmp/certificate.crt 51 | 52 | # Domain used on Venafi Platform demo resources 53 | TPP_DOMAIN := venqa.venafi.com 54 | # Domain used in Venafi Cloud demo resources 55 | CLOUD_DOMAIN := venafi.example.com 56 | # Domain used in fake demo resources 57 | FAKE_DOMAIN := fake.example.com 58 | #Random site name for demo resources 59 | RANDOM_SITE_EXP := $$(head /dev/urandom | docker run --rm -i busybox tr -dc a-z0-9 | head -c 5 ; echo '') 60 | 61 | ### Exporting variables for demo and tests 62 | .EXPORT_ALL_VARIABLES: 63 | VAULT_ADDR = http://127.0.0.1:8200 64 | #Must be set,otherwise cloud certificates will timeout 65 | VAULT_CLIENT_TIMEOUT = 180s 66 | 67 | #List of certificates issuers CN 68 | TPP_ISSUER_CN = QA Venafi CA 69 | CLOUD_ISSUER_CN = DigiCert Test SHA2 Intermediate CA-1 70 | FAKE_ISSUER_CN = VCert Test Mode CA 71 | 72 | 73 | version: 74 | echo "$(VERSION)" 75 | 76 | #Need to unset VAULT_TOKEN when running vault with dev parameter. 77 | unset: 78 | unset VAULT_TOKEN 79 | 80 | 81 | #Build 82 | build: 83 | env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO_BUILD) -o $(PLUGIN_DIR)/linux/$(PLUGIN_NAME) || exit 1 84 | env CGO_ENABLED=0 GOOS=linux GOARCH=386 $(GO_BUILD) -o $(PLUGIN_DIR)/linux86/$(PLUGIN_NAME) || exit 1 85 | env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO_BUILD) -o $(PLUGIN_DIR)/darwin/$(PLUGIN_NAME) || exit 1 86 | env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO_BUILD) -o $(PLUGIN_DIR)/darwin_arm/$(PLUGIN_NAME) || exit 1 87 | env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO_BUILD) -o $(PLUGIN_DIR)/windows/$(PLUGIN_NAME).exe || exit 1 88 | env CGO_ENABLED=0 GOOS=windows GOARCH=386 $(GO_BUILD) -o $(PLUGIN_DIR)/windows86/$(PLUGIN_NAME).exe || exit 1 89 | chmod +x $(PLUGIN_DIR)/* 90 | shasum -a 256 "$(PLUGIN_DIR)/linux/$(PLUGIN_NAME)" | cut -d' ' -f1 > $(PLUGIN_DIR)/linux/$(PLUGIN_NAME).SHA256SUM 91 | shasum -a 256 "$(PLUGIN_DIR)/linux86/$(PLUGIN_NAME)" | cut -d' ' -f1 > $(PLUGIN_DIR)/linux86/$(PLUGIN_NAME).SHA256SUM 92 | shasum -a 256 "$(PLUGIN_DIR)/darwin/$(PLUGIN_NAME)" | cut -d' ' -f1 > $(PLUGIN_DIR)/darwin/$(PLUGIN_NAME).SHA256SUM 93 | shasum -a 256 "$(PLUGIN_DIR)/darwin_arm/$(PLUGIN_NAME)" | cut -d' ' -f1 > $(PLUGIN_DIR)/darwin_arm/$(PLUGIN_NAME).SHA256SUM 94 | shasum -a 256 "$(PLUGIN_DIR)/windows/$(PLUGIN_NAME).exe" | cut -d' ' -f1 > $(PLUGIN_DIR)/windows/$(PLUGIN_NAME).exe.SHA256SUM 95 | shasum -a 256 "$(PLUGIN_DIR)/windows86/$(PLUGIN_NAME).exe" | cut -d' ' -f1 > $(PLUGIN_DIR)/windows86/$(PLUGIN_NAME).exe.SHA256SUM 96 | 97 | #quickly build linux for testing 98 | quick_build: 99 | go build -ldflags '-s -w -extldflags "-static"' -a -o $(PLUGIN_DIR)/$(PLUGIN_NAME) || exit 1 100 | 101 | compress: 102 | mkdir -p $(DIST_DIR) 103 | rm -f $(DIST_DIR)/* 104 | echo "Path $(CURRENT_DIR)/$(DIST_DIR)/$(PLUGIN_NAME)_$(VERSION)" 105 | zip -j "$(CURRENT_DIR)/$(DIST_DIR)/$(PLUGIN_NAME)_$(VERSION)_linux.zip" \ 106 | "$(PLUGIN_DIR)/linux/$(PLUGIN_NAME)" \ 107 | "$(PLUGIN_DIR)/linux/$(PLUGIN_NAME)_linux.sig" \ 108 | "$(PLUGIN_DIR)/linux/$(PLUGIN_NAME).SHA256SUM" || exit 1 109 | zip -j "$(CURRENT_DIR)/$(DIST_DIR)/$(PLUGIN_NAME)_$(VERSION)_linux86.zip" \ 110 | "$(PLUGIN_DIR)/linux86/$(PLUGIN_NAME)" \ 111 | "$(PLUGIN_DIR)/linux86/$(PLUGIN_NAME)_linux86.sig" \ 112 | "$(PLUGIN_DIR)/linux86/$(PLUGIN_NAME).SHA256SUM" || exit 1 113 | zip -j "$(CURRENT_DIR)/$(DIST_DIR)/$(PLUGIN_NAME)_$(VERSION)_darwin.zip" \ 114 | "$(PLUGIN_DIR)/darwin/$(PLUGIN_NAME)" \ 115 | "$(PLUGIN_DIR)/darwin/$(PLUGIN_NAME)_darwin.sig" \ 116 | "$(PLUGIN_DIR)/darwin/$(PLUGIN_NAME).SHA256SUM" || exit 1 117 | zip -j "$(CURRENT_DIR)/$(DIST_DIR)/$(PLUGIN_NAME)_$(VERSION)_darwin_arm.zip" \ 118 | "$(PLUGIN_DIR)/darwin_arm/$(PLUGIN_NAME)" \ 119 | "$(PLUGIN_DIR)/darwin_arm/$(PLUGIN_NAME)_darwin_arm.sig" \ 120 | "$(PLUGIN_DIR)/darwin_arm/$(PLUGIN_NAME).SHA256SUM" || exit 1 121 | zip -j "$(CURRENT_DIR)/$(DIST_DIR)/$(PLUGIN_NAME)_$(VERSION)_windows.zip" \ 122 | "$(PLUGIN_DIR)/windows/$(PLUGIN_NAME).exe" "$(PLUGIN_DIR)/windows/$(PLUGIN_NAME).exe.SHA256SUM" || exit 1 123 | zip -j "$(CURRENT_DIR)/$(DIST_DIR)/$(PLUGIN_NAME)_$(VERSION)_windows86.zip"\ 124 | "$(PLUGIN_DIR)/windows86/$(PLUGIN_NAME).exe" "$(PLUGIN_DIR)/windows86/$(PLUGIN_NAME).exe.SHA256SUM" || exit 1 125 | 126 | 127 | build_docker: 128 | docker build -t $(DOCKER_IMAGE):$(BUILD_TAG) . 129 | 130 | test: linter test_go test_e2e 131 | 132 | test_go: 133 | go test -v \ 134 | -race \ 135 | $$(go list ./... | \ 136 | grep -v '/vendor/' | \ 137 | grep -v '/e2e' \ 138 | ) 139 | 140 | test_e2e: 141 | sed -i "s#image:.*$(IMAGE_NAME).*#image: $(DOCKER_IMAGE):$(BUILD_TAG)#" docker-compose.yaml 142 | cd plugin/pki/e2e && ginkgo -v 143 | 144 | test_tpp: 145 | go test -tags=tpp -run ^TestTPP -v github.com/Venafi/vault-pki-backend-venafi/plugin/pki 146 | 147 | test_vaas: 148 | go test -tags=vaas -run ^TestVAAS -v github.com/Venafi/vault-pki-backend-venafi/plugin/pki 149 | 150 | test_fake: 151 | go test -run ^TestFake -v github.com/Venafi/vault-pki-backend-venafi/plugin/pki 152 | 153 | test_token: 154 | go test -run ^TestTokenIntegration$ -v github.com/Venafi/vault-pki-backend-venafi/plugin/pki 155 | 156 | push: build build_docker test_e2e 157 | docker push $(DOCKER_IMAGE):$(BUILD_TAG) 158 | 159 | 160 | #Developement server tasks 161 | dev_server: unset 162 | pkill vault || echo "Vault server is not running" 163 | sed -e 's#__PLUGIN_DIR__#$(PLUGIN_DIR)#' scripts/config/vault/vault-config.hcl.sed > vault-config.hcl 164 | vault server -log-level=debug -dev -config=vault-config.hcl 165 | 166 | dev: quick_build mount_dev 167 | 168 | mount_dev: unset 169 | vault write sys/plugins/catalog/$(PLUGIN_NAME) sha_256="$(SHA256)" command="$(PLUGIN_NAME)" 170 | vault secrets disable $(MOUNT) || echo "Secrets already disabled" 171 | vault secrets enable -path=$(MOUNT) -plugin-name=$(PLUGIN_NAME) plugin 172 | 173 | dev_server_debug: 174 | pkill vault || echo "Vault server is not running" 175 | sed -e 's#__GOPATH__#'$$GOPATH'#' vault-config.hcl.sed > vault-config.hcl 176 | dlv --listen=:2345 --headless=true --api-version=2 exec -- vault server -log-level=debug -dev -config=vault-config.hcl 177 | 178 | 179 | #Production server tasks 180 | prod_server_prepare: 181 | @echo "Using vault client version $(VAULT_VERSION)" 182 | ifeq ($(VAULT_VERSION),v0.10.3) 183 | @echo "Vault version v0.10.3 have bug which prevents plugin to work properly. Please update your vault client" 184 | @exit 1 185 | endif 186 | 187 | prod_server_up: 188 | docker-compose up -d 189 | @echo "Run: docker-compose logs" 190 | @echo "to see the logs" 191 | @echo "Run: docker exec -it cault_vault_1 sh" 192 | @echo "to login into vault container" 193 | @echo "Waiting until server start" 194 | sleep 10 195 | 196 | 197 | prod_server_init: 198 | $(VAULT_CMD) operator init -key-shares=1 -key-threshold=1 199 | @echo "To unseal the vault run:" 200 | @echo "$(VAULT_CMD) operator unseal UNSEAL_KEY" 201 | 202 | prod_server_unseal: 203 | @echo Enter unseal key: 204 | $(VAULT_CMD) operator unseal 205 | 206 | prod_server_login: 207 | @echo Enter root token: 208 | $(VAULT_CMD) login 209 | 210 | prod_server_down: 211 | docker-compose down --remove-orphans 212 | 213 | prod_server_logs: 214 | docker-compose logs -f 215 | 216 | prod_server_sh: 217 | $(DOCKER_CMD) sh 218 | 219 | prod: prod_server_prepare prod_server_down prod_server_up prod_server_init prod_server_unseal prod_server_login mount_prod 220 | @echo "Vault started. To run make command export VAULT_TOKEN variable and run make with -e flag, for example:" 221 | @echo "export VAULT_TOKEN=enter-root-token-here" 222 | @echo "make cloud -e" 223 | 224 | mount_prod: 225 | $(eval SHA256 := $(shell echo $$($(DOCKER_CMD) $(SHA256_DOCKER_CMD)))) 226 | $(VAULT_CMD) write sys/plugins/catalog/$(PLUGIN_NAME) sha_256="$$SHA256" command="$(PLUGIN_NAME)" 227 | $(VAULT_CMD) secrets disable $(MOUNT) || echo "Secrets already disabled" 228 | $(VAULT_CMD) secrets enable -path=$(MOUNT) -plugin-name=$(PLUGIN_NAME) plugin 229 | 230 | #Fake role tasks 231 | fake_config_write: 232 | vault write $(MOUNT)/venafi/$(FAKE_VENAFI) fakemode="true" 233 | vault write $(MOUNT)/roles/$(FAKE_ROLE) venafi_secret=$(FAKE_VENAFI) $(ROLE_OPTIONS) 234 | fake_config_read: 235 | vault read $(MOUNT)/venafi/$(FAKE_VENAFI) 236 | vault read $(MOUNT)/roles/$(FAKE_ROLE) 237 | 238 | fake_cert_write: 239 | $(eval RANDOM_SITE := $(shell echo $(RANDOM_SITE_EXP))) 240 | @echo "Issuing fake-$(RANDOM_SITE).$(FAKE_DOMAIN)" 241 | vault write $(MOUNT)/issue/$(FAKE_ROLE) common_name="fake-$(RANDOM_SITE).$(FAKE_DOMAIN)" alt_names="alt-$(RANDOM_SITE).$(FAKE_DOMAIN),alt2-$(RANDOM_SITE).$(FAKE_DOMAIN)" 242 | fake_cert_read_certificate: 243 | vault read -field=certificate $(MOUNT)/cert/fake-$(RANDOM_SITE).$(FAKE_DOMAIN) > $(CERT_TMP_FILE) 244 | $(CHECK_CERT_CMD) $(CERT_TMP_FILE) 245 | fake_cert_read_pkey: 246 | vault read -field=private_key $(MOUNT)/cert/fake-$(RANDOM_SITE).$(FAKE_DOMAIN)|tee /tmp/privateKey.key 247 | @echo "\nChecking modulus for certificate and key:\n" 248 | @openssl pkey -in /tmp/privateKey.key -pubout -outform pem| sha256sum 249 | @openssl x509 -in $(CERT_TMP_FILE) -pubkey -noout -outform pem | sha256sum 250 | 251 | 252 | fake: fake_config_write fake_cert_write fake_cert_read_certificate fake_cert_read_pkey 253 | 254 | #Cloud role tasks 255 | cloud_config_write: 256 | vault write $(MOUNT)/venafi/$(CLOUD_VENAFI) cloud_url=$(CLOUD_URL) zone="$(CLOUD_ZONE)" apikey=$(CLOUD_APIKEY) 257 | vault write $(MOUNT)/roles/$(CLOUD_ROLE) venafi_secret=$(CLOUD_VENAFI) $(ROLE_OPTIONS) 258 | cloud_config_read: 259 | vault read $(MOUNT)/venafi/$(CLOUD_VENAFI) 260 | vault read $(MOUNT)/roles/$(CLOUD_ROLE) 261 | 262 | cloud_cert_write: 263 | $(eval RANDOM_SITE := $(shell echo $(RANDOM_SITE_EXP))) 264 | @echo "Issuing cloud-$(RANDOM_SITE).$(CLOUD_DOMAIN)" 265 | vault write $(MOUNT)/issue/$(CLOUD_ROLE) common_name="cloud-$(RANDOM_SITE).$(CLOUD_DOMAIN)" alt_names="alt-$(RANDOM_SITE).$(CLOUD_DOMAIN),alt2-$(RANDOM_SITE).$(CLOUD_DOMAIN)" 266 | cloud_cert_read_certificate: 267 | vault read -field=certificate $(MOUNT)/cert/cloud-$(RANDOM_SITE).$(CLOUD_DOMAIN) > $(CERT_TMP_FILE) 268 | $(CHECK_CERT_CMD) $(CERT_TMP_FILE) 269 | cloud_cert_read_pkey: 270 | vault read -field=private_key $(MOUNT)/cert/cloud-$(RANDOM_SITE).$(CLOUD_DOMAIN)|tee /tmp/privateKey.key 271 | @echo "\nChecking modulus for certificate and key:\n" 272 | @openssl pkey -in /tmp/privateKey.key -pubout -outform pem| sha256sum 273 | @openssl x509 -in $(CERT_TMP_FILE) -pubkey -noout -outform pem | sha256sum 274 | 275 | 276 | cloud: cloud_config_write cloud_cert_write cloud_cert_read_certificate cloud_cert_read_pkey 277 | 278 | 279 | #TPP role tasks 280 | tpp_config_write: 281 | vault write $(MOUNT)/venafi/$(TPP_VENAFI) tpp_url=$(TPP_URL) tpp_user=$(TPP_USER) tpp_password=$(TPP_PASSWORD) zone="$(TPP_ZONE)" trust_bundle_file=$(TRUST_BUNDLE) 282 | vault write $(MOUNT)/roles/$(TPP_ROLE) venafi_secret=$(TPP_VENAFI) $(ROLE_OPTIONS) 283 | tpp_config_read: 284 | vault read $(MOUNT)/venafi/$(TPP_VENAFI) 285 | vault read $(MOUNT)/roles/$(TPP_ROLE) 286 | 287 | tpp_cert_write: 288 | $(eval RANDOM_SITE := $(shell echo $(RANDOM_SITE_EXP))) 289 | @echo "Issuing tpp-$(RANDOM_SITE).$(TPP_DOMAIN)" 290 | vault write $(MOUNT)/issue/$(TPP_ROLE) common_name="tpp-$(RANDOM_SITE).$(TPP_DOMAIN)" alt_names="alt-$(RANDOM_SITE).$(TPP_DOMAIN),alt2-$(RANDOM_SITE).$(TPP_DOMAIN)" 291 | tpp_cert_read_certificate: 292 | vault read -field=certificate $(MOUNT)/cert/tpp-$(RANDOM_SITE).$(TPP_DOMAIN) > $(CERT_TMP_FILE) 293 | $(CHECK_CERT_CMD) $(CERT_TMP_FILE) 294 | tpp_cert_read_pkey: 295 | vault read -field=private_key $(MOUNT)/cert/tpp-$(RANDOM_SITE).$(TPP_DOMAIN)|tee /tmp/privateKey.key 296 | @echo "\nChecking modulus for certificate and key:\n" 297 | @openssl pkey -in /tmp/privateKey.key -pubout -outform pem| sha256sum 298 | @openssl x509 -in $(CERT_TMP_FILE) -pubkey -noout -outform pem | sha256sum 299 | 300 | 301 | tpp: tpp_config_write tpp_cert_write tpp_cert_read_certificate tpp_cert_read_pkey 302 | 303 | 304 | #TPP Token tasks 305 | token_config_write: 306 | vault write $(MOUNT)/venafi/$(TOKEN_VENAFI) url=$(TPP_TOKEN_URL) access_token=$(TPP_ACCESS_TOKEN) zone="$(TPP_ZONE)" trust_bundle_file=$(TRUST_BUNDLE) 307 | vault write $(MOUNT)/roles/$(TOKEN_ROLE) venafi_secret=$(TOKEN_VENAFI) $(ROLE_OPTIONS) 308 | token_config_read: 309 | vault read $(MOUNT)/venafi/$(TOKEN_VENAFI) 310 | vault read $(MOUNT)/roles/$(TOKEN_ROLE) 311 | token_cert_write: 312 | $(eval RANDOM_SITE := $(shell echo $(RANDOM_SITE_EXP))) 313 | @echo "Issuing tpp-$(RANDOM_SITE).$(TPP_DOMAIN)" 314 | vault write $(MOUNT)/issue/$(TOKEN_ROLE) common_name="tppToken-$(RANDOM_SITE).$(TPP_DOMAIN)" alt_names="alt-$(RANDOM_SITE).$(TPP_DOMAIN),alt2-$(RANDOM_SITE).$(TPP_DOMAIN)" 315 | token_cert_read_certificate: 316 | vault read -field=certificate $(MOUNT)/cert/tppToken-$(RANDOM_SITE).$(TPP_DOMAIN) > $(CERT_TMP_FILE) 317 | $(CHECK_CERT_CMD) $(CERT_TMP_FILE) 318 | token_cert_read_pkey: 319 | vault read -field=private_key $(MOUNT)/cert/tppToken-$(RANDOM_SITE).$(TPP_DOMAIN)|tee /tmp/privateKey.key 320 | @echo "\nChecking modulus for certificate and key:\n" 321 | @openssl pkey -in /tmp/privateKey.key -pubout -outform pem| sha256sum 322 | @openssl x509 -in $(CERT_TMP_FILE) -pubkey -noout -outform pem | sha256sum 323 | 324 | tpp_token: token_config_write token_cert_write token_cert_read_certificate token_cert_read_pkey 325 | 326 | 327 | #Consul template tasks 328 | consul_template_tpp: tpp_config_write 329 | $(CT_CMD) -once -config=scripts/config/nginx/consul-template-tpp.hcl -vault-token=$(VAULT_TOKEN) 330 | 331 | consul_template_cloud: cloud_config_write 332 | $(CT_CMD) -once -config=scripts/config/nginx/consul-template-cloud.hcl -vault-token=$(VAULT_TOKEN) 333 | 334 | consul_template_fake: fake_config_write 335 | $(CT_CMD) -once -config=scripts/config/nginx/consul-template-fake.hcl -vault-token=$(VAULT_TOKEN) 336 | 337 | consul_template_tpp_daemon: tpp_config_write 338 | $(CT_CMD) -config=scripts/config/nginx/consul-template-tpp.hcl -vault-token=$(VAULT_TOKEN) 339 | 340 | consul_template_cloud_daemon: cloud_config_write 341 | $(CT_CMD) -config=scripts/config/nginx/consul-template-cloud.hcl -vault-token=$(VAULT_TOKEN) 342 | 343 | consul_template_fake_daemon: fake_config_write 344 | $(CT_CMD) -config=scripts/config/nginx/consul-template-fake.hcl -vault-token=$(VAULT_TOKEN) 345 | 346 | nginx: 347 | docker rm -f vault-demo-nginx || echo "Container not found" 348 | docker run --name vault-demo-nginx -p 443:443 -v $$(pwd)/scripts/config/nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro \ 349 | -v $$(pwd)/scripts/config/nginx/cert:/etc/nginx/ssl -d nginx 350 | docker logs -f vault-demo-nginx 351 | 352 | #Helper tasks 353 | doc: 354 | @pandoc --from markdown --to dokuwiki README.md > README.dokuwiki 355 | @pandoc --from markdown --to rst README.md > README.rst 356 | 357 | cert_list: 358 | vault list $(MOUNT)/certs 359 | @echo "\nTo read the certificate run" 360 | @echo "vault read $(MOUNT)/cert/" 361 | 362 | show_config: fake_config_read cloud_config_read tpp_config_read 363 | config: fake_config_write cloud_config_write tpp_config_write 364 | 365 | collect_artifacts: 366 | rm -rf artifacts 367 | mkdir -p artifacts 368 | cp -rv $(DIST_DIR)/*.zip artifacts 369 | 370 | linter: 371 | @golangci-lint --version || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b /go/bin 372 | golangci-lint run 373 | 374 | release: 375 | echo '```' > release.txt 376 | cd artifacts; sha256sum * >> ../release.txt 377 | echo '```' >> release.txt 378 | go install github.com/tcnksm/ghr@v0.16.2 379 | ghr -prerelease -n $$RELEASE_VERSION -body="$$(cat ./release.txt)" $$RELEASE_VERSION artifacts/ 380 | -------------------------------------------------------------------------------- /PLUGIN-HASHES.md: -------------------------------------------------------------------------------- 1 | # Secrets Engine Plugin Versions 2 | 3 | Unfortunately, the HashiCorp Vault plugin architecture does not provide developers with a way to 4 | communicate the actual version of their plugins to Vault administrators. Instead administrators 5 | must rely on the SHA256 hash of the plugin binary to differentiate one version of a plugin from 6 | another. 7 | 8 | Listed below are the SHA256 hashes of plugins from recent official releases, provided to help 9 | simplify the task of identifying which version of the *Venafi PKI Secrets Engine for HashiCorp 10 | Vault* you are currently using. 11 | 12 | ### v0.12.1 13 | ``` 14 | b25746156e7db8990fe237c348be741e959d0ca6820436335c5cb39d8b915f58 darwin venafi-pki-backend 15 | 59e2c3e6b389e340c219781655aa9ad6f0f87cd86ed48425e881401925e9e47d linux venafi-pki-backend 16 | 26d86349b30dec902de9185401c2351f5363dc16f4ae124193708c7d1951a232 linux86 venafi-pki-backend 17 | aeb74aec2282bfc4c50a83998a4554f522dfe526d7034347b0337fab5d2cb613 windows venafi-pki-backend.exe 18 | cd3efcc4098eace5a4ef950b3245b3c0a4d985ed7b6c5f0f8b533686355cd31a windows86 venafi-pki-backend.exe 19 | ``` 20 | 21 | ### v0.12.0 22 | ``` 23 | a0e8c07a1fc0d0681bc6faa1c8ee5426ea5ef50e75d632f12929cc37931d8b41 darwin venafi-pki-backend 24 | 2f34f485a1de4e90ff88cf4ce5a84f180879133e68f6c9e0e132ba5eb2411299 linux venafi-pki-backend 25 | 29c5177cf97ef82bef9a46982981d01980ad9e2da528fc35433969e9b0326eb2 linux86 venafi-pki-backend 26 | 1ee70b1b268641548f16b5536821b41c6d4bccb60f107d51a0b31859a6778809 windows venafi-pki-backend.exe 27 | 6906708814e1509761bb0f599788f0ddd9e233fe06003ecf6149e34949eafe0d windows86 venafi-pki-backend.exe 28 | ``` 29 | 30 | ### v0.11.0 31 | ``` 32 | 11a661801d52955ad8656eba4f851701b8c62f238f7c8c38bbc2cebfac22ca1a darwin venafi-pki-backend 33 | 5fb58fdd83be359e5447ff788f8cf1592a76c3038536a9fe9285bb64d5a14367 linux venafi-pki-backend 34 | 5bc4560462dfe725f93b8c144f72f31992d96fbaf429cc7de4c49534ffca6cb4 linux86 venafi-pki-backend 35 | 0f61497dac53fd0849f8121ab5b5d9781a99ad34aefdf64793c23104caefd6bc windows venafi-pki-backend.exe 36 | cc8cca052374f97cfde93d097c23bed210e59c1fbda7e15460340627e4f8ee79 windows86 venafi-pki-backend.exe 37 | ``` 38 | 39 | ### v0.10.6 40 | ``` 41 | 6bb213b74abc5a2c5683b0d48022cc30ba77e55db7cc47f59ba99886737605c8 darwin venafi-pki-backend 42 | 64aae207e0ab6fa57411ca7f3546e45cdbe789ca965f203e274e4d42ae24e583 linux venafi-pki-backend 43 | cc4d9b1469be8047ee8e3ec3f53ce4849c9b0f6e26eb8f5d8b1d42ccc9a2ea7a linux86 venafi-pki-backend 44 | 12def594aef626a10a2a602322c7cd680fa3f1330d7475045b662bb1a85be8ec windows venafi-pki-backend.exe 45 | b662b84d9182dd9ca1afcd61d1a092723531a9952839c58bbe5700a92454273c windows86 venafi-pki-backend.exe 46 | ``` 47 | 48 | ### v0.10.5 49 | ``` 50 | 4d11e2d06a791e387cf56056db688308cd62964c66ed66fcea94109f447c5faf darwin venafi-pki-backend 51 | 005c5157770ed6f33e8bf4942eac84d0ec6faa6892218596fa2032cd8c32c1be linux venafi-pki-backend 52 | 5f1018231ce2af525f6643afd13edc5999c9905a90d654aaf5e1d00a8654fba5 linux86 venafi-pki-backend 53 | be9c02fea22e465ff89bea1bde78a017288b0d3b51030aff4c9dc4b6d0c91100 windows venafi-pki-backend.exe 54 | c6c47f4a9bdbdb9c233fc5746a10c7d243c88f3514151f47c0c5d93d274b031f windows86 venafi-pki-backend.exe 55 | ``` 56 | 57 | ### v0.10.4 58 | ``` 59 | 1ac60f0f6f6f97006d9c63eb445fada7bd80912a1d52f4b8b1a9d70130f3b6b1 darwin venafi-pki-backend 60 | 0807fb4a244aab85d271f80c70f563c4794132549f9d718823f8dbece119ec91 linux venafi-pki-backend 61 | 3f2da03cb28b01ee0aeb70e6a12e1e27db9e27c296bb9149942fe8c7fb6af789 linux86 venafi-pki-backend 62 | f2c3271bbd31b37a9067d293ea07fb1d7170aa751f02fa94f8035365336b98bc windows venafi-pki-backend.exe 63 | 477650795e10e055696a7521c07882dc505a28ed258afad79e5797e2f9b16c8e windows86 venafi-pki-backend.exe 64 | ``` 65 | 66 | ### v0.10.3 67 | ``` 68 | a9a6b0e8366867d78531e9865347a105af91167d9bff9c038ab2a52b49c65a5c darwin venafi-pki-backend 69 | 0b4f067058c31644e64babda27cd93e8fdd82f78065be68d3c5b627204bbe9ff linux venafi-pki-backend 70 | f0d1defeedd6ddf1cd747f637f8b4d083e5b7b4d89a4f8852c02f98619e8ade9 linux86 venafi-pki-backend 71 | a7b64553e0e257fa9b075ff324cedb5c92e788160c8cecbf16607e3b3a04cf78 windows venafi-pki-backend.exe 72 | a77719907d757b7c41f69e6f115755961390c0641f31e86f9929ff68d4d2f850 windows86 venafi-pki-backend.exe 73 | ``` 74 | 75 | ### v0.10.2 76 | ``` 77 | 4b11554db47301986f7d591843bfdb0142751030aba669f1cec6b15a04c3b965 darwin venafi-pki-backend 78 | 4a62456882cfdc96a0028d429bf83474e9dc0d56320a7c154e238152cbe13c08 linux venafi-pki-backend 79 | f16c5f2a5082dcef137a69050a98b2c7c9665accfff85c4179430d76f50fd401 linux86 venafi-pki-backend 80 | 594503e35c8f8a4ec84e038116290259ebd6da89d14fa1766ba85128670090c6 windows venafi-pki-backend.exe 81 | 5fd3e489ac6cec64e68a023328f0f7976d81d460181f4ff0f7630d93e2039200 windows86 venafi-pki-backend.exe 82 | ``` 83 | 84 | ### v0.10.1 85 | ``` 86 | 4a6b2aedee1c67c1039dc19becd6b82c902ed59ff436338dab674255935395e3 darwin venafi-pki-backend 87 | 21d6101796a528b6c4220c787b9cd5e20f68b658ba653475da2a3cdc583aec51 linux venafi-pki-backend 88 | d7cc0b1f92206612a6e5b49ae71a86766a912e553870594fe09b622eb8c2e288 linux86 venafi-pki-backend 89 | 2d1b631a9888701fb9026e42bda51aa090fcfb27700e92952bf8548c884e7537 windows venafi-pki-backend.exe 90 | 5eed07efda5ea7e28d7d42201e03f0f251feaedb77837a60ea5659555142ba6b windows86 venafi-pki-backend.exe 91 | ``` 92 | 93 | ### v0.10.0 94 | ``` 95 | a09bdabfc31deb2de8b02646d319a71e5424d6db09bd15c63d7f31d02a9cf93a darwin venafi-pki-backend 96 | 322c6d74a9e6ac258feac739100c354f94dff64a7c91b605b29b4cef9e3e8cdc linux venafi-pki-backend 97 | 623a2c207bd2a472108f01c8099e237197df8eef0779898f81151922fefaf752 linux86 venafi-pki-backend 98 | 92cb0527c4871c9c73067ba33610777c2ed81e53545bbf46c8f22179980024bb windows venafi-pki-backend.exe 99 | c2d2e457515f57a0039519f6281b4c9fd83d9d008e878fdf0133d55cfcf3c0bc windows86 venafi-pki-backend.exe 100 | ``` 101 | 102 | ### v0.9.1 103 | ``` 104 | b64502fab669236981f5d4af39624285cb4ae2f6b02a12b8cc5eacd589b96b18 darwin venafi-pki-backend 105 | acfee893ef1363810d433f8456644b36ed6342778ff7d2ad0c17ec2e7d13630b linux venafi-pki-backend 106 | 30f05f537bb17e6569b67aaee97c7586f9acc6d88f723284059d4e9b4ae91e0b linux86 venafi-pki-backend 107 | deca347d1793fd51f0ea051f16330e6c08a0d8675db298f9727a1fb7215c9e25 windows venafi-pki-backend.exe 108 | 8bf18d683b346a43759a62dbb3b0658e56795c0994c516a5d30fe694f8e7fd3a windows86 venafi-pki-backend.exe 109 | ``` 110 | 111 | ### v0.9.0 112 | ``` 113 | b467450dfefe0293593c3ccfd9b65436c9df9571ca6b40d717435dc92dfe1b69 darwin venafi-pki-backend 114 | ef633c05af5224dd2bc992ff3ac1e56a5849b2c6aab9ee9f67840d4c20208e15 linux venafi-pki-backend 115 | bbd152216855d2f441f4374da320eb99784d089464efbbd8db3da76f778dd89b linux86 venafi-pki-backend 116 | e61fd27138b2639f6be578da72836214b1c3ce05a93a3294a079f714586db5b9 windows venafi-pki-backend.exe 117 | 54139afe86a98d58d2300134e161df2300fa91242dfe095a6b820d0b8a4c65f0 windows86 venafi-pki-backend.exe 118 | ``` 119 | 120 | ### v0.8.3 121 | ``` 122 | a943069891e2d725e88d74e002189223442647df6939a5120ded983a120d7ab2 darwin venafi-pki-backend 123 | 4440ee7d3cde5fe2aaab2f0276d645d37aef8edc86651cc183c31c22cd39ea67 linux venafi-pki-backend 124 | 8d892cc449f20c840fefc44ac2d176e13e64f9449e72bf0f059a36008caf62e0 linux86 venafi-pki-backend 125 | 4c84988add1ae2323872ce9b036e996aabec2cfc62d25d1523546eeaa5bde1da windows venafi-pki-backend.exe 126 | 4580b7464f586d148a4a269f84ce21a8bb79131d65020de0b7b46d4848b0fe72 windows86 venafi-pki-backend.exe 127 | ``` 128 | 129 | ### v0.8.2 130 | ``` 131 | 1b81bcf1620dcdf073fcabe0129837038d8205fc8047a847b2c8a968a8943dc9 darwin venafi-pki-backend 132 | 2376ff1173c0613181ae37c96b11f93ab323ed2936a8112009050e583dd8acc6 darwin86 venafi-pki-backend 133 | 1682d4f697436dbcc8b1fd3f7b894ff99678292ba37af5016b40c187d26c7e9e linux venafi-pki-backend 134 | 9bdc4466b1ad0ca48b4f31b35f1d2ea464d0b9f59d7004e21e4556ab43f19ce4 linux86 venafi-pki-backend 135 | 0896d010122884e9430a6cfc66e5b02b8731ef0212eb0c47d0c3ce1bc4d1b217 windows venafi-pki-backend.exe 136 | 4828e87332e1799a6b111eca5e343fe11cc1839eac7e2179ea9f2ed227d5c248 windows86 venafi-pki-backend.exe 137 | ``` 138 | 139 | ### v0.8.1 140 | ``` 141 | 463ee6810a1e0637b2ab3011f4bc0299a45a4e0a41843e14d2570e06e254dcca darwin venafi-pki-backend 142 | 0435045f430b56796c04e579074bfea9b3a3e38044e2bd468501cebd0daf3291 darwin86 venafi-pki-backend 143 | f5c0b6c71328d70af9ad88bf067d5d47f15273b45c5b633de87bb1a5cd2e15ea linux venafi-pki-backend 144 | 25e5059bb9fff75f683124bfbffee7c28c283137e7f8c2684fae789b528f9b0c linux86 venafi-pki-backend 145 | 82698781fcc99a73548d77c84aa2f427f793fa2f5238877d0814316eb6f69067 windows venafi-pki-backend.exe 146 | 5c8fb78367af780dede27a733b4c26e442a5695d9bda654bad853210040c3970 windows86 venafi-pki-backend.exe 147 | ``` 148 | 149 | ### v0.8.0 150 | ``` 151 | 459a014f6abbe2f2cab24974efea3f935eeafabe00de67be938d91ada6ad3d96 darwin venafi-pki-backend 152 | cf8a6e8b06d50e308d187cad4bbf4e78b0fc385e7dd0e370ca0b7f117351c3a7 darwin86 venafi-pki-backend 153 | 018e0fef396103489df57ba4911d75309cbd21026d6b24643db25985846d1fd8 linux venafi-pki-backend 154 | bfa0b57abbe93122c96bddfd5652d14c7117da0384678010a00987c5839e95aa linux86 venafi-pki-backend 155 | 9470ce43996fac1bb645ea6fd40290f6667b6604e855eb07582e957459d61cf5 windows venafi-pki-backend.exe 156 | 38aebbdf41f70fec5da908768e59848c27cdbc12af61fcc0fdc551b03747d32c windows86 venafi-pki-backend.exe 157 | ``` 158 | -------------------------------------------------------------------------------- /README-ADVANCED.md: -------------------------------------------------------------------------------- 1 | ![Venafi](Venafi_logo.png) 2 | [![MPL 2.0 License](https://img.shields.io/badge/License-MPL%202.0-blue.svg)](https://opensource.org/licenses/MPL-2.0) 3 | ![Community Supported](https://img.shields.io/badge/Support%20Level-Community-brightgreen) 4 | ![Compatible with TPP 17.3+ & Cloud](https://img.shields.io/badge/Compatibility-TPP%2017.3+%20%26%20Cloud-f9a90c) 5 | _This open source project is community-supported. To report a problem or share an idea, use the 6 | **[Issues](../../issues)** tab; and if you have a suggestion for fixing the issue, please include those details, too. 7 | In addition, use the **[Pull requests](../../pulls)** tab to contribute actual bug fixes or proposed enhancements. 8 | We welcome and appreciate all contributions._ 9 | 10 | # Venafi PKI Secrets Engine for HashiCorp Vault 11 | 12 | This solution enables [HashiCorp Vault](https://www.vaultproject.io/) users to have certificate requests fulfilled by the [Venafi Platform](https://www.venafi.com/platform/trust-protection-platform) or [Venafi Cloud](https://www.venafi.com/platform/cloud/devops) ensuring compliance with corporate security policy and providing visibility into certificate issuance enterprise wide. 13 | 14 | ## Dependencies 15 | 16 | * HashiCorp Vault: https://www.vaultproject.io/downloads.html 17 | * HashiCorp Consul Template: https://github.com/hashicorp/consul-template#installation 18 | * Docker Compose: https://docs.docker.com/compose/install/ 19 | 20 | ## Demonstrating End-to-End 21 | 22 | Here, we'll use a Makefile to encapsulate several command sequences in a single step. For specific details on those commands and their parameters, please review the contents of the [Makefile](Makefile) itself. 23 | 24 | 1. Export your Venafi Platform and/or Venafi Cloud configuration variables: 25 | 26 | **Venafi Platform Variables** 27 | 28 | ```text 29 | export TPP_USER= 30 | export TPP_PASSWORD= 31 | export TPP_URL= 32 | export TPP_ZONE= 33 | export TRUST_BUNDLE=/bundle.pem 34 | ``` 35 | 36 | The syntax for the Venafi Platform policy folder can be tricky. If the policy folder name contains spaces, it must be wrapped in double quotes like this: 37 | 38 | ```text 39 | export TPP_ZONE="My Policy" * 40 | ``` 41 | 42 | Also, if the policy folder is not at the root of the policy tree (nested folder), you need to escape the backslash delimiters twice (four backslashes in total): 43 | 44 | ```text 45 | export TPP_ZONE="Parent Folder\\\\Child Folder" 46 | ``` 47 | 48 | **Venafi Cloud Variables** 49 | 50 | ```text 51 | export CLOUD_APIKEY= 52 | export CLOUD_ZONE= 53 | export CLOUD_URL= 54 | ``` 55 | 56 | 1. Run `make prod`. 57 | 58 | 1. Follow the Vault [unseal instructions](https://https://www.vaultproject.io/docs/commands/operator/unseal/) to enter the unseal key and get the root token. 59 | 60 | 61 | 1. Export the root token to the VAULT_TOKEN variable (see example in the output). 62 | 63 | ```text 64 | export VAULT_TOKEN="enter-root-token-here" 65 | ``` 66 | 67 | 1. Check Vault status on http://localhost:8200/ui (root token required) and Consul on http://localhost:8500. 68 | 69 | 1. To verify that the Vault is working, run `make consul_template_fake -e`. 70 | 71 | 1. Run the following commands to check Venafi Platform: 72 | 73 | ```text 74 | make consul_template_tpp -e 75 | echo|openssl s_client -connect localhost:3443 76 | ``` 77 | 78 | Or go to the URL https://127.0.0.1:3443. 79 | 80 | 1. Run the following commands to check Venafi Cloud. 81 | 82 | ```text 83 | make consul_template_cloud -e 84 | echo|openssl s_client -connect localhost:2443 85 | ``` 86 | 87 | Or go to the URL https://127.0.0.1:2443. 88 | 89 | 1. You also can verify how the Vault is working without using a HashiCorp Consul Template. Run the following commands for Fake, Platform and Cloud endpoints, respectively: 90 | 91 | ```text 92 | make fake -e 93 | make tpp -e 94 | make cloud -e 95 | ``` 96 | 97 | 1. Cleanup: 98 | 99 | ```text 100 | docker-compose down 101 | docker ps|grep vault-demo-nginx|awk '{print $1}'|xargs docker rm -f 102 | ``` 103 | 104 | ## Usage Scenarios 105 | 106 | First, mount the Venafi plugin. Then, use one of the following sections to get the certificate and private key: 107 | 108 | * Use Trust Protection Platform and Node application 109 | * Use Consul-template engine 110 | 111 | ### Mount Venafi Plugin 112 | 113 | To mount the plugin automatically run `make prod` as described in the previous section. To manually mount the plugin: 114 | 115 | 1. If you want to use a different plugin image, edit the image section under the vault service in the [docker-compose.yaml](docker-compose.yaml) file. 116 | 117 | 1. Start Docker Compose using the configuration: 118 | 119 | ```text 120 | docker-compose up -d 121 | ``` 122 | 123 | 1. Check that all services started using the following commands: 124 | 125 | ```text 126 | docker-compose ps 127 | docker-compose logs 128 | ``` 129 | 130 | 1. Log into the running Vault container: 131 | 132 | ```text 133 | docker exec -it $(docker-compose ps |grep Up|grep vault_1|awk '{print $1}') sh 134 | ``` 135 | 136 | 1. Set the `VAULT_ADDR` variable: 137 | 138 | ```text 139 | export VAULT_ADDR='http://127.0.0.1:8200' 140 | ``` 141 | 142 | 1. Initialize the Vault: 143 | 144 | ```text 145 | vault operator init -key-shares=1 -key-threshold=1 146 | ``` 147 | 148 | Here, we initialize the Vault with only one unseal key part. However, this is not recommended for production usage. Read more at [https://www.vaultproject.io/docs/concepts/seal.html](https://www.vaultproject.io/docs/concepts/seal.html). 149 | 150 | 1. Enter the unseal key. You'll see it as "Unseal Key 1": 151 | 152 | ```text 153 | vault operator unseal UNSEAL_KEY_HERE 154 | ``` 155 | 156 | 1. Authenticate with the root token, you will see it as "Initial Root Token": 157 | 158 | ```text 159 | vault auth 160 | ``` 161 | 162 | 1. After successful authentication, get the SHA-256 checksum of plugin binary and store it in a variable: 163 | 164 | ```text 165 | SHA256=`sha256sum "/vault_plugin/venafi-pki-backend" | cut -d' ' -f1` 166 | echo $SHA256 167 | ``` 168 | 169 | 1. "Write" the plugin into the Vault: 170 | 171 | ```text 172 | vault write sys/plugins/catalog/venafi-pki-backend sha_256="$SHA256" command="venafi-pki-backend" 173 | ``` 174 | 175 | 1. Enable the Venafi secret backend: 176 | 177 | ```text 178 | vault secrets enable -path=venafi-pki -plugin-name=venafi-pki-backend plugin 179 | ``` 180 | 181 | ### Use Trust Protection Platform and Node Application 182 | 183 | Get the certificate and private key from Trust Protection Platform, and then pass them to the Node application. 184 | 185 | 1. Set up custom TPP role: 186 | 187 | ```text 188 | vault write venafi-pki/roles/custom-tpp \ 189 | tpp_url=https://tpp.venafi.example/vedsdk \ 190 | tpp_user=admin \ 191 | tpp_password=password \ 192 | zone=testpolicy\\vault \ 193 | generate_lease=true \ 194 | trust_bundle_file="/opt/venafi/bundle.pem" 195 | ``` 196 | 197 | 1. To set up proper parameters, please read the path-help for the role configuration: 198 | 199 | ```text 200 | vault path-help venafi-pki/roles/tpp 201 | ``` 202 | 203 | 1. Request the certificate: 204 | 205 | ```text 206 | vault write venafi-pki/issue/custom-tpp common_name="tpp-cert1.venqa.venafi.com" alt_names="tpp-cert1-alt1.venqa.venafi.com,tpp-cert1-alt2.venqa.venafi.com" 207 | ``` 208 | 209 | 1. List requested certificates: 210 | 211 | ```text 212 | vault list venafi-pki/certs 213 | ``` 214 | 215 | 1. Store certificate to the PEM file: 216 | 217 | ```text 218 | vault read -field=certificate venafi-pki/cert/tpp-cert1.venqa.venafi.com > tls.crt 219 | ``` 220 | 221 | 1. Store private key to the PEM file: 222 | 223 | ```text 224 | vault read -field=private_key venafi-pki/cert/tpp-cert1.venqa.venafi.com > tls.key 225 | ``` 226 | 227 | 1. Run docker container with Node application: 228 | 229 | ```text 230 | docker run --rm -it --name hello-node-ssl -p 443:443 \ 231 | -v $(pwd)/tls.crt:/etc/certdata/tls.crt:ro \ 232 | -v $(pwd)/tls.key:/etc/certdata/tls.key:ro \ 233 | arykalin/hello-node:v1 234 | ``` 235 | 236 | 1. Go to the https://localhost to check. 237 | 238 | ### Use Consul-template Engine 239 | 240 | To get the certificate and private key from HashiCorp Consul-template Engine, you need the role from the previous scenario. 241 | 242 | 1. Get the consul-template from [https://releases.hashicorp.com/consul-template/](https://releases.hashicorp.com/consul-template/). 243 | 244 | 1. Create config file consul-template.hcl: 245 | 246 | ```text 247 | cat << EOF > consul-template.hcl 248 | 249 | //Configuration of consul backend 250 | consul { 251 | auth { 252 | enabled = false 253 | } 254 | address = "127.0.0.1:8500" 255 | 256 | retry { 257 | enabled = true 258 | attempts = 12 259 | backoff = "250ms" 260 | max_backoff = "1m" 261 | } 262 | 263 | ssl { 264 | enabled = false 265 | } 266 | } 267 | 268 | reload_signal = "SIGHUP" 269 | kill_signal = "SIGINT" 270 | max_stale = "10m" 271 | log_level = "info" 272 | pid_file = "/tmp/venafi-demo-consul-template.pid" 273 | 274 | //Vault configuration 275 | vault { 276 | address = "http://127.0.0.1:8200" 277 | grace = "5m" 278 | unwrap_token = false 279 | renew_token = false 280 | } 281 | 282 | //template for the certificate file 283 | template { 284 | source = "tls.crt.ctmpl" 285 | destination = "tls.crt" 286 | } 287 | 288 | //template for the key file 289 | template { 290 | source = "tls.key.ctmpl" 291 | destination = "tls.key" 292 | command = "/bin/sh -c './app.sh'" 293 | } 294 | EOF 295 | ``` 296 | 297 | 1. Create the template for the certificate file, tls.crt.ctmpl: 298 | 299 | ```text 300 | cat << EOF > tls.crt.ctmpl 301 | {{ with secret "venafi-pki/issue/custom-tpp" "common_name=tpp-cert1-consul-template.venqa.venafi.com " }} 302 | {{ .Data.certificate }}{{ end }} 303 | EOF 304 | ``` 305 | 306 | 1. Create the template for the key file, tls.key.ctmpl: 307 | 308 | ```text 309 | cat << EOF > tls.key.ctmpl 310 | {{ with secret "venafi-pki/issue/custom-tpp" "common_name=tpp-cert1-consul-template.venqa.venafi.com " }} 311 | {{ .Data.private_key }}{{ end }} 312 | EOF 313 | ``` 314 | 315 | 1. Create the launch script app.sh: 316 | 317 | ```text 318 | cat << 'EOF' > app.sh 319 | #!/bin/bash 320 | cont=hello-node-ssl 321 | PORT=7443 322 | docker rm -f $cont || echo "Conrtainer $cont doesn't exists" 323 | docker run --name $cont -d -p ${PORT}:443 \ 324 | -v $(pwd)/tls.crt:/etc/certdata/tls.crt:ro \ 325 | -v $(pwd)/tls.key:/etc/certdata/tls.key:ro \ 326 | arykalin/hello-node:v1 327 | echo "app started, check URL https://localhost:${PORT}" 328 | EOF 329 | chmod +x app.sh 330 | ``` 331 | 332 | 1. Export the vault token variable: 333 | 334 | ```text 335 | export VAULT_TOKEN=YOUR_VAULT_TOKEN_SHOULD_BE_HERE 336 | ``` 337 | 338 | 1. Run consul template command: 339 | 340 | ```text 341 | consul-template -once -config=consul-template.hcl -vault-token=$(VAULT_TOKEN) 342 | ``` 343 | 344 | 1. Use the generated certificate to check https://localhost:7443 for a Hello World app. 345 | 346 | 1. Delete the container with the running application: 347 | 348 | ```text 349 | docker rm -f hello-node-ssl 350 | ``` 351 | 352 | ## Developer Quick Start (Linux only) 353 | 354 | 1. Configure [Go build environment](https://golang.org/doc/install)). 355 | 356 | 1. Change to the project directory and make sure you don't have any symbolic links in the path. The Vault doesn't allow symlinks in the plugin paths. For example, `cd $(pwd -P)`. 357 | 358 | 1. To start the Vault in development mode, run `unset VAULT_TOKEN && make dev_server`. 359 | 360 | 1. Open the new window in the same directory and run: 361 | 362 | ```text 363 | unset VAULT_TOKEN 364 | export VAULT_ADDR='http://127.0.0.1:8200' 365 | ``` 366 | 367 | 1. Run `vault unseal` and enter the unseal key that is located in the server window. 368 | 369 | 1. Put the latest VCert code to your $GOPATH. 370 | 371 | 1. To build the plugin and mount it to the Vault, run `make dev`. 372 | 373 | 1. To use the configuration with a temporary CA generating the certificate, run `make fake`. Then verify the output. You should see something like this: 374 | 375 | ```text 376 | vault read -field=Chain venafi-pki/certs/fake|openssl x509 -text -inform pem -noout -certopt no_header,no_version,no_serial,no_signame,no_pubkey,no_sigdump,no_aux 377 | Issuer: C=US, ST=Utah, L=Salt Lake City, O=Venafi, OU=NOT FOR PRODUCTION, CN=VCert Test Mode CA 378 | Validity 379 | Not Before: Jun 4 13:55:03 2018 GMT 380 | Not After : Sep 2 13:55:03 2018 GMT 381 | Subject: CN=fake-bnhz5.fake.example.com 382 | X509v3 extensions: 383 | X509v3 Extended Key Usage: 384 | TLS Web Server Authentication 385 | X509v3 Basic Constraints: critical 386 | CA:FALSE 387 | X509v3 Authority Key Identifier: 388 | keyid:CE:A4:45:0E:F2:D7:D2:6C:F8:02:33:DB:E3:9B:4B:19:AB:E6:F0:07 389 | X509v3 Subject Alternative Name: 390 | DNS:alt-bnhz5.fake.example.com, DNS:alt2-bnhz5.fake.example.com, DNS:fake-bnhz5.fake.example.com 391 | ``` 392 | 393 | 1. Edit the Makefile and configure credentials for the Venafi Cloud and/or Venafi Platform. 394 | 395 | 1. To check the Cloud and TPP functionality, run `make cloud` and `make tpp`. 396 | 397 | ## Deploy New Image for Prod 398 | 399 | 1. To build the plugin binary and Docker image and deploy it to DockerHub, run `make push`. 400 | 401 | ## Debug Information 402 | 403 | 1. Run `make server_debug`. 404 | 405 | 1. Connect to the dlv server using the debugger setup (pki-backend-debug in idea, for example). 406 | 407 | 1. Unseal the Vault. 408 | 409 | ## Testing 410 | 411 | We have tests for fake vcert endpoint, if you don't have TPP or Cloud you can test all endpoints using this command:_ 412 | 413 | ``` 414 | go test -run ^TestFake -v github.com/Venafi/vault-pki-backend-venafi/plugin/pki 415 | ``` 416 | 417 | Also you can run integration tests but for it you need to add TPP\Cloud credentials._ 418 | 419 | Example fro TPP:_ 420 | ``` 421 | export TPP_USER='admin' 422 | export TPP_PASSWORD='strongPassword' 423 | export TRUST_BUNDLE="/opt/venafi/bundle.pem" 424 | export TPP_URL="https://tpp.example.com:/vedsdk" 425 | export TPP_ZONE="devops\\\\vcert" 426 | 427 | ``` 428 | Example for Cloud:_ 429 | ``` 430 | export CLOUD_ZONE="xxxxxxx-xxxxx-xxxx-xxxx-xxxxxxx" 431 | export CLOUD_APIKEY='xxxxxxx-xxxxx-xxxx-xxxx-xxxxxxx' 432 | ``` 433 | 434 | To run tests use make commands:_ 435 | ``` 436 | make test_tpp 437 | make test_cloud 438 | ``` 439 | 440 | There are also e2e tests written on [Ginkgo](https://github.com/onsi/ginkgo). 441 | 442 | 1. Install the Ginkgo CLI: 443 | 444 | ```bash 445 | go get -u github.com/onsi/ginkgo/ginkgo 446 | ``` 447 | 448 | 1. Run: 449 | 450 | ```bash 451 | cd plugin/pki/test/e2e 452 | ginkgo -v 453 | ``` 454 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | 4 | consul: 5 | image: "consul:1.1.0" 6 | hostname: "consul" 7 | command: "agent -dev -client 0.0.0.0" 8 | ports: 9 | - "8400:8400" 10 | - "8500:8500" 11 | - "8600:53/udp" 12 | 13 | vault: 14 | depends_on: 15 | - consul 16 | # Venafi plugin image 17 | image: venafi/vault-venafi:build 18 | # Uncomment if you want to use compose in dev mode 19 | # build: . 20 | hostname: "vault" 21 | links: 22 | - "consul:consul" 23 | environment: 24 | VAULT_ADDR: http://127.0.0.1:8200 25 | TRUST_BUNDLE: /opt/venafi/bundle.pem 26 | ports: 27 | - "8200:8200" 28 | extra_hosts: 29 | - "ha-tpp1.sqlha.com:192.168.6.23" 30 | volumes: 31 | # If you want to use trust bundle file option 32 | - /tmp/chain.pem:/opt/venafi/bundle.pem 33 | # Uncomment this mounts if you want to use developement version of the plugin 34 | # - ./scripts/tools:/tools 35 | # - ./scripts/config/vault:/config 36 | # - ./scripts/config/vault/policies:/policies 37 | # - ./bin:/vault_plugin 38 | # - ./Makefile:/Makefile 39 | entrypoint: /tools/wait-for-it.sh -t 20 -h consul -p 8500 -s -- vault server -config=/config/vault-config-with-consul.hcl -log-level=debug 40 | networks: # this is a workaround to avoid internal network conflict 41 | default: 42 | ipam: 43 | driver: default 44 | config: 45 | - subnet: "192.168.84.1/24" 46 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Venafi/vault-pki-backend-venafi 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/Venafi/vcert/v5 v5.8.1 7 | github.com/hashicorp/go-hclog v1.6.3 8 | github.com/hashicorp/vault/api v1.14.0 9 | github.com/hashicorp/vault/sdk v0.13.0 10 | github.com/onsi/ginkgo v1.16.5 11 | github.com/onsi/gomega v1.29.0 12 | github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f 13 | github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a 14 | ) 15 | 16 | require ( 17 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 18 | github.com/Khan/genqlient v0.7.0 // indirect 19 | github.com/Microsoft/go-winio v0.6.1 // indirect 20 | github.com/armon/go-metrics v0.4.1 // indirect 21 | github.com/armon/go-radix v1.0.0 // indirect 22 | github.com/cenkalti/backoff/v3 v3.2.2 // indirect 23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 24 | github.com/distribution/reference v0.6.0 // indirect 25 | github.com/docker/docker v25.0.6+incompatible // indirect 26 | github.com/docker/go-connections v0.4.0 // indirect 27 | github.com/docker/go-units v0.5.0 // indirect 28 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 29 | github.com/fatih/color v1.17.0 // indirect 30 | github.com/felixge/httpsnoop v1.0.4 // indirect 31 | github.com/frankban/quicktest v1.14.6 // indirect 32 | github.com/fsnotify/fsnotify v1.5.1 // indirect 33 | github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect 34 | github.com/go-jose/go-jose/v4 v4.0.2 // indirect 35 | github.com/go-logr/logr v1.4.1 // indirect 36 | github.com/go-logr/stdr v1.2.2 // indirect 37 | github.com/gogo/protobuf v1.3.2 // indirect 38 | github.com/golang/protobuf v1.5.4 // indirect 39 | github.com/golang/snappy v0.0.4 // indirect 40 | github.com/google/go-cmp v0.6.0 // indirect 41 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect 42 | github.com/google/uuid v1.6.0 // indirect 43 | github.com/gorilla/websocket v1.5.1 // indirect 44 | github.com/hashicorp/errwrap v1.1.0 // indirect 45 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 46 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 47 | github.com/hashicorp/go-kms-wrapping/entropy/v2 v2.0.1 // indirect 48 | github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 // indirect 49 | github.com/hashicorp/go-multierror v1.1.1 // indirect 50 | github.com/hashicorp/go-plugin v1.6.0 // indirect 51 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 52 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 53 | github.com/hashicorp/go-secure-stdlib/mlock v0.1.3 // indirect 54 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect 55 | github.com/hashicorp/go-secure-stdlib/plugincontainer v0.3.0 // indirect 56 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect 57 | github.com/hashicorp/go-sockaddr v1.0.6 // indirect 58 | github.com/hashicorp/go-uuid v1.0.3 // indirect 59 | github.com/hashicorp/go-version v1.6.0 // indirect 60 | github.com/hashicorp/golang-lru v1.0.2 // indirect 61 | github.com/hashicorp/hcl v1.0.1-vault-5 // indirect 62 | github.com/hashicorp/yamux v0.1.1 // indirect 63 | github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 // indirect 64 | github.com/mattn/go-colorable v0.1.13 // indirect 65 | github.com/mattn/go-isatty v0.0.20 // indirect 66 | github.com/mitchellh/copystructure v1.2.0 // indirect 67 | github.com/mitchellh/go-homedir v1.1.0 // indirect 68 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 69 | github.com/mitchellh/mapstructure v1.5.0 // indirect 70 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 71 | github.com/nxadm/tail v1.4.8 // indirect 72 | github.com/oklog/run v1.1.0 // indirect 73 | github.com/opencontainers/go-digest v1.0.0 // indirect 74 | github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect 75 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect 76 | github.com/pierrec/lz4 v2.6.1+incompatible // indirect 77 | github.com/pkg/errors v0.9.1 // indirect 78 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 79 | github.com/rogpeppe/go-internal v1.12.0 // indirect 80 | github.com/ryanuber/go-glob v1.0.0 // indirect 81 | github.com/sasha-s/go-deadlock v0.2.0 // indirect 82 | github.com/sosodev/duration v1.2.0 // indirect 83 | github.com/stretchr/testify v1.9.0 // indirect 84 | github.com/vektah/gqlparser/v2 v2.5.14 // indirect 85 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect 86 | go.opentelemetry.io/otel v1.27.0 // indirect 87 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect 88 | go.opentelemetry.io/otel/metric v1.27.0 // indirect 89 | go.opentelemetry.io/otel/sdk v1.27.0 // indirect 90 | go.opentelemetry.io/otel/trace v1.27.0 // indirect 91 | go.uber.org/atomic v1.11.0 // indirect 92 | go.uber.org/multierr v1.11.0 // indirect 93 | go.uber.org/zap v1.27.0 // indirect 94 | golang.org/x/crypto v0.32.0 // indirect 95 | golang.org/x/mod v0.17.0 // indirect 96 | golang.org/x/net v0.34.0 // indirect 97 | golang.org/x/oauth2 v0.20.0 // indirect 98 | golang.org/x/sync v0.10.0 // indirect 99 | golang.org/x/sys v0.29.0 // indirect 100 | golang.org/x/text v0.21.0 // indirect 101 | golang.org/x/time v0.5.0 // indirect 102 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 103 | google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect 104 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect 105 | google.golang.org/grpc v1.64.1 // indirect 106 | google.golang.org/protobuf v1.34.1 // indirect 107 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 108 | gopkg.in/ini.v1 v1.67.0 // indirect 109 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 110 | gopkg.in/yaml.v2 v2.4.0 // indirect 111 | gopkg.in/yaml.v3 v3.0.1 // indirect 112 | ) 113 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | pki "github.com/Venafi/vault-pki-backend-venafi/plugin/pki" 5 | "github.com/hashicorp/vault/api" 6 | "github.com/hashicorp/vault/sdk/plugin" 7 | "log" 8 | "os" 9 | ) 10 | 11 | //Plugin config 12 | func main() { 13 | apiClientMeta := &api.PluginAPIClientMeta{} 14 | flags := apiClientMeta.FlagSet() 15 | if err := flags.Parse(os.Args[1:]); err != nil { 16 | log.Fatalf("can not parse command line argumens: %v (%s)", os.Args[1:], err) 17 | } 18 | 19 | tlsConfig := apiClientMeta.GetTLSConfig() 20 | tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig) 21 | 22 | if err := plugin.Serve(&plugin.ServeOpts{ 23 | BackendFactoryFunc: pki.Factory, 24 | TLSProviderFunc: tlsProviderFunc, 25 | }); err != nil { 26 | log.Fatal(err) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plugin/pki/backend.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "sync" 7 | 8 | "github.com/hashicorp/vault/sdk/framework" 9 | "github.com/hashicorp/vault/sdk/logical" 10 | ) 11 | 12 | // Factory creates a new backend implementing the logical.Backend interface 13 | func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) { 14 | b := Backend(conf) 15 | if err := b.Setup(ctx, conf); err != nil { 16 | return nil, err 17 | } 18 | return b, nil 19 | } 20 | 21 | type backend struct { 22 | *framework.Backend 23 | Storage logical.Storage 24 | mux sync.Mutex 25 | } 26 | 27 | // Backend returns a new Backend framework struct 28 | func Backend(conf *logical.BackendConfig) *backend { 29 | var b backend 30 | b.Backend = &framework.Backend{ 31 | Help: strings.TrimSpace(backendHelp), 32 | 33 | PathsSpecial: &logical.Paths{ 34 | SealWrapStorage: []string{ 35 | "roles/", 36 | }, 37 | }, 38 | 39 | Paths: []*framework.Path{ 40 | pathListRoles(&b), 41 | pathRoles(&b), 42 | pathCredentialsList(&b), 43 | pathCredentials(&b), 44 | pathVenafiCertEnroll(&b), 45 | pathVenafiCertSign(&b), 46 | pathVenafiCertRead(&b), 47 | pathVenafiCertRevoke(&b), 48 | pathVenafiFetchListCerts(&b), 49 | }, 50 | 51 | Secrets: []*framework.Secret{ 52 | secretCerts(&b), 53 | }, 54 | 55 | BackendType: logical.TypeLogical, 56 | } 57 | b.Storage = conf.StorageView 58 | return &b 59 | } 60 | 61 | const ( 62 | backendHelp = ` 63 | The Venafi certificates backend plugin requests certificates from TPP of Condor. 64 | 65 | After mounting this backend create a role using role/ path. 66 | ` 67 | utilityName = "HashiCorp Vault" 68 | ) 69 | -------------------------------------------------------------------------------- /plugin/pki/backend_test.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/hashicorp/vault/sdk/logical" 8 | 9 | "github.com/Venafi/vault-pki-backend-venafi/plugin/util" 10 | ) 11 | 12 | func TestFakeRolesConfigurations(t *testing.T) { 13 | integrationTestEnv, err := NewIntegrationTestEnv() 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | 18 | t.Run("create role with no venafi secret", integrationTestEnv.CreateRoleEmptyVenafi) 19 | 20 | } 21 | 22 | func TestFakeVenafiSecretsConfigurations(t *testing.T) { 23 | integrationTestEnv, err := NewIntegrationTestEnv() 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | t.Run("create wrong venafi secret (TPP/Cloud)", integrationTestEnv.CreateVenafiMixedTppAndCloud) 29 | t.Run("delete venafi secret", integrationTestEnv.DeleteVenafi) 30 | 31 | t.Run("create wrong venafi secret (TPP/Token)", integrationTestEnv.CreateVenafiMixedTppAndToken) 32 | t.Run("delete venafi secret", integrationTestEnv.DeleteVenafi) 33 | 34 | t.Run("create wrong venafi secret (Token/Cloud)", integrationTestEnv.CreateVenafiMixedTokenAndCloud) 35 | t.Run("delete venafi secret", integrationTestEnv.DeleteVenafi) 36 | 37 | t.Run("create venafi secret TPP", integrationTestEnv.CreateVenafiTPP) 38 | t.Run("read venafi secret TPP", integrationTestEnv.ReadVenafiTPP) 39 | t.Run("delete venafi secret", integrationTestEnv.DeleteVenafi) 40 | 41 | t.Run("create venafi secret Cloud", integrationTestEnv.CreateVenafiCloud) 42 | t.Run("read venafi secret Cloud", integrationTestEnv.ReadVenafiCloud) 43 | t.Run("delete venafi secret", integrationTestEnv.DeleteVenafi) 44 | 45 | t.Run("create venafi secret TPP Token", integrationTestEnv.CreateVenafiToken) 46 | t.Run("read venafi secret TPP Token", integrationTestEnv.ReadVenafiToken) 47 | t.Run("delete venafi secret", integrationTestEnv.DeleteVenafi) 48 | 49 | t.Run("create venafi secret TPP Token", integrationTestEnv.CreateVenafiTokenWithRefresh) 50 | t.Run("read venafi secret TPP Token", integrationTestEnv.ReadVenafiToken) 51 | t.Run("delete venafi secret", integrationTestEnv.DeleteVenafi) 52 | 53 | t.Run("create venafi secret TPP Token", integrationTestEnv.FailCreateVenafiTokenWithOnlyOneRefresh) 54 | t.Run("delete venafi secret", integrationTestEnv.DeleteVenafi) 55 | 56 | t.Run("create venafi secret TPP Token", integrationTestEnv.FailCreateVenafiTokenWithOnlySecondRefreshSet) 57 | t.Run("delete venafi secret", integrationTestEnv.DeleteVenafi) 58 | } 59 | 60 | // Testing all endpoints with fake vcert CA 61 | func TestFakeEndpoints(t *testing.T) { 62 | integrationTestEnv, err := NewIntegrationTestEnv() 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | 67 | t.Run("fake create venafi secret", integrationTestEnv.FakeCreateVenafi) 68 | t.Run("fake list venafi secrets", integrationTestEnv.FakeListVenafi) 69 | t.Run("fake read venafi secrets", integrationTestEnv.FakeReadVenafi) 70 | t.Run("fake create role", integrationTestEnv.FakeCreateRole) 71 | t.Run("fake list roles", integrationTestEnv.FakeListRole) 72 | t.Run("fake read roles", integrationTestEnv.FakeReadRole) 73 | t.Run("fake issue", integrationTestEnv.FakeIssueCertificateAndSaveSerial) 74 | t.Run("fake list certificates", integrationTestEnv.FakeListCertificate) 75 | t.Run("fake read certificate by serial", integrationTestEnv.FakeReadCertificateBySerial) 76 | t.Run("fake sign", integrationTestEnv.FakeSignCertificate) 77 | 78 | } 79 | 80 | // testing store_by no_store and deprecated store_by_cn and store_by_serial options 81 | func TestFakeStoreByOptions(t *testing.T) { 82 | integrationTestEnv, err := NewIntegrationTestEnv() 83 | if err != nil { 84 | t.Fatal(err) 85 | } 86 | 87 | //test store_by_serial deprecated option 88 | t.Run("create venafi secret", integrationTestEnv.FakeCreateVenafi) 89 | t.Run("create role deprecated store_by_serial", integrationTestEnv.FakeCreateRoleDeprecatedStoreBySerial) 90 | t.Run("issue", integrationTestEnv.FakeIssueCertificateAndSaveSerial) 91 | t.Run("read certificate by serial", integrationTestEnv.FakeReadCertificateBySerial) 92 | t.Run("delete role", integrationTestEnv.DeleteRole) 93 | t.Run("delete venafi", integrationTestEnv.DeleteVenafi) 94 | 95 | //test store_by_cn deprecated option 96 | t.Run("create venafi secret", integrationTestEnv.FakeCreateVenafi) 97 | t.Run("create role deprecated store_by_cn", integrationTestEnv.FakeCreateRoleDeprecatedStoreByCN) 98 | t.Run("issue", integrationTestEnv.FakeIssueCertificateAndSaveSerial) 99 | t.Run("read certificate by cn", integrationTestEnv.FakeReadCertificateByCN) 100 | t.Run("delete role", integrationTestEnv.DeleteRole) 101 | t.Run("delete venafi", integrationTestEnv.DeleteVenafi) 102 | 103 | //test store_by cn 104 | t.Run("create venafi secret", integrationTestEnv.FakeCreateVenafi) 105 | t.Run("create role store_by cn", integrationTestEnv.FakeCreateRoleStoreByCN) 106 | t.Run("issue", integrationTestEnv.FakeIssueCertificateAndSaveSerial) 107 | t.Run("read certificate by cn", integrationTestEnv.FakeReadCertificateByCN) 108 | t.Run("delete role", integrationTestEnv.DeleteRole) 109 | t.Run("delete venafi", integrationTestEnv.DeleteVenafi) 110 | 111 | //test store_by default 112 | t.Run("create venafi secret", integrationTestEnv.FakeCreateVenafi) 113 | t.Run("create role store_by serial", integrationTestEnv.FakeCreateRoleStoreBySerial) 114 | t.Run("issue", integrationTestEnv.FakeIssueCertificateAndSaveSerial) 115 | t.Run("read certificate by serial", integrationTestEnv.FakeReadCertificateBySerial) 116 | t.Run("delete role", integrationTestEnv.DeleteRole) 117 | t.Run("delete venafi", integrationTestEnv.DeleteVenafi) 118 | 119 | //test no_store 120 | t.Run("create venafi secret", integrationTestEnv.FakeCreateVenafi) 121 | t.Run("create role no_store true", integrationTestEnv.FakeCreateRoleNoStore) 122 | t.Run("issue", integrationTestEnv.FakeIssueCertificateAndSaveSerial) 123 | t.Run("check that there is no certificate", integrationTestEnv.FakeCheckThatThereIsNoCertificate) 124 | t.Run("delete role", integrationTestEnv.DeleteRole) 125 | t.Run("delete venafi", integrationTestEnv.DeleteVenafi) 126 | 127 | //test store_pkey false 128 | t.Run("create venafi secret", integrationTestEnv.FakeCreateVenafi) 129 | t.Run("create role store_pkey false", integrationTestEnv.FakeCreateRoleNoStorePKey) 130 | t.Run("issue", integrationTestEnv.FakeIssueCertificateAndSaveSerial) 131 | t.Run("check that there is no private key", integrationTestEnv.FakeCheckThatThereIsNoPKey) 132 | t.Run("delete role", integrationTestEnv.DeleteRole) 133 | t.Run("delete venafi", integrationTestEnv.DeleteVenafi) 134 | 135 | } 136 | 137 | func NewIntegrationTestEnv() (*testEnv, error) { 138 | ctx := context.Background() 139 | 140 | config := logical.TestBackendConfig() 141 | config.StorageView = &logical.InmemStorage{} 142 | 143 | var err error 144 | b := Backend(config) 145 | err = b.Setup(context.Background(), config) 146 | if err != nil { 147 | return nil, err 148 | } 149 | 150 | return &testEnv{ 151 | Backend: b, 152 | Context: ctx, 153 | Storage: config.StorageView, 154 | TestRandString: util.RandRunes(9), 155 | RoleName: util.RandRunes(9) + "-role", 156 | VenafiSecretName: util.RandRunes(9) + "-venafi", 157 | }, nil 158 | } 159 | -------------------------------------------------------------------------------- /plugin/pki/backend_vaas_test.go: -------------------------------------------------------------------------------- 1 | //go:build vaas 2 | // +build vaas 3 | 4 | package pki 5 | 6 | import ( 7 | "fmt" 8 | "sync" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | // **Important** 14 | // All VAAS tests should start with "TestVAAS" as defined in the Makefile 15 | // otherwise they will be ignored 16 | 17 | //Testing Venafi As A Service 18 | func TestVAASintegration(t *testing.T) { 19 | t.Parallel() 20 | integrationTestEnv, err := NewIntegrationTestEnv() 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | t.Run("Cloud base enroll", integrationTestEnv.CloudIntegrationIssueCertificate) 26 | t.Run("Cloud base enroll and verify ttl", integrationTestEnv.CloudIntegrationIssueCertificateAndVerifyTTL) 27 | t.Run("Cloud restricted enroll", integrationTestEnv.CloudIntegrationIssueCertificateRestricted) 28 | t.Run("Cloud issue certificate with password", integrationTestEnv.CloudIntegrationIssueCertificateWithPassword) 29 | t.Run("Cloud sign certificate", integrationTestEnv.CloudIntegrationSignCertificate) 30 | 31 | } 32 | 33 | func TestVAASparallelism(t *testing.T) { 34 | mu := sync.Mutex{} 35 | regDuration := time.Duration(24) * time.Hour 36 | t.Run("execute 20 certificates with same CN", func(t *testing.T) { 37 | integrationTestEnv, err := NewIntegrationTestEnv() 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | data := &testData{minCertTimeLeft: regDuration} 42 | integrationTestEnv.SetupParallelismEnv(t, data, venafiConfigCloud, nil) 43 | count := 20 44 | var certSerials = map[string]string{} 45 | t.Run("executing", func(t *testing.T) { 46 | for i := 1; i <= count; i++ { 47 | index := i 48 | t.Run(fmt.Sprintf("executing cert number: %d", index), func(t *testing.T) { 49 | t.Parallel() 50 | serialNumber := integrationTestEnv.IssueCertificateAndSaveSerialParallelism(t, *data, venafiConfigCloud) 51 | mu.Lock() 52 | //certSerials = append(certSerials, serialNumber) 53 | certSerials[serialNumber] = serialNumber 54 | mu.Unlock() 55 | }) 56 | } 57 | }) 58 | if len(certSerials) != 1 { 59 | t.Fatal("The distinct amount of certificate serials is different that the distinct certificates we requested") 60 | } 61 | }) 62 | 63 | t.Run("execute 50 certificates with some of them having different CN", func(t *testing.T) { 64 | integrationTestEnv, err := NewIntegrationTestEnv() 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | data := &testData{minCertTimeLeft: regDuration} 69 | integrationTestEnv.SetupParallelismEnv(t, data, venafiConfigCloud, nil) 70 | count := 10 71 | countCertNames := 5 72 | var certSerials = map[string]string{} 73 | t.Run("executing", func(t *testing.T) { 74 | for i := 1; i <= countCertNames; i++ { 75 | var dataCertReq *testData 76 | rand := randSeq(9) 77 | domain := "venafi.example.com" 78 | dataCertReq = &testData{ 79 | cn: rand + "." + domain, 80 | } 81 | for j := 1; j <= count; j++ { 82 | index := j 83 | t.Run(fmt.Sprintf("executing cert number: %d and CN: %s", index, (*dataCertReq).cn), func(t *testing.T) { 84 | t.Parallel() 85 | serialNumber := integrationTestEnv.IssueCertificateAndSaveSerialParallelism(t, *dataCertReq, venafiConfigCloud) 86 | mu.Lock() 87 | certSerials[serialNumber] = serialNumber 88 | mu.Unlock() 89 | }) 90 | } 91 | } 92 | }) 93 | // If the amount of distinct serials is different than the amount of certificates names, means we got 94 | if len(certSerials) != countCertNames { 95 | t.Fatal("The distinct amount of certificate serials is different that the distinct certificates we requested") 96 | } 97 | }) 98 | } 99 | 100 | func TestVAASpreventLocalReissuance(t *testing.T) { 101 | t.Parallel() 102 | // regular duration for testing 103 | regDuration := time.Duration(24) * time.Hour 104 | // CASE: should be the SAME - same CN and SAN 105 | t.Run("VaaS enroll same certificate and prevent-reissue", func(t *testing.T) { 106 | integrationTestEnv, err := NewIntegrationTestEnv() 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | data := testData{minCertTimeLeft: regDuration} 111 | integrationTestEnv.PreventReissuanceLocal(t, data, venafiConfigCloud) 112 | }) 113 | // CASE: should be different - same CN but 1 additional SAN 114 | t.Run("VaaS second enroll certificate with extra SAN DNS and should not prevent-reissue", func(t *testing.T) { 115 | integrationTestEnv, err := NewIntegrationTestEnv() 116 | if err != nil { 117 | t.Fatal(err) 118 | } 119 | data := testData{minCertTimeLeft: regDuration} 120 | integrationTestEnv.PreventReissuanceLocalCNwithExtraSANDNS(t, data, venafiConfigCloud) 121 | }) 122 | // CASE: should be different - same CN and missing 1 SAN of 3 123 | t.Run("VaaS second enroll certificate and removing one SAN DNS from list and should not prevent-reissue", func(t *testing.T) { 124 | integrationTestEnv, err := NewIntegrationTestEnv() 125 | if err != nil { 126 | t.Fatal(err) 127 | } 128 | data := testData{minCertTimeLeft: regDuration} 129 | integrationTestEnv.PreventReissuanceLocalCNandRemovingSANDNS(t, data, venafiConfigCloud) 130 | }) 131 | // CASE: should be the SAME - same CN and no SANs 132 | t.Run("VaaS certificate with CN only and no SAN DNS second enroll should be prevented", func(t *testing.T) { 133 | integrationTestEnv, err := NewIntegrationTestEnv() 134 | if err != nil { 135 | t.Fatal(err) 136 | } 137 | data := testData{minCertTimeLeft: regDuration} 138 | integrationTestEnv.PreventReissuanceLocalCNandNoSANSDNS(t, data, venafiConfigCloud) 139 | }) 140 | t.Run("VaaS second enroll same certificate with TTL that is not sufficient for set valid time and should not prevent-reissue", 141 | func(t *testing.T) { 142 | integrationTestEnv, err := NewIntegrationTestEnv() 143 | if err != nil { 144 | t.Fatal(err) 145 | } 146 | data := testData{ 147 | // for VaaS use case we need to set and extra 24 hours since value is truncated (2184hrs = 91 days) 148 | minCertTimeLeft: time.Duration(2184) * time.Hour, 149 | } 150 | integrationTestEnv.PreventReissuanceLocalTTLnotValid(t, data, venafiConfigCloud) 151 | }) 152 | // CASE: should be the SAME - same CN and SAN and just barely sufficiently valid 153 | t.Run("VaaS second enroll same certificate wit TTL with barely sufficient valid time and should prevent-reissue", func(t *testing.T) { 154 | integrationTestEnv, err := NewIntegrationTestEnv() 155 | if err != nil { 156 | t.Fatal(err) 157 | } 158 | data := testData{minCertTimeLeft: time.Duration(2159) * time.Hour} 159 | integrationTestEnv.PreventReissuanceLocalTTLvalid(t, data, venafiConfigCloud) 160 | }) 161 | // CASE: should be the SAME - same CN and same 3 SANs 162 | t.Run("VaaS second enroll certificate with three SAN DNS and should prevent-reissue", func(t *testing.T) { 163 | integrationTestEnv, err := NewIntegrationTestEnv() 164 | if err != nil { 165 | t.Fatal(err) 166 | } 167 | data := testData{minCertTimeLeft: regDuration} 168 | integrationTestEnv.PreventReissuanceLocalCNwithThreeSANDNS(t, data, venafiConfigCloud) 169 | }) 170 | // CASE: should be different - different CN and same 3 SANs 171 | t.Run("VaaS second enroll certificate with three SAN DNS but different CN and should not prevent-reissue", func(t *testing.T) { 172 | integrationTestEnv, err := NewIntegrationTestEnv() 173 | if err != nil { 174 | t.Fatal(err) 175 | } 176 | data := testData{minCertTimeLeft: regDuration} 177 | integrationTestEnv.PreventReissuanceLocalCNwithDifferentCNandThreeSANDNS(t, data, venafiConfigCloud) 178 | }) 179 | // CASE: should be the SAME - no CN and same 3 SANs 180 | t.Run("VaaS second enroll certificate with three SAN DNS but no CN and should prevent-reissue", func(t *testing.T) { 181 | integrationTestEnv, err := NewIntegrationTestEnv() 182 | if err != nil { 183 | t.Fatal(err) 184 | } 185 | data := testData{minCertTimeLeft: regDuration} 186 | integrationTestEnv.PreventReissuanceLocalCNwithNoCNandThreeSANDNS(t, data, venafiConfigCloud) 187 | }) 188 | // Service generated CSR 189 | // CASE: should be the SAME - same CN and SAN 190 | t.Run("Service Generated CSR - VaaS enroll same certificate and prevent-reissue", func(t *testing.T) { 191 | integrationTestEnv, err := NewIntegrationTestEnv() 192 | if err != nil { 193 | t.Fatal(err) 194 | } 195 | data := testData{ 196 | serviceGeneratedCert: true, 197 | minCertTimeLeft: regDuration, 198 | } 199 | integrationTestEnv.PreventReissuanceLocal(t, data, venafiConfigCloud) 200 | }) 201 | // CASE: should be different - same CN but 1 additional SAN 202 | t.Run("Service Generated CSR - VaaS second enroll certificate with extra SAN DNS and should not prevent-reissue", 203 | func(t *testing.T) { 204 | integrationTestEnv, err := NewIntegrationTestEnv() 205 | if err != nil { 206 | t.Fatal(err) 207 | } 208 | data := testData{ 209 | serviceGeneratedCert: true, 210 | minCertTimeLeft: regDuration, 211 | } 212 | integrationTestEnv.PreventReissuanceLocalCNwithExtraSANDNS(t, data, venafiConfigCloud) 213 | }) 214 | // CASE: should be different - same CN and missing 1 SAN of 3 215 | t.Run("Service Generated CSR - VaaS second enroll certificate and removing one SAN DNS from list and should not prevent-reissue", 216 | func(t *testing.T) { 217 | integrationTestEnv, err := NewIntegrationTestEnv() 218 | if err != nil { 219 | t.Fatal(err) 220 | } 221 | data := testData{ 222 | serviceGeneratedCert: true, 223 | minCertTimeLeft: regDuration, 224 | } 225 | integrationTestEnv.PreventReissuanceLocalCNandRemovingSANDNS(t, data, venafiConfigCloud) 226 | }) 227 | // CASE: should be the SAME - same CN and no SANs 228 | t.Run("Service Generated CSR - VaaS certificate with CN only and no SAN DNS second enroll should be prevented", 229 | func(t *testing.T) { 230 | integrationTestEnv, err := NewIntegrationTestEnv() 231 | if err != nil { 232 | t.Fatal(err) 233 | } 234 | data := testData{ 235 | serviceGeneratedCert: true, 236 | minCertTimeLeft: regDuration, 237 | } 238 | integrationTestEnv.PreventReissuanceLocalCNandNoSANSDNS(t, data, venafiConfigCloud) 239 | }) 240 | t.Run("Service Generated CSR - VaaS second enroll same certificate with TTL that is not sufficient for set valid time and should not prevent-reissue", 241 | func(t *testing.T) { 242 | integrationTestEnv, err := NewIntegrationTestEnv() 243 | if err != nil { 244 | t.Fatal(err) 245 | } 246 | data := testData{ 247 | serviceGeneratedCert: true, 248 | // for VaaS use case we need to set and extra 24 hours since value is truncated (2184hrs = 91 days) 249 | minCertTimeLeft: time.Duration(2184) * time.Hour, 250 | } 251 | integrationTestEnv.PreventReissuanceLocalTTLnotValid(t, data, venafiConfigCloud) 252 | }) 253 | // CASE: should be the SAME - same CN and SAN and just barely sufficiently valid 254 | t.Run("Service Generated CSR - VaaS second enroll same certificate wit TTL with barely sufficient valid time and should prevent-reissue", 255 | func(t *testing.T) { 256 | integrationTestEnv, err := NewIntegrationTestEnv() 257 | if err != nil { 258 | t.Fatal(err) 259 | } 260 | data := testData{ 261 | serviceGeneratedCert: true, 262 | minCertTimeLeft: time.Duration(2159) * time.Hour, 263 | } 264 | integrationTestEnv.PreventReissuanceLocalTTLvalid(t, data, venafiConfigCloud) 265 | }) 266 | // CASE: should be the SAME - same CN and same 3 SANs 267 | t.Run("Service Generated CSR - VaaS second enroll certificate with three SAN DNS and should prevent-reissue", 268 | func(t *testing.T) { 269 | integrationTestEnv, err := NewIntegrationTestEnv() 270 | if err != nil { 271 | t.Fatal(err) 272 | } 273 | data := testData{ 274 | serviceGeneratedCert: true, 275 | minCertTimeLeft: regDuration, 276 | } 277 | integrationTestEnv.PreventReissuanceLocalCNwithThreeSANDNS(t, data, venafiConfigCloud) 278 | }) 279 | // CASE: should be different - different CN and same 3 SANs 280 | t.Run("Service Generated CSR - VaaS second enroll certificate with three SAN DNS but different CN and should not prevent-reissue", 281 | func(t *testing.T) { 282 | integrationTestEnv, err := NewIntegrationTestEnv() 283 | if err != nil { 284 | t.Fatal(err) 285 | } 286 | data := testData{ 287 | serviceGeneratedCert: true, 288 | minCertTimeLeft: regDuration, 289 | } 290 | integrationTestEnv.PreventReissuanceLocalCNwithDifferentCNandThreeSANDNS(t, data, venafiConfigCloud) 291 | }) 292 | // CASE: should be the SAME - no CN and same 3 SANs 293 | t.Skip("Currently we skip this scenario as VaaS currently doesn't support it (probable bug)") 294 | t.Run("Service Generated CSR - VaaS second enroll certificate with three SAN DNS but no CN and should prevent-reissue", 295 | func(t *testing.T) { 296 | integrationTestEnv, err := NewIntegrationTestEnv() 297 | if err != nil { 298 | t.Fatal(err) 299 | } 300 | data := testData{ 301 | serviceGeneratedCert: true, 302 | minCertTimeLeft: regDuration, 303 | } 304 | integrationTestEnv.PreventReissuanceLocalCNwithNoCNandThreeSANDNS(t, data, venafiConfigCloud) 305 | }) 306 | 307 | // CASE: should be the SAME - same CN and SAN 308 | // no cert time left set in roll, role's cert minimum time left defaults to 30 days, should still be valid on next validation 309 | t.Run("TPP Token enroll same certificate and prevent-reissue locally - no min time specified", func(t *testing.T) { 310 | integrationTestEnv, err := NewIntegrationTestEnv() 311 | if err != nil { 312 | t.Fatal(err) 313 | } 314 | data := testData{ 315 | ignoreLocalStorage: false, 316 | } 317 | integrationTestEnv.PreventReissuanceLocal(t, data, venafiConfigToken) 318 | }) 319 | 320 | // CASE: should be the SAME - same CN and SAN 321 | // turn off prevent-reissue-local, we specify certificate's minimum time left but still should not prevent reissue 322 | t.Run("TPP Token enroll same certificate and should not prevent-reissue locally - cache turned off - min time specified", func(t *testing.T) { 323 | integrationTestEnv, err := NewIntegrationTestEnv() 324 | if err != nil { 325 | t.Fatal(err) 326 | } 327 | data := testData{ 328 | ignoreLocalStorage: true, 329 | minCertTimeLeft: regDuration, 330 | } 331 | integrationTestEnv.NotPreventReissuanceLocal(t, data, venafiConfigToken) 332 | }) 333 | } 334 | 335 | func TestVAASpreventReissuance(t *testing.T) { 336 | t.Parallel() 337 | // regular duration for testing 338 | regDuration := time.Duration(24) * time.Hour 339 | // CASE: should be the SAME - same CN and SAN 340 | t.Run("VaaS enroll same certificate and prevent-reissue", func(t *testing.T) { 341 | integrationTestEnv, err := NewIntegrationTestEnv() 342 | if err != nil { 343 | t.Fatal(err) 344 | } 345 | data := testData{ 346 | minCertTimeLeft: regDuration, 347 | } 348 | integrationTestEnv.PreventReissuance(t, data, venafiConfigCloud) 349 | }) 350 | // CASE: should be different - same CN but 1 additional SAN 351 | t.Run("VaaS second enroll certificate with extra SAN DNS and should not prevent-reissue", func(t *testing.T) { 352 | integrationTestEnv, err := NewIntegrationTestEnv() 353 | if err != nil { 354 | t.Fatal(err) 355 | } 356 | data := testData{ 357 | minCertTimeLeft: regDuration, 358 | } 359 | integrationTestEnv.PreventReissuanceCNwithExtraSANDNS(t, data, venafiConfigCloud) 360 | }) 361 | // CASE: should be different - same CN and missing 1 SAN of 3 362 | t.Run("VaaS second enroll certificate and removing one SAN DNS from list and should not prevent-reissue", func(t *testing.T) { 363 | integrationTestEnv, err := NewIntegrationTestEnv() 364 | if err != nil { 365 | t.Fatal(err) 366 | } 367 | data := testData{ 368 | minCertTimeLeft: regDuration, 369 | } 370 | integrationTestEnv.PreventReissuanceCNandRemovingSANDNS(t, data, venafiConfigCloud) 371 | }) 372 | // CASE: should be the SAME - same CN and no SANs 373 | t.Run("VaaS certificate with CN only and no SAN DNS second enroll should be prevented", func(t *testing.T) { 374 | integrationTestEnv, err := NewIntegrationTestEnv() 375 | if err != nil { 376 | t.Fatal(err) 377 | } 378 | data := testData{ 379 | minCertTimeLeft: regDuration, 380 | } 381 | integrationTestEnv.PreventReissuanceCNnoSANSDNS(t, data, venafiConfigCloud) 382 | }) 383 | t.Run("VaaS second enroll same certificate with TTL that is not sufficient for set valid time and should not prevent-reissue", 384 | func(t *testing.T) { 385 | integrationTestEnv, err := NewIntegrationTestEnv() 386 | if err != nil { 387 | t.Fatal(err) 388 | } 389 | data := testData{ 390 | // for VaaS use case we need to set and extra 24 hours since value is truncated (2184hrs = 91 days) 391 | minCertTimeLeft: time.Duration(2184) * time.Hour, 392 | } 393 | integrationTestEnv.PreventReissuanceTTLnotValid(t, data, venafiConfigCloud) 394 | }) 395 | // CASE: should be the SAME - same CN and SAN and just barely sufficiently valid 396 | t.Run("VaaS second enroll same certificate wit TTL with barely sufficient valid time and should prevent-reissue", func(t *testing.T) { 397 | integrationTestEnv, err := NewIntegrationTestEnv() 398 | if err != nil { 399 | t.Fatal(err) 400 | } 401 | data := testData{ 402 | minCertTimeLeft: time.Duration(2159) * time.Hour, 403 | } 404 | integrationTestEnv.PreventReissuanceTTLvalid(t, data, venafiConfigCloud) 405 | }) 406 | // CASE: should be the SAME - same CN and same 3 SANs 407 | t.Run("VaaS second enroll certificate with three SAN DNS and should prevent-reissue", func(t *testing.T) { 408 | integrationTestEnv, err := NewIntegrationTestEnv() 409 | if err != nil { 410 | t.Fatal(err) 411 | } 412 | data := testData{ 413 | minCertTimeLeft: regDuration, 414 | } 415 | integrationTestEnv.PreventReissuanceCNwithThreeSANDNS(t, data, venafiConfigCloud) 416 | }) 417 | // CASE: should be different - different CN and same 3 SANs 418 | t.Run("VaaS second enroll certificate with three SAN DNS but different CN and should not prevent-reissue", func(t *testing.T) { 419 | integrationTestEnv, err := NewIntegrationTestEnv() 420 | if err != nil { 421 | t.Fatal(err) 422 | } 423 | data := testData{ 424 | minCertTimeLeft: regDuration, 425 | } 426 | integrationTestEnv.PreventReissuanceCNwithDifferentCNandThreeSANDNS(t, data, venafiConfigCloud) 427 | }) 428 | // CASE: should be the SAME - no CN and same 3 SANs 429 | t.Run("VaaS second enroll certificate with three SAN DNS but no CN and should prevent-reissue", func(t *testing.T) { 430 | integrationTestEnv, err := NewIntegrationTestEnv() 431 | if err != nil { 432 | t.Fatal(err) 433 | } 434 | data := testData{ 435 | minCertTimeLeft: regDuration, 436 | } 437 | integrationTestEnv.PreventReissuanceCNwithNoCNandThreeSANDNS(t, data, venafiConfigCloud) 438 | }) 439 | // Service generated CSR 440 | // CASE: should be the SAME - same CN and SAN 441 | t.Run("Service Generated CSR - VaaS enroll same certificate and prevent-reissue", func(t *testing.T) { 442 | integrationTestEnv, err := NewIntegrationTestEnv() 443 | if err != nil { 444 | t.Fatal(err) 445 | } 446 | data := testData{ 447 | serviceGeneratedCert: true, 448 | minCertTimeLeft: regDuration, 449 | } 450 | integrationTestEnv.PreventReissuance(t, data, venafiConfigCloud) 451 | }) 452 | // CASE: should be different - same CN but 1 additional SAN 453 | t.Run("Service Generated CSR - VaaS second enroll certificate with extra SAN DNS and should not prevent-reissue", 454 | func(t *testing.T) { 455 | integrationTestEnv, err := NewIntegrationTestEnv() 456 | if err != nil { 457 | t.Fatal(err) 458 | } 459 | data := testData{ 460 | serviceGeneratedCert: true, 461 | minCertTimeLeft: regDuration, 462 | } 463 | integrationTestEnv.PreventReissuanceCNwithExtraSANDNS(t, data, venafiConfigCloud) 464 | }) 465 | // CASE: should be different - same CN and missing 1 SAN of 3 466 | t.Run("Service Generated CSR - VaaS second enroll certificate and removing one SAN DNS from list and should not prevent-reissue", 467 | func(t *testing.T) { 468 | integrationTestEnv, err := NewIntegrationTestEnv() 469 | if err != nil { 470 | t.Fatal(err) 471 | } 472 | data := testData{ 473 | serviceGeneratedCert: true, 474 | minCertTimeLeft: regDuration, 475 | } 476 | integrationTestEnv.PreventReissuanceCNandRemovingSANDNS(t, data, venafiConfigCloud) 477 | }) 478 | // CASE: should be the SAME - same CN and no SANs 479 | t.Run("Service Generated CSR - VaaS certificate with CN only and no SAN DNS second enroll should be prevented", 480 | func(t *testing.T) { 481 | integrationTestEnv, err := NewIntegrationTestEnv() 482 | if err != nil { 483 | t.Fatal(err) 484 | } 485 | data := testData{ 486 | serviceGeneratedCert: true, 487 | minCertTimeLeft: regDuration, 488 | } 489 | integrationTestEnv.PreventReissuanceCNnoSANSDNS(t, data, venafiConfigCloud) 490 | }) 491 | t.Run("Service Generated CSR - VaaS second enroll same certificate with TTL that is not sufficient for set valid time and should not prevent-reissue", 492 | func(t *testing.T) { 493 | integrationTestEnv, err := NewIntegrationTestEnv() 494 | if err != nil { 495 | t.Fatal(err) 496 | } 497 | data := testData{ 498 | serviceGeneratedCert: true, 499 | // for VaaS use case we need to set and extra 24 hours since value is truncated (2184hrs = 91 days) 500 | minCertTimeLeft: time.Duration(2184) * time.Hour, 501 | } 502 | integrationTestEnv.PreventReissuanceTTLnotValid(t, data, venafiConfigCloud) 503 | }) 504 | // CASE: should be the SAME - same CN and SAN and just barely sufficiently valid 505 | t.Run("Service Generated CSR - VaaS second enroll same certificate wit TTL with barely sufficient valid time and should prevent-reissue", 506 | func(t *testing.T) { 507 | integrationTestEnv, err := NewIntegrationTestEnv() 508 | if err != nil { 509 | t.Fatal(err) 510 | } 511 | data := testData{ 512 | serviceGeneratedCert: true, 513 | minCertTimeLeft: time.Duration(2159) * time.Hour, 514 | } 515 | integrationTestEnv.PreventReissuanceTTLvalid(t, data, venafiConfigCloud) 516 | }) 517 | // CASE: should be the SAME - same CN and same 3 SANs 518 | t.Run("Service Generated CSR - VaaS second enroll certificate with three SAN DNS and should prevent-reissue", 519 | func(t *testing.T) { 520 | integrationTestEnv, err := NewIntegrationTestEnv() 521 | if err != nil { 522 | t.Fatal(err) 523 | } 524 | data := testData{ 525 | serviceGeneratedCert: true, 526 | minCertTimeLeft: regDuration, 527 | } 528 | integrationTestEnv.PreventReissuanceCNwithThreeSANDNS(t, data, venafiConfigCloud) 529 | }) 530 | // CASE: should be different - different CN and same 3 SANs 531 | t.Run("Service Generated CSR - VaaS second enroll certificate with three SAN DNS but different CN and should not prevent-reissue", 532 | func(t *testing.T) { 533 | integrationTestEnv, err := NewIntegrationTestEnv() 534 | if err != nil { 535 | t.Fatal(err) 536 | } 537 | data := testData{ 538 | serviceGeneratedCert: true, 539 | minCertTimeLeft: regDuration, 540 | } 541 | integrationTestEnv.PreventReissuanceCNwithDifferentCNandThreeSANDNS(t, data, venafiConfigCloud) 542 | }) 543 | // CASE: should be the SAME - no CN and same 3 SANs 544 | t.Skip("Currently we skip this scenario as VaaS currently doesn't support it (probable bug)") 545 | t.Run("Service Generated CSR - VaaS second enroll certificate with three SAN DNS but no CN and should prevent-reissue", 546 | func(t *testing.T) { 547 | integrationTestEnv, err := NewIntegrationTestEnv() 548 | if err != nil { 549 | t.Fatal(err) 550 | } 551 | data := testData{ 552 | serviceGeneratedCert: true, 553 | minCertTimeLeft: regDuration, 554 | } 555 | integrationTestEnv.PreventReissuanceCNwithNoCNandThreeSANDNS(t, data, venafiConfigCloud) 556 | }) 557 | } 558 | 559 | func TestVAASnegativeTest(t *testing.T) { 560 | t.Parallel() 561 | t.Run("test error with wrong credentials", func(t *testing.T) { 562 | integrationTestEnv, err := NewIntegrationTestEnv() 563 | if err != nil { 564 | t.Fatal(err) 565 | } 566 | t.Run("VAAS base negative enroll", integrationTestEnv.VAASnegativeIssueCertificate) 567 | }) 568 | } 569 | -------------------------------------------------------------------------------- /plugin/pki/e2e/path_venafi_cert_e2e.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "crypto/x509" 5 | "encoding/json" 6 | "encoding/pem" 7 | "fmt" 8 | "strings" 9 | 10 | "github.com/Venafi/vault-pki-backend-venafi/plugin/pki" 11 | "github.com/Venafi/vault-pki-backend-venafi/plugin/util" 12 | "github.com/Venafi/vcert/v5/test" 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | ) 16 | 17 | const ( 18 | vaultContainerName = "vault-pki-backend-venafi_vault_1" 19 | ) 20 | 21 | var _ = Describe("Vault PKI Venafi backend e2e tests ", func() { 22 | var ( 23 | cmd string 24 | out string 25 | err string 26 | code int 27 | ) 28 | 29 | type endpointConnectionId int 30 | 31 | const ( 32 | tpp endpointConnectionId = iota 33 | cloud 34 | fake 35 | tppToken 36 | roleOpts = "venafi_secret=%s %s" 37 | ) 38 | 39 | type endpointConnection struct { 40 | id endpointConnectionId 41 | name string 42 | roleOpt string 43 | enabled bool 44 | issuerCN string 45 | venafiOpt string 46 | } 47 | 48 | ctx := pki.GetContext() 49 | 50 | defaultOpts := "generate_lease=true store_by_cn=true store_pkey=true store_by_serial=true" 51 | 52 | var endpoints = []endpointConnection{ 53 | {fake, 54 | "fake", 55 | fmt.Sprintf(roleOpts, "fakeVenafi", defaultOpts), 56 | ctx.FakeTestingEnabled, 57 | ctx.FakeIssuerCN, 58 | "fakemode=true", 59 | }, 60 | {tpp, 61 | "tpp", 62 | fmt.Sprintf(roleOpts, "tppVenafi", defaultOpts), 63 | ctx.TPPTestingEnabled, 64 | ctx.TPPIssuerCN, 65 | fmt.Sprintf("tpp_url=%s tpp_user=%s tpp_password=%s zone=%s trust_bundle_file=/opt/venafi/bundle.pem", ctx.TPPurl, ctx.TPPuser, ctx.TPPPassword, ctx.TPPZone), 66 | }, 67 | {cloud, 68 | "cloud", 69 | fmt.Sprintf(roleOpts, "cloudVenafi", defaultOpts), 70 | ctx.CloudTestingEnabled, 71 | ctx.CloudIssuerCN, 72 | fmt.Sprintf("cloud_url=%s zone=%s apikey=%s", ctx.CloudUrl, ctx.CloudZone, ctx.CloudAPIkey), 73 | }, 74 | {tppToken, 75 | "tpp_token", 76 | fmt.Sprintf(roleOpts, "tokenVenafi", defaultOpts), 77 | ctx.TPPTestingEnabled, 78 | ctx.TPPIssuerCN, 79 | fmt.Sprintf("url=%s access_token=%s zone=%s trust_bundle_file=/opt/venafi/bundle.pem", ctx.TokenUrl, ctx.AccessToken, ctx.TPPZone), 80 | }, 81 | } 82 | 83 | Describe("Checking vault status ", func() { 84 | 85 | Describe("Vault status", func() { 86 | It("should return that Vault is unseald", func() { 87 | cmd = fmt.Sprintf("docker exec %s vault status", vaultContainerName) 88 | By("running " + cmd) 89 | out, err, code := testRun(cmd) 90 | Expect(code).To(BeZero()) 91 | Expect(out).To(ContainSubstring("Sealed false")) 92 | Expect(err).To(BeEmpty()) 93 | }) 94 | }) 95 | }) 96 | 97 | Describe("Enrolling certificates for test endpoints", func() { 98 | Describe("with defaults", func() { 99 | for _, endpoint := range endpoints { 100 | if !endpoint.enabled { 101 | continue 102 | } 103 | Context("with "+endpoint.name, func() { 104 | It("Writing venafi secret configuration", func() { 105 | cmd = fmt.Sprintf(`docker exec %s vault write venafi-pki/venafi/%s `+endpoint.venafiOpt, vaultContainerName, endpoint.name+"Venafi") 106 | By("Running " + cmd) 107 | out, err, code = testRun(cmd) 108 | Expect(code).To(BeZero()) 109 | Expect(out).To(MatchRegexp("Success! Data written to: venafi-pki/venafi/" + endpoint.name + "Venafi")) 110 | }) 111 | It("Writing role configuration", func() { 112 | cmd = fmt.Sprintf( 113 | `docker exec %s vault write venafi-pki/roles/%s 114 | `+endpoint.roleOpt, 115 | vaultContainerName, endpoint.name) 116 | By("Running " + cmd) 117 | out, err, code = testRun(cmd) 118 | Expect(code).To(BeZero()) 119 | Expect(out).To(MatchRegexp("Success! Data written to: venafi-pki/roles/" + endpoint.name)) 120 | }) 121 | cn := test.RandCN() 122 | It("Enrolling certificate for "+endpoint.name, func() { 123 | dns1 := "alt-" + test.RandCN() 124 | dns2 := "alt-" + test.RandCN() 125 | cmd = fmt.Sprintf( 126 | `docker exec %s vault write venafi-pki/issue/%s common_name=%s alt_names=%s,%s -format=json`, 127 | vaultContainerName, endpoint.name, cn, dns1, dns2) 128 | 129 | By("Should run " + cmd) 130 | out, err, code = testRun(cmd) 131 | Expect(code).To(BeZero()) 132 | Expect(out).To(ContainSubstring("----BEGIN CERTIFICATE-----")) 133 | Expect(err).To(BeEmpty()) 134 | 135 | By("Should return valid JSON") 136 | cert := vaultJSONCertificate{} 137 | response := json.Unmarshal([]byte(out), &cert) 138 | Expect(response).To(BeZero()) 139 | 140 | By("Should be valid certificate") 141 | certificate := strings.Join([]string{cert.Data.Certificate}, "\n") 142 | pemBlock, _ := pem.Decode([]byte(certificate)) 143 | parsedCertificate, parseErr := x509.ParseCertificate(pemBlock.Bytes) 144 | Expect(parseErr).To(BeZero()) 145 | 146 | haveCN := parsedCertificate.Subject.CommonName 147 | By("Should have requested CN " + cn + " equal to " + haveCN) 148 | Expect(haveCN).To(Equal(cn)) 149 | 150 | //Skip DNS check for cloud since int not implemented in Condor. 151 | if endpoint.id == tpp { 152 | wantDNSNames := []string{cn, dns1, dns2} 153 | haveDNSNames := parsedCertificate.DNSNames 154 | By("Should have requested SANs and CN in DNSNames " + strings.Join(wantDNSNames, " ") + " same as " + strings.Join(haveDNSNames, " ")) 155 | Expect(util.SameStringSlice(haveDNSNames, wantDNSNames)).To(BeTrue()) 156 | } 157 | 158 | By("Should have valid issuer CN") 159 | haveIssuerCN := parsedCertificate.Issuer.CommonName 160 | Expect(haveIssuerCN).To(Equal(endpoint.issuerCN)) 161 | 162 | }) 163 | It("Fetching "+endpoint.name+" endpoint certificate with CN "+cn, func() { 164 | By("Should be listed in certificates list") 165 | cmd = fmt.Sprintf(`docker exec %s vault list venafi-pki/certs`, vaultContainerName) 166 | out, err, code = testRun(cmd) 167 | Expect(code).To(BeZero()) 168 | Expect(out).To(MatchRegexp(cn)) 169 | 170 | By("Should return valid JSON") 171 | cmd = fmt.Sprintf(`docker exec %s vault read -format=json venafi-pki/cert/%s`, vaultContainerName, cn) 172 | fmt.Println(cmd) 173 | out, err, code = testRun(cmd) 174 | cert := vaultJSONCertificate{} 175 | response := json.Unmarshal([]byte(out), &cert) 176 | Expect(response).To(BeZero()) 177 | 178 | By("Should be valid certificate") 179 | certificate := strings.Join([]string{cert.Data.Certificate}, "\n") 180 | pemBlock, _ := pem.Decode([]byte(certificate)) 181 | parsedCertificate, parseErr := x509.ParseCertificate(pemBlock.Bytes) 182 | Expect(parseErr).To(BeZero()) 183 | 184 | By("Should have requested CN") 185 | haveCN := parsedCertificate.Subject.CommonName 186 | Expect(haveCN).To(Equal(cn)) 187 | }) 188 | 189 | }) 190 | } 191 | }) 192 | }) 193 | 194 | }) 195 | -------------------------------------------------------------------------------- /plugin/pki/e2e/path_venafi_cert_e2e_test.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "testing" 7 | 8 | "bufio" 9 | "fmt" 10 | . "github.com/onsi/ginkgo" 11 | "github.com/onsi/ginkgo/reporters" 12 | . "github.com/onsi/gomega" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | func init() { 18 | fmt.Println("Initilazing Vault with consul backend") 19 | commandWithArgs("docker-compose", "down", "--remove-orphans") 20 | commandWithArgs("docker", "images") 21 | commandWithArgs("docker-compose", "up", "-d") 22 | time.Sleep(20) 23 | 24 | init := run(fmt.Sprintf("docker exec %s vault operator init -key-shares=1 -key-threshold=1", vaultContainerName)) 25 | 26 | scanner := bufio.NewScanner(strings.NewReader(string(init))) 27 | var keyLine string 28 | var tokenLine string 29 | var s []string 30 | 31 | for scanner.Scan() { 32 | if strings.Contains(scanner.Text(), "Unseal Key") { 33 | keyLine = scanner.Text() 34 | } 35 | if strings.Contains(scanner.Text(), "Initial Root Token") { 36 | tokenLine = scanner.Text() 37 | } 38 | } 39 | 40 | s = strings.Split(keyLine, " ") 41 | key := s[3] 42 | s = strings.Split(tokenLine, " ") 43 | token := s[3] 44 | 45 | unseal := run(fmt.Sprintf("docker exec %s vault operator unseal %s", vaultContainerName, key)) 46 | fmt.Println(unseal) 47 | 48 | auth := run(fmt.Sprintf("docker exec %s vault login %s", vaultContainerName, token)) 49 | fmt.Println(auth) 50 | 51 | s = strings.Split(run(fmt.Sprintf("docker exec %s sha256sum /vault_plugin/venafi-pki-backend", vaultContainerName)), " ") 52 | sha256 := s[0] 53 | fmt.Println("sha256 is:", sha256) 54 | 55 | write_pki := run(fmt.Sprintf("docker exec %s vault write sys/plugins/catalog/venafi-pki-backend sha_256=%s command=venafi-pki-backend", vaultContainerName, sha256)) 56 | fmt.Println(write_pki) 57 | enable_pki := run(fmt.Sprintf("docker exec %s vault secrets enable -path=venafi-pki -plugin-name=venafi-pki-backend plugin", vaultContainerName)) 58 | fmt.Println(enable_pki) 59 | } 60 | 61 | func TestPki(t *testing.T) { 62 | RegisterFailHandler(Fail) 63 | junitReporter := reporters.NewJUnitReporter("junit_00.xml") 64 | RunSpecsWithDefaultAndCustomReporters(t, "Integration tests for Venafi Vault PKI backend", []Reporter{junitReporter}) 65 | } 66 | 67 | func run(command string) string { 68 | var err error 69 | cmd := splitAndFlat(command) 70 | fmt.Println("Running: ", cmd) 71 | out, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput() 72 | if err != nil { 73 | fmt.Println(string(out)) 74 | panic(err) 75 | } 76 | return string(out) 77 | } 78 | 79 | func commandWithArgs(run string, extraArgs ...string) { 80 | args := []string{} 81 | args = append(args, extraArgs...) 82 | cmd := exec.Command(run, args...) 83 | cmd.Stdout = os.Stdout 84 | cmd.Stderr = os.Stderr 85 | err := cmd.Run() 86 | if err != nil { 87 | panic(err) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /plugin/pki/e2e/util.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "fmt" 5 | . "github.com/Venafi/vault-pki-backend-venafi/plugin/pki" 6 | . "github.com/onsi/ginkgo" 7 | "github.com/rendon/testcli" 8 | "strings" 9 | ) 10 | 11 | func testRunCmd(some ...string) (out, err string, exitCode int) { 12 | cmd := splitAndFlat(some) 13 | testcli.Run(cmd[0], cmd[1:]...) 14 | if testcli.Failure() { 15 | exitCode = 1 16 | } 17 | return testcli.Stdout(), testcli.Stderr(), exitCode 18 | } 19 | 20 | type vaultJSONCertificate struct { 21 | Data VenafiCert 22 | } 23 | 24 | func splitAndFlat(parts ...interface{}) (ret []string) { 25 | for _, part := range parts { 26 | switch part := part.(type) { 27 | case string: 28 | ret = append(ret, strings.Fields(part)...) 29 | case []string: 30 | for _, s := range part { 31 | ret = append(ret, strings.Fields(s)...) 32 | } 33 | default: 34 | fmt.Printf("DEFAULT: %T\n", part) 35 | } 36 | } 37 | return 38 | } 39 | 40 | func testRun(cmd string) (out, err string, exitCode int) { 41 | out, err, exitCode = testRunCmd(cmd) 42 | fmt.Fprintf(GinkgoWriter, 43 | "===CMD: %s\n===OUT:%s\n===ERR:%s\n===EXIT(%d)", 44 | strings.Fields(cmd), out, err, exitCode) 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /plugin/pki/path_roles.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/hashicorp/vault/sdk/framework" 9 | "github.com/hashicorp/vault/sdk/logical" 10 | 11 | "github.com/Venafi/vault-pki-backend-venafi/plugin/util" 12 | ) 13 | 14 | func pathListRoles(b *backend) *framework.Path { 15 | return &framework.Path{ 16 | Pattern: "roles/?$", 17 | 18 | Callbacks: map[logical.Operation]framework.OperationFunc{ 19 | logical.ListOperation: b.pathRoleList, 20 | }, 21 | 22 | HelpSynopsis: pathListRolesHelpSyn, 23 | HelpDescription: pathListRolesHelpDesc, 24 | } 25 | } 26 | 27 | func pathRoles(b *backend) *framework.Path { 28 | return &framework.Path{ 29 | Pattern: "roles/" + framework.GenericNameRegex("name"), 30 | Fields: map[string]*framework.FieldSchema{ 31 | "name": { 32 | Type: framework.TypeString, 33 | Description: "Name of the role", 34 | Required: true, 35 | }, 36 | "zone": { 37 | Type: framework.TypeString, 38 | Description: `Name of Venafi Platform policy or Venafi Cloud project zone. 39 | This field overrides the zone field declared in the Venafi secret. 40 | Example for Platform: testpolicy\\vault 41 | Example for Venafi Cloud: e33f3e40-4e7e-11ea-8da3-b3c196ebeb0b`, 42 | Required: false, 43 | }, 44 | "store_by_cn": { 45 | Type: framework.TypeBool, 46 | Description: `Set it to true to store certificates by CN in certs/ path`, 47 | Deprecated: true, 48 | }, 49 | 50 | "store_by_serial": { 51 | Type: framework.TypeBool, 52 | Description: `Set it to true to store certificates by unique serial number in certs/ path`, 53 | Deprecated: true, 54 | }, 55 | 56 | "store_by": { 57 | Type: framework.TypeString, 58 | Description: `The attribute by which certificates are stored in the backend. "serial" (default), "cn" and "hash" are the only valid values.`, 59 | }, 60 | 61 | "no_store": { 62 | Type: framework.TypeBool, 63 | Description: `If set, certificates issued/signed against this role will not be stored in the storage backend.`, 64 | }, 65 | 66 | "service_generated_cert": { 67 | Type: framework.TypeBool, 68 | Description: `Have Trust Protection Platform or Venafi as a Service generate keys and CSRs`, 69 | Default: false, 70 | }, 71 | "store_pkey": { 72 | Type: framework.TypeBool, 73 | Description: `Set it to true to store certificates privates key in certificate fields`, 74 | }, 75 | "chain_option": { 76 | Type: framework.TypeString, 77 | Description: `Specify ordering certificates in chain. Root can be "first" or "last"`, 78 | Default: "last", 79 | }, 80 | "key_type": { 81 | Type: framework.TypeString, 82 | Default: "rsa", 83 | Description: `The type of key to use; defaults to RSA. "rsa" 84 | and "ec" (ECDSA) are the only valid values.`, 85 | }, 86 | "key_bits": { 87 | Type: framework.TypeInt, 88 | Default: 2048, 89 | Description: `The number of bits to use. You will almost 90 | certainly want to change this if you adjust 91 | the key_type. Default: 2048`, 92 | }, 93 | "key_curve": { 94 | Type: framework.TypeString, 95 | Default: "P256", 96 | Description: `Key curve for EC key type. Valid values are: "P256","P384","P521"`, 97 | }, 98 | "ttl": { 99 | Type: framework.TypeDurationSecond, 100 | Description: `The certificate validity if no specific certificate validity is requested.`, 101 | }, 102 | "issuer_hint": { 103 | Type: framework.TypeString, 104 | Description: `Indicate the target issuer to enable ttl with Venafi Platform; "DigiCert", "Entrust", and "Microsoft" are supported values.`, 105 | }, 106 | "max_ttl": { 107 | Type: framework.TypeDurationSecond, 108 | Description: "The maximum allowed certificate validity", 109 | }, 110 | 111 | "generate_lease": { 112 | Type: framework.TypeBool, 113 | Description: ` 114 | If set, certificates issued/signed against this role will have Vault leases 115 | attached to them. Defaults to "false".`, 116 | }, 117 | "server_timeout": { 118 | Type: framework.TypeDurationSecond, 119 | Description: "Timeout of waiting certificate (seconds)", 120 | Default: 180, 121 | }, 122 | "venafi_secret": { 123 | Type: framework.TypeString, 124 | Description: `The name of the credentials object to be used for authentication`, 125 | Required: true, 126 | }, 127 | "update_if_exist": { 128 | Type: framework.TypeBool, 129 | Description: `When true, settings of an existing role will be retained unless they are specified in the update. 130 | By default unspecified settings are returned to their default values`, 131 | }, 132 | "min_cert_time_left": { 133 | Type: framework.TypeDurationSecond, 134 | Description: `When set, is used to determinate if certificate issuance is needed comparing certificate validity against desired remaining validity`, 135 | Default: time.Duration(30*24) * time.Hour, 136 | }, 137 | "ignore_local_storage": { 138 | Type: framework.TypeBool, 139 | Description: `When true, bypasses prevent re-issue logic to issue new certificate'`, 140 | Default: false, 141 | }, 142 | }, 143 | 144 | Operations: map[logical.Operation]framework.OperationHandler{ 145 | logical.ReadOperation: &framework.PathOperation{ 146 | Callback: b.pathRoleRead, 147 | Summary: "Read the properties of a role and displays it to the user.", 148 | }, 149 | logical.UpdateOperation: &framework.PathOperation{ 150 | Callback: b.pathRoleCreate, 151 | Summary: "Create a role if not exist and updates it if exists", 152 | }, 153 | logical.DeleteOperation: &framework.PathOperation{ 154 | Callback: b.pathRoleDelete, 155 | Summary: "Delete a role", 156 | }, 157 | }, 158 | 159 | HelpSynopsis: pathRoleHelpSyn, 160 | HelpDescription: pathRoleHelpDesc, 161 | } 162 | } 163 | 164 | func (b *backend) getRole(ctx context.Context, s logical.Storage, n string) (*roleEntry, error) { 165 | entry, err := s.Get(ctx, "role/"+n) 166 | if err != nil { 167 | return nil, err 168 | } 169 | if entry == nil { 170 | return nil, nil 171 | } 172 | 173 | var result roleEntry 174 | if err := entry.DecodeJSON(&result); err != nil { 175 | return nil, err 176 | } 177 | 178 | return &result, nil 179 | } 180 | 181 | func (b *backend) pathRoleDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 182 | err := req.Storage.Delete(ctx, "role/"+data.Get("name").(string)) 183 | if err != nil { 184 | return nil, err 185 | } 186 | 187 | return nil, nil 188 | } 189 | 190 | func (b *backend) pathRoleRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 191 | roleName := data.Get("name").(string) 192 | if roleName == "" { 193 | return logical.ErrorResponse("missing role name"), nil 194 | } 195 | 196 | role, err := b.getRole(ctx, req.Storage, roleName) 197 | if err != nil { 198 | return nil, err 199 | } 200 | if role == nil { 201 | return nil, nil 202 | } 203 | 204 | resp := &logical.Response{ 205 | Data: role.ToResponseData(), 206 | } 207 | return resp, nil 208 | } 209 | 210 | func (b *backend) pathRoleList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 211 | entries, err := req.Storage.List(ctx, "role/") 212 | if err != nil { 213 | return nil, err 214 | } 215 | 216 | return logical.ListResponse(entries), nil 217 | } 218 | 219 | func (b *backend) pathRoleUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*roleEntry, error) { 220 | name := data.Get("name").(string) 221 | entry, err := b.getRole(ctx, req.Storage, name) 222 | 223 | if err != nil { 224 | return nil, err 225 | } 226 | if entry == nil { 227 | return nil, fmt.Errorf("role %s does not exist", name) 228 | } 229 | 230 | _, isSet := data.GetOk("chain_option") 231 | chainOption := data.Get("chain_option").(string) 232 | if isSet && (entry.ChainOption != chainOption) { 233 | entry.ChainOption = chainOption 234 | } 235 | 236 | _, isSet = data.GetOk("store_by_cn") 237 | storeByCn := data.Get("store_by_cn").(bool) 238 | if isSet && storeByCn { 239 | entry.StoreBy = "cn" 240 | } 241 | 242 | _, isSet = data.GetOk("store_by_serial") 243 | storeBySerial := data.Get("store_by_serial").(bool) 244 | if isSet && storeBySerial { 245 | entry.StoreBy = "serial" 246 | } 247 | 248 | _, isSet = data.GetOk("store_by") 249 | storeBy := data.Get("store_by").(string) 250 | if isSet && (entry.StoreBy != storeBy) { 251 | entry.StoreBy = storeBy 252 | } 253 | 254 | _, isSet = data.GetOk("no_store") 255 | noStore := data.Get("no_store").(bool) 256 | if isSet && (entry.NoStore != noStore) { 257 | entry.NoStore = noStore 258 | } 259 | 260 | _, isSet = data.GetOk("service_generated_cert") 261 | serviceGeneratedCert := data.Get("service_generated_cert").(bool) 262 | if isSet && (entry.ServiceGenerated != serviceGeneratedCert) { 263 | entry.ServiceGenerated = serviceGeneratedCert 264 | } 265 | 266 | _, isSet = data.GetOk("store_pkey") 267 | storePkey := data.Get("store_pkey").(bool) 268 | if isSet && (entry.StorePrivateKey != storePkey) { 269 | entry.StorePrivateKey = storePkey 270 | } 271 | 272 | _, isSet = data.GetOk("key_type") 273 | keyType := data.Get("key_type").(string) 274 | if isSet && (entry.KeyType != keyType) { 275 | entry.KeyType = keyType 276 | } 277 | 278 | _, isSet = data.GetOk("key_bits") 279 | keyBits := data.Get("key_bits").(int) 280 | if isSet && (entry.KeyBits != keyBits) { 281 | entry.KeyBits = keyBits 282 | } 283 | 284 | _, isSet = data.GetOk("key_curve") 285 | keyCurve := data.Get("key_curve").(string) 286 | if isSet && (entry.KeyCurve != keyCurve) { 287 | entry.KeyCurve = keyCurve 288 | } 289 | 290 | _, isSet = data.GetOk("max_ttl") 291 | maxTtl := time.Duration(data.Get("max_ttl").(int)) * time.Second 292 | if isSet && (entry.MaxTTL != maxTtl) { 293 | entry.MaxTTL = maxTtl 294 | } 295 | 296 | _, isSet = data.GetOk("ttl") 297 | ttl := time.Duration(data.Get("ttl").(int)) * time.Second 298 | if isSet && (entry.TTL != ttl) { 299 | entry.TTL = ttl 300 | } 301 | 302 | _, isSet = data.GetOk("generate_lease") 303 | generateLease := data.Get("generate_lease").(bool) 304 | if isSet && (entry.GenerateLease != generateLease) { 305 | entry.GenerateLease = generateLease 306 | } 307 | 308 | _, isSet = data.GetOk("server_timeout") 309 | serverTimeout := time.Duration(data.Get("server_timeout").(int)) * time.Second 310 | if isSet && (entry.ServerTimeout != serverTimeout) { 311 | entry.ServerTimeout = serverTimeout 312 | } 313 | 314 | _, isSet = data.GetOk("venafi_secret") 315 | venafiSecret := data.Get("venafi_secret").(string) 316 | if isSet && (entry.VenafiSecret != venafiSecret) { 317 | entry.VenafiSecret = venafiSecret 318 | } 319 | 320 | _, isSet = data.GetOk("zone") 321 | zone := data.Get("zone").(string) 322 | if isSet && (entry.Zone != zone) { 323 | entry.Zone = zone 324 | } 325 | 326 | _, isSet = data.GetOk("min_cert_time_left") 327 | minCertTimeLeft := time.Duration(data.Get("min_cert_time_left").(int)) * time.Second 328 | if isSet && (entry.MinCertTimeLeft != minCertTimeLeft) { 329 | entry.MinCertTimeLeft = minCertTimeLeft 330 | } 331 | 332 | _, isSet = data.GetOk("ignore_local_storage") 333 | ignoreLocalStorage := data.Get("ignore_local_storage").(bool) 334 | if isSet && (entry.IgnoreLocalStorage != ignoreLocalStorage) { 335 | entry.IgnoreLocalStorage = ignoreLocalStorage 336 | } 337 | 338 | err = validateEntry(entry) 339 | if err != nil { 340 | return nil, err 341 | } 342 | 343 | return entry, nil 344 | } 345 | 346 | func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 347 | var err error 348 | 349 | name := data.Get("name").(string) 350 | updateEntry := data.Get("update_if_exist").(bool) 351 | 352 | var entry *roleEntry 353 | 354 | if updateEntry { 355 | entry, err = b.pathRoleUpdate(ctx, req, data) 356 | if err != nil { 357 | return nil, err 358 | } 359 | 360 | } else { 361 | entry = &roleEntry{ 362 | ChainOption: data.Get("chain_option").(string), 363 | StoreByCN: data.Get("store_by_cn").(bool), 364 | StoreBySerial: data.Get("store_by_serial").(bool), 365 | StoreBy: data.Get("store_by").(string), 366 | NoStore: data.Get("no_store").(bool), 367 | ServiceGenerated: data.Get("service_generated_cert").(bool), 368 | StorePrivateKey: data.Get("store_pkey").(bool), 369 | KeyType: data.Get("key_type").(string), 370 | KeyBits: data.Get("key_bits").(int), 371 | KeyCurve: data.Get("key_curve").(string), 372 | MaxTTL: time.Duration(data.Get("max_ttl").(int)) * time.Second, 373 | TTL: time.Duration(data.Get("ttl").(int)) * time.Second, 374 | IssuerHint: data.Get("issuer_hint").(string), 375 | GenerateLease: data.Get("generate_lease").(bool), 376 | ServerTimeout: time.Duration(data.Get("server_timeout").(int)) * time.Second, 377 | VenafiSecret: data.Get("venafi_secret").(string), 378 | Zone: data.Get("zone").(string), 379 | MinCertTimeLeft: time.Duration(data.Get("min_cert_time_left").(int)) * time.Second, 380 | IgnoreLocalStorage: data.Get("ignore_local_storage").(bool), 381 | } 382 | } 383 | 384 | err = validateEntry(entry) 385 | if err != nil { 386 | return logical.ErrorResponse(err.Error()), nil 387 | } 388 | 389 | // Store it 390 | jsonEntry, err := logical.StorageEntryJSON("role/"+name, entry) 391 | if err != nil { 392 | return nil, err 393 | } 394 | if err := req.Storage.Put(ctx, jsonEntry); err != nil { 395 | return nil, err 396 | } 397 | 398 | var logResp *logical.Response 399 | 400 | respData := map[string]interface{}{} 401 | 402 | warnings := getCredentialsWarnings(b, ctx, req.Storage, entry.VenafiSecret) 403 | 404 | if cap(warnings) > 0 { 405 | logResp = &logical.Response{ 406 | 407 | Data: respData, 408 | Redirect: "", 409 | Warnings: warnings, 410 | } 411 | return logResp, nil 412 | } 413 | 414 | return nil, nil 415 | } 416 | 417 | func validateEntry(entry *roleEntry) (err error) { 418 | 419 | credName := entry.VenafiSecret 420 | if credName == "" { 421 | return fmt.Errorf(util.ErrorTextVenafiSecretEmpty) 422 | } 423 | 424 | if entry.MaxTTL > 0 && entry.TTL > entry.MaxTTL { 425 | return fmt.Errorf( 426 | util.ErrorTextValueMustBeLess, 427 | ) 428 | } 429 | 430 | if (entry.StoreByCN || entry.StoreBySerial) && entry.StoreBy != "" { 431 | return fmt.Errorf(util.ErrorTextStoreByAndStoreByCNOrSerialConflict) 432 | } 433 | if (entry.StoreByCN || entry.StoreBySerial) && entry.NoStore { 434 | return fmt.Errorf(util.ErrorTextNoStoreAndStoreByCNOrSerialConflict) 435 | } 436 | if entry.StoreBy != "" && entry.NoStore { 437 | return fmt.Errorf(util.ErrorTextNoStoreAndStoreByConflict) 438 | } 439 | if entry.StoreBy != "" { 440 | if (entry.StoreBy != util.StoreBySerialString) && (entry.StoreBy != util.StoreByCNString) && (entry.StoreBy != util.StoreByHASHstring) { 441 | return fmt.Errorf( 442 | fmt.Sprintf(util.ErrTextStoreByWrongOption, util.StoreBySerialString, util.StoreByCNString, util.StoreByHASHstring, entry.StoreBy), 443 | ) 444 | } 445 | } 446 | 447 | //StoreBySerial and StoreByCN options are deprecated 448 | //if one of them is set we will set store_by option 449 | //if both are set then we set store_by to serial 450 | if entry.StoreBySerial { 451 | entry.StoreBy = util.StoreBySerialString 452 | } else if entry.StoreByCN { 453 | entry.StoreBy = util.StoreByCNString 454 | } 455 | 456 | return nil 457 | } 458 | 459 | func getCredentialsWarnings(b *backend, ctx context.Context, s logical.Storage, credentialsName string) []string { 460 | if credentialsName == "" { 461 | return []string{} 462 | } 463 | 464 | cred, err := b.getVenafiSecret(ctx, s, credentialsName) 465 | if err != nil || cred == nil { 466 | return []string{} 467 | } 468 | 469 | warnings := getWarnings(cred, credentialsName) 470 | 471 | return warnings 472 | } 473 | 474 | type roleEntry struct { 475 | 476 | //Venafi values 477 | Name string 478 | ChainOption string `json:"chain_option"` 479 | StoreByCN bool `json:"store_by_cn"` 480 | StoreBySerial bool `json:"store_by_serial"` 481 | StoreBy string `json:"store_by"` 482 | NoStore bool `json:"no_store"` 483 | ServiceGenerated bool `json:"service_generated_cert"` 484 | StorePrivateKey bool `json:"store_pkey"` 485 | KeyType string `json:"key_type"` 486 | KeyBits int `json:"key_bits"` 487 | KeyCurve string `json:"key_curve"` 488 | LeaseMax string `json:"lease_max"` 489 | Lease string `json:"lease"` 490 | TTL time.Duration `json:"ttl_duration"` 491 | MaxTTL time.Duration `json:"max_ttl_duration"` 492 | IssuerHint string `json:"issuer_hint"` 493 | GenerateLease bool `json:"generate_lease,omitempty"` 494 | DeprecatedMaxTTL string `json:"max_ttl"` 495 | DeprecatedTTL string `json:"ttl"` 496 | ServerTimeout time.Duration `json:"server_timeout"` 497 | VenafiSecret string `json:"venafi_secret"` 498 | Zone string `json:"zone"` 499 | MinCertTimeLeft time.Duration `json:"min_cert_time_left"` 500 | IgnoreLocalStorage bool `json:"ignore_local_storage"` 501 | } 502 | 503 | func (r *roleEntry) ToResponseData() map[string]interface{} { 504 | responseData := map[string]interface{}{ 505 | "venafi_secret": r.VenafiSecret, 506 | "role_zone": r.Zone, 507 | "store_by": r.StoreBy, 508 | "no_store": r.NoStore, 509 | "service_generated_cert": r.ServiceGenerated, 510 | "store_pkey": r.StorePrivateKey, 511 | "ttl": int64(r.TTL.Seconds()), 512 | "issuer_hint": r.IssuerHint, 513 | "max_ttl": int64(r.MaxTTL.Seconds()), 514 | "generate_lease": r.GenerateLease, 515 | "chain_option": r.ChainOption, 516 | "min_cert_time_left": util.ShortDurationString(r.MinCertTimeLeft), 517 | "server_timeout": util.ShortDurationString(r.ServerTimeout), 518 | "ignore_local_storage": r.IgnoreLocalStorage, 519 | } 520 | return responseData 521 | } 522 | 523 | const ( 524 | pathListRolesHelpSyn = `List the existing roles in this backend` 525 | pathListRolesHelpDesc = `Roles will be listed by the role name.` 526 | pathRoleHelpSyn = `Manage the roles that can be created with this backend.` 527 | pathRoleHelpDesc = `This path lets you manage the roles that can be created with this backend.` 528 | ) 529 | -------------------------------------------------------------------------------- /plugin/pki/path_roles_test.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/Venafi/vault-pki-backend-venafi/plugin/util" 8 | ) 9 | 10 | func TestRoleValidate(t *testing.T) { 11 | 12 | entry := &roleEntry{} 13 | 14 | err := validateEntry(entry) 15 | if err == nil { 16 | t.Fatalf("Expecting error") 17 | } 18 | if err.Error() != util.ErrorTextVenafiSecretEmpty { 19 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextInvalidMode, err) 20 | } 21 | 22 | entry = &roleEntry{ 23 | VenafiSecret: "testSecret", 24 | TTL: 120, 25 | MaxTTL: 100, 26 | } 27 | 28 | err = validateEntry(entry) 29 | if err == nil { 30 | t.Fatalf("Expecting error") 31 | } 32 | if err.Error() != util.ErrorTextValueMustBeLess { 33 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextValueMustBeLess, err) 34 | } 35 | 36 | entry = &roleEntry{ 37 | VenafiSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 38 | StoreByCN: true, 39 | StoreBy: "cn", 40 | } 41 | err = validateEntry(entry) 42 | if err == nil { 43 | t.Fatalf("Expecting error") 44 | } 45 | if err.Error() != util.ErrorTextStoreByAndStoreByCNOrSerialConflict { 46 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextStoreByAndStoreByCNOrSerialConflict, err) 47 | } 48 | 49 | entry = &roleEntry{ 50 | VenafiSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 51 | StoreBySerial: true, 52 | StoreBy: "cn", 53 | } 54 | err = validateEntry(entry) 55 | if err == nil { 56 | t.Fatalf("Expecting error") 57 | } 58 | if err.Error() != util.ErrorTextStoreByAndStoreByCNOrSerialConflict { 59 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextStoreByAndStoreByCNOrSerialConflict, err) 60 | } 61 | 62 | entry = &roleEntry{ 63 | VenafiSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 64 | StoreBySerial: true, 65 | StoreByCN: true, 66 | StoreBy: "cn", 67 | } 68 | err = validateEntry(entry) 69 | if err == nil { 70 | t.Fatalf("Expecting error") 71 | } 72 | if err.Error() != util.ErrorTextStoreByAndStoreByCNOrSerialConflict { 73 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextStoreByAndStoreByCNOrSerialConflict, err) 74 | } 75 | 76 | entry = &roleEntry{ 77 | VenafiSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 78 | StoreBySerial: true, 79 | StoreByCN: true, 80 | NoStore: true, 81 | } 82 | err = validateEntry(entry) 83 | if err == nil { 84 | t.Fatalf("Expecting error") 85 | } 86 | if err.Error() != util.ErrorTextNoStoreAndStoreByCNOrSerialConflict { 87 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextNoStoreAndStoreByCNOrSerialConflict, err) 88 | } 89 | 90 | entry = &roleEntry{ 91 | VenafiSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 92 | StoreBy: "serial", 93 | NoStore: true, 94 | } 95 | err = validateEntry(entry) 96 | if err == nil { 97 | t.Fatalf("Expecting error") 98 | } 99 | if err.Error() != util.ErrorTextNoStoreAndStoreByConflict { 100 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextNoStoreAndStoreByConflict, err) 101 | } 102 | 103 | entry = &roleEntry{ 104 | VenafiSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 105 | StoreBy: "sebial", 106 | } 107 | err = validateEntry(entry) 108 | if err == nil { 109 | t.Fatalf("Expecting error") 110 | } 111 | expectingError := fmt.Sprintf(util.ErrTextStoreByWrongOption, util.StoreBySerialString, util.StoreByCNString, util.StoreByHASHstring, "sebial") 112 | if err.Error() != expectingError { 113 | t.Fatalf("Expecting error %s but got %s", expectingError, err) 114 | } 115 | 116 | entry = &roleEntry{ 117 | VenafiSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 118 | StoreBySerial: true, 119 | StoreByCN: true, 120 | } 121 | err = validateEntry(entry) 122 | if err != nil { 123 | t.Fatal(err) 124 | } 125 | 126 | if entry.StoreBy != util.StoreBySerialString { 127 | t.Fatalf("Expecting store_by parameter will be set to %s", util.StoreBySerialString) 128 | } 129 | 130 | entry = &roleEntry{ 131 | VenafiSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 132 | StoreByCN: true, 133 | } 134 | err = validateEntry(entry) 135 | if err != nil { 136 | t.Fatal(err) 137 | } 138 | 139 | if entry.StoreBy != util.StoreByCNString { 140 | t.Fatalf("Expecting store_by parameter will be set to %s", util.StoreByCNString) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /plugin/pki/path_venafi_cert_enroll_test.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Venafi/vcert/v5" 7 | 8 | "github.com/Venafi/vault-pki-backend-venafi/plugin/util" 9 | ) 10 | 11 | func TestOriginInRequest(t *testing.T) { 12 | integrationTestEnv, err := NewIntegrationTestEnv() 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | signCSR := false 18 | var data requestData 19 | var role roleEntry 20 | 21 | data.commonName = "tpp.example.com" 22 | role.KeyType = "rsa" 23 | role.ChainOption = "first" 24 | 25 | // The purpose of this test is to verify the customField Utility, regardless of connector Type 26 | cfg := &vcert.Config{} 27 | client, err := vcert.NewClient(cfg) 28 | 29 | certReq, err := formRequest(data, &role, &client, signCSR, integrationTestEnv.Backend.Logger()) 30 | if certReq.CustomFields[0].Value != utilityName { 31 | t.Fatalf("Expected %s in request custom fields origin", utilityName) 32 | } 33 | } 34 | 35 | func TestSanitizeCertRequest(t *testing.T) { 36 | integrationTestEnv, err := NewIntegrationTestEnv() 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | 41 | sn := "test" // site name 42 | sd := "venafi.com" // site domain 43 | cn := sn + sd 44 | reqData := requestData{ 45 | commonName: cn, 46 | altNames: []string{"maria-" + sd, "rose-" + sd, "rose-" + sd, "bob-" + sd, "bob-" + sd, "shina-" + sd}, 47 | } 48 | correctReqData := requestData{ 49 | commonName: cn, 50 | altNames: []string{"bob-" + sd, "maria-" + sd, "rose-" + sd, "shina-" + sd, cn}, 51 | } 52 | sanitizeRequestData(&reqData, integrationTestEnv.Backend.Logger()) 53 | if reqData.commonName != correctReqData.commonName || !util.StringSlicesEqual(reqData.altNames, correctReqData.altNames) { 54 | t.Fatalf("Expected %s in request custom fields origin", utilityName) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /plugin/pki/path_venafi_cert_read.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/vault/sdk/framework" 7 | "github.com/hashicorp/vault/sdk/logical" 8 | 9 | "github.com/Venafi/vault-pki-backend-venafi/plugin/util" 10 | ) 11 | 12 | func pathVenafiCertRead(b *backend) *framework.Path { 13 | return &framework.Path{ 14 | Pattern: "cert/" + framework.GenericNameRegex("certificate_uid"), 15 | Fields: map[string]*framework.FieldSchema{ 16 | "certificate_uid": { 17 | Type: framework.TypeString, 18 | Description: "Common name or serial number of desired certificate", 19 | }, 20 | "key_password": { 21 | Type: framework.TypeString, 22 | Description: "Password for encrypting private key", 23 | }, 24 | }, 25 | Callbacks: map[logical.Operation]framework.OperationFunc{ 26 | logical.ReadOperation: b.pathVenafiCertRead, 27 | //todo: maybe add delete operation to delete certificate entry from storage 28 | }, 29 | 30 | HelpSynopsis: pathConfigRootHelpSyn, 31 | HelpDescription: pathConfigRootHelpDesc, 32 | } 33 | } 34 | 35 | func (b *backend) pathVenafiCertRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 36 | b.Logger().Debug("Trying to read certificate") 37 | certUID := data.Get("certificate_uid").(string) 38 | if len(certUID) == 0 { 39 | return logical.ErrorResponse("no common name specified on certificate"), nil 40 | } 41 | 42 | keyPassword := data.Get("key_password").(string) 43 | 44 | cert, err := loadCertificateFromStorage(b, ctx, req, certUID, keyPassword) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | respData := map[string]interface{}{ 50 | "certificate_uid": certUID, 51 | "serial_number": cert.SerialNumber, 52 | "certificate_chain": cert.CertificateChain, 53 | "certificate": cert.Certificate, 54 | "private_key": cert.PrivateKey, 55 | } 56 | 57 | if keyPassword != "" { 58 | encryptedPrivateKeyPem, err := util.EncryptPrivateKey(cert.PrivateKey, keyPassword) 59 | if err != nil { 60 | return nil, err 61 | } 62 | respData["private_key"] = encryptedPrivateKeyPem 63 | } 64 | 65 | return &logical.Response{ 66 | //Data: structs.New(cert).Map(), 67 | Data: respData, 68 | }, nil 69 | } 70 | -------------------------------------------------------------------------------- /plugin/pki/path_venafi_cert_revoke.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/Venafi/vcert/v5/pkg/certificate" 9 | "github.com/Venafi/vcert/v5/pkg/endpoint" 10 | "github.com/Venafi/vcert/v5/pkg/policy" 11 | vcertutil "github.com/Venafi/vcert/v5/pkg/util" 12 | "github.com/hashicorp/vault/sdk/framework" 13 | "github.com/hashicorp/vault/sdk/logical" 14 | 15 | "github.com/Venafi/vault-pki-backend-venafi/plugin/util" 16 | ) 17 | 18 | func pathVenafiCertRevoke(b *backend) *framework.Path { 19 | return &framework.Path{ 20 | Pattern: "revoke/" + framework.GenericNameRegex("role"), 21 | Fields: map[string]*framework.FieldSchema{ 22 | "role": { 23 | Type: framework.TypeString, 24 | Description: `The desired role with configuration for this request`, 25 | }, 26 | "certificate_uid": { 27 | Type: framework.TypeString, 28 | Description: "Common name for created certificate", 29 | }, 30 | }, 31 | Operations: map[logical.Operation]framework.OperationHandler{ 32 | logical.UpdateOperation: &framework.PathOperation{ 33 | Callback: b.venafiCertRevoke, 34 | }, 35 | }, 36 | 37 | HelpSynopsis: pathConfigRootHelpSyn, 38 | HelpDescription: pathConfigRootHelpDesc, 39 | } 40 | } 41 | 42 | func (b *backend) venafiCertRevoke(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 43 | 44 | op := req.Operation 45 | if op == logical.RevokeOperation { 46 | return nil, nil 47 | } 48 | 49 | b.Logger().Debug("Getting the role\n") 50 | roleName := d.Get("role").(string) 51 | 52 | // Get the role 53 | role, err := b.getRole(ctx, req.Storage, roleName) 54 | if err != nil { 55 | return nil, err 56 | } 57 | if role == nil { 58 | return logical.ErrorResponse(fmt.Sprintf("unknown role: %s", roleName)), nil 59 | } 60 | 61 | if role.KeyType == "any" { 62 | return logical.ErrorResponse("role key type \"any\" not allowed for issuing certificates, only signing"), nil 63 | } 64 | 65 | id := d.Get("certificate_uid").(string) 66 | 67 | if exists, err := isCertificateStored(ctx, req, id, role); !exists { 68 | if err != nil { 69 | return logical.ErrorResponse(err.Error()), err 70 | } 71 | return logical.ErrorResponse("the certificate is not stored"), fmt.Errorf("the certificate is not stored") 72 | } 73 | 74 | b.Logger().Debug("Creating Venafi client:") 75 | 76 | cl, cfg, err := b.ClientVenafi(ctx, req, role) 77 | 78 | if err != nil { 79 | return logical.ErrorResponse(err.Error()), nil 80 | } 81 | 82 | dn, err := getDn(b, &cl, ctx, req, cfg.Zone, id, role.StoreBy) 83 | 84 | if err != nil { 85 | return logical.ErrorResponse(err.Error()), nil 86 | } 87 | 88 | revReq := certificate.RevocationRequest{} 89 | 90 | revReq.CertificateDN = dn 91 | 92 | err = cl.RevokeCertificate(&revReq) 93 | 94 | if err != nil { 95 | return logical.ErrorResponse("Failed to revoke certificate: %s", err), nil 96 | } 97 | 98 | err = deleteCertificateEntry(ctx, req, id, role) 99 | if err != nil { 100 | return logical.ErrorResponse("Certificate was revoked, but failed to remove it from vault: %s", err), nil 101 | 102 | } 103 | 104 | return nil, nil 105 | } 106 | 107 | func deleteCertificateEntry(ctx context.Context, req *logical.Request, id string, role *roleEntry) error { 108 | 109 | if !role.StoreByCN { 110 | id = strings.ReplaceAll(id, ":", "-") 111 | } 112 | 113 | path := "certs/" + id 114 | 115 | //remove the certificate from vault. 116 | err := req.Storage.Delete(ctx, path) 117 | 118 | if err != nil { 119 | return err 120 | } 121 | 122 | return nil 123 | 124 | } 125 | 126 | func isCertificateStored(ctx context.Context, req *logical.Request, id string, role *roleEntry) (bool, error) { 127 | 128 | //there is nothing to remove. 129 | if role.NoStore { 130 | return false, fmt.Errorf("certificate is not stored") 131 | } 132 | 133 | if !role.StoreByCN { 134 | if strings.Contains(id, ":") { 135 | id = strings.ReplaceAll(id, ":", "-") 136 | } 137 | if strings.Contains(id, "-") { 138 | id = strings.ReplaceAll(id, "-", "-") 139 | } 140 | } 141 | 142 | path := "certs/" + id 143 | entry, err := req.Storage.Get(ctx, path) 144 | 145 | if err != nil { 146 | return false, err 147 | } 148 | 149 | if entry == nil { 150 | return false, nil 151 | } 152 | 153 | return true, nil 154 | } 155 | 156 | func getDn(b *backend, c *endpoint.Connector, ctx context.Context, req *logical.Request, zone string, CertId string, storeByType string) (string, error) { 157 | 158 | dn := CertId 159 | switch storeByType { 160 | case util.StoreBySerialString: 161 | return getDnFromSerial(c, CertId) 162 | case util.StoreByCNString: 163 | if (*c).GetType() == endpoint.ConnectorTypeTPP { 164 | 165 | if !strings.HasPrefix(zone, vcertutil.PathSeparator) { 166 | zone = vcertutil.PathSeparator + zone 167 | } 168 | 169 | if !strings.HasPrefix(zone, policy.RootPath) { 170 | zone = policy.RootPath + zone 171 | 172 | } 173 | 174 | if !strings.HasPrefix(dn, zone) { 175 | dn = fmt.Sprintf("%s\\%s", zone, CertId) 176 | } 177 | 178 | } 179 | case util.StoreByHASHstring: 180 | cert, err := loadCertificateFromStorage(b, ctx, req, CertId, "") 181 | if err != nil { 182 | return "", err 183 | } 184 | serialNumber := strings.ReplaceAll(cert.SerialNumber, ":", "") 185 | return getDnFromSerial(c, serialNumber) 186 | default: 187 | return "", fmt.Errorf("unknown role type of uid for storage") 188 | } 189 | 190 | return dn, nil 191 | 192 | } 193 | 194 | func getDnFromSerial(c *endpoint.Connector, serial string) (string, error) { 195 | var reqS certificate.SearchRequest 196 | //removing dash from serial since TPP does not contain them 197 | tppSerial := strings.ReplaceAll(serial, "-", "") 198 | reqS = append(reqS, fmt.Sprintf("serial=%s", tppSerial)) 199 | data, err := (*c).SearchCertificates(&reqS) 200 | if err != nil { 201 | return "", err 202 | } 203 | dn := data.Certificates[0].CertificateRequestId 204 | 205 | return dn, nil 206 | 207 | } 208 | -------------------------------------------------------------------------------- /plugin/pki/path_venafi_fetch.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/vault/sdk/framework" 7 | "github.com/hashicorp/vault/sdk/logical" 8 | ) 9 | 10 | func pathVenafiFetchListCerts(b *backend) *framework.Path { 11 | return &framework.Path{ 12 | Pattern: "certs/?$", 13 | 14 | Callbacks: map[logical.Operation]framework.OperationFunc{ 15 | logical.ListOperation: b.pathVenafiFetchCertList, 16 | }, 17 | 18 | HelpSynopsis: pathVenafiFetchHelpSyn, 19 | HelpDescription: pathVenafiFetchHelpDesc, 20 | } 21 | } 22 | 23 | func (b *backend) pathVenafiFetchCertList(ctx context.Context, req *logical.Request, data *framework.FieldData) (response *logical.Response, retErr error) { 24 | entries, err := req.Storage.List(ctx, "certs/") 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | return logical.ListResponse(entries), nil 30 | } 31 | 32 | const pathVenafiFetchHelpSyn = ` 33 | This allows certificates to be fetched. 34 | ` 35 | 36 | const pathVenafiFetchHelpDesc = ` 37 | This allows certificates to be fetched. 38 | ` 39 | -------------------------------------------------------------------------------- /plugin/pki/path_venafi_secrets.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/hashicorp/vault/sdk/framework" 9 | "github.com/hashicorp/vault/sdk/helper/consts" 10 | "github.com/hashicorp/vault/sdk/logical" 11 | 12 | "github.com/Venafi/vault-pki-backend-venafi/plugin/util" 13 | ) 14 | 15 | func pathCredentialsList(b *backend) *framework.Path { 16 | return &framework.Path{ 17 | Pattern: util.CredentialsRootPath + "?$", 18 | Fields: nil, 19 | Operations: map[logical.Operation]framework.OperationHandler{ 20 | logical.ListOperation: &framework.PathOperation{ 21 | Callback: b.pathVenafiSecretList, 22 | Summary: "List all venafi secrets", 23 | }, 24 | }, 25 | HelpSynopsis: pathListVenafiSecretsHelpSyn, 26 | HelpDescription: pathListVenafiSecretsHelpDesc, 27 | } 28 | } 29 | 30 | func pathCredentials(b *backend) *framework.Path { 31 | return &framework.Path{ 32 | Pattern: util.CredentialsRootPath + framework.GenericNameRegex("name"), 33 | Fields: map[string]*framework.FieldSchema{ 34 | "name": { 35 | Type: framework.TypeString, 36 | Description: "Name of the authentication object", 37 | Required: true, 38 | }, 39 | "zone": { 40 | Type: framework.TypeString, 41 | Description: `Name of Venafi Platform policy or Venafi Cloud project zone. 42 | Example for Platform: testpolicy\\vault 43 | Example for Venafi Cloud: e33f3e40-4e7e-11ea-8da3-b3c196ebeb0b`, 44 | Required: true, 45 | }, 46 | "tpp_url": { 47 | Type: framework.TypeString, 48 | Description: `URL of Venafi Platform. Example: https://tpp.venafi.example/vedsdk. Deprecated, use 'url' instead`, 49 | Deprecated: true, 50 | }, 51 | "url": { 52 | Type: framework.TypeString, 53 | Description: `URL of Venafi API Endpoint. Example: https://tpp.venafi.example`, 54 | Required: true, 55 | }, 56 | 57 | "cloud_url": { 58 | Type: framework.TypeString, 59 | Description: `URL for Venafi Cloud. Set it only if you want to use non production Cloud. Deprecated, use 'url' instead`, 60 | Deprecated: true, 61 | }, 62 | "tpp_user": { 63 | Type: framework.TypeString, 64 | Description: `WebSDK username for Venafi Platform API`, 65 | Deprecated: true, 66 | }, 67 | "tpp_password": { 68 | Type: framework.TypeString, 69 | Description: `Password for WebSDK user`, 70 | Deprecated: true, 71 | }, 72 | "access_token": { 73 | Type: framework.TypeString, 74 | Description: `Access token for TPP; omit if secrets engine should manage token refreshes`, 75 | }, 76 | "refresh_token": { 77 | Type: framework.TypeString, 78 | Description: `Primary refresh token for updating TPP access token before it expires`, 79 | }, 80 | "refresh_token_2": { 81 | Type: framework.TypeString, 82 | Description: `Secondary refresh token for ensuring no impact on certificate requests when tokens are refreshed`, 83 | }, 84 | "refresh_interval": { 85 | Type: framework.TypeDurationSecond, 86 | Description: `Frequency at which secrets engine should refresh tokens.`, 87 | Default: time.Duration(30*24) * time.Hour, 88 | }, 89 | "apikey": { 90 | Type: framework.TypeString, 91 | Description: `API key for Venafi Cloud. Example: 142231b7-cvb0-412e-886b-6aeght0bc93d`, 92 | }, 93 | "trust_bundle_file": { 94 | Type: framework.TypeString, 95 | Description: `Use to specify a PEM formatted file with certificates to be used as trust anchors when communicating with the remote server. 96 | Example: trust_bundle_file="/path-to/bundle.pem""`, 97 | }, 98 | "fakemode": { 99 | Type: framework.TypeBool, 100 | Description: `Set it to true to use fake CA instead of Cloud or Platform to issue certificates. Useful for testing.`, 101 | Default: false, 102 | }, 103 | "client_id": { 104 | Type: framework.TypeString, 105 | Description: `Use to specify the application that will be using the token.`, 106 | Default: `hashicorp-vault-by-venafi`, 107 | }, 108 | }, 109 | Operations: map[logical.Operation]framework.OperationHandler{ 110 | logical.ReadOperation: &framework.PathOperation{ 111 | Callback: b.pathVenafiSecretRead, 112 | Summary: "Read the properties of a venafi secret and displays it to the user.", 113 | }, 114 | logical.UpdateOperation: &framework.PathOperation{ 115 | Callback: b.pathVenafiSecretCreate, 116 | Summary: "Create a venafi secret", 117 | }, 118 | logical.DeleteOperation: &framework.PathOperation{ 119 | Callback: b.pathVenafiSecretDelete, 120 | Summary: "Delete a venafi secret", 121 | }, 122 | }, 123 | HelpSynopsis: pathVenafiSecretsHelpSyn, 124 | HelpDescription: pathVenafiSecretsHelpDesc, 125 | } 126 | } 127 | 128 | func (b *backend) pathVenafiSecretList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 129 | entries, err := req.Storage.List(ctx, util.CredentialsRootPath) 130 | if err != nil { 131 | return nil, err 132 | } 133 | 134 | return logical.ListResponse(entries), nil 135 | } 136 | 137 | func (b *backend) pathVenafiSecretRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 138 | policyName := data.Get("name").(string) 139 | if policyName == "" { 140 | return logical.ErrorResponse("missing policy name"), nil 141 | } 142 | 143 | cred, err := b.getVenafiSecret(ctx, req.Storage, policyName) 144 | if err != nil { 145 | return nil, err 146 | } 147 | if cred == nil { 148 | return nil, nil 149 | } 150 | resp := &logical.Response{ 151 | Data: cred.ToResponseData(), 152 | } 153 | 154 | return resp, nil 155 | } 156 | 157 | func (b *backend) pathVenafiSecretDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 158 | 159 | if b.System().ReplicationState().HasState(consts.ReplicationPerformanceStandby | consts.ReplicationPerformanceSecondary) { 160 | // only the leader can handle deletion 161 | return nil, logical.ErrReadOnly 162 | } 163 | err := req.Storage.Delete(ctx, util.CredentialsRootPath+data.Get("name").(string)) 164 | if err != nil { 165 | return nil, err 166 | } 167 | return nil, nil 168 | } 169 | 170 | func (b *backend) pathVenafiSecretCreate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 171 | if b.System().ReplicationState().HasState(consts.ReplicationPerformanceStandby | consts.ReplicationPerformanceSecondary) { 172 | // only the leader can handle token creating, we don't ever want to enter into refreshing process if we are 173 | // getting request in vault follower node 174 | return nil, logical.ErrReadOnly 175 | } 176 | var err error 177 | name := data.Get("name").(string) 178 | b.Logger().Info(fmt.Sprintf("Creating Venafi secret: %s", name)) 179 | url := data.Get("url").(string) 180 | var tppUrl, cloudUrl string 181 | 182 | if url == "" { 183 | tppUrl = data.Get("tpp_url").(string) 184 | url = tppUrl 185 | } 186 | if url == "" { 187 | cloudUrl = data.Get("cloud_url").(string) 188 | url = cloudUrl 189 | } 190 | 191 | entry := &venafiSecretEntry{ 192 | URL: url, 193 | Zone: data.Get("zone").(string), 194 | TppURL: tppUrl, 195 | TppUser: data.Get("tpp_user").(string), 196 | TppPassword: data.Get("tpp_password").(string), 197 | AccessToken: data.Get("access_token").(string), 198 | RefreshToken: data.Get("refresh_token").(string), 199 | RefreshToken2: data.Get("refresh_token_2").(string), 200 | RefreshInterval: time.Duration(data.Get("refresh_interval").(int)) * time.Second, 201 | NextRefresh: time.Now(), 202 | CloudURL: cloudUrl, 203 | Apikey: data.Get("apikey").(string), 204 | TrustBundleFile: data.Get("trust_bundle_file").(string), 205 | Fakemode: data.Get("fakemode").(bool), 206 | ClientId: data.Get("client_id").(string), 207 | } 208 | 209 | b.Logger().Info(fmt.Sprintf("Validating data for venafi secret %s", name)) 210 | err = validateVenafiSecretEntry(entry) 211 | if err != nil { 212 | b.Logger().Error(fmt.Sprintf("Error with venafi secret data: %s", err.Error())) 213 | return logical.ErrorResponse(err.Error()), nil 214 | } 215 | if entry.RefreshToken != "" && !entry.Fakemode { 216 | b.Logger().Info("Refresh tokens are provided. Setting up data") 217 | for i := 0; i < 2; i++ { 218 | 219 | b.Logger().Info("creating config for refreshing tokens") 220 | cfg, err := createConfigFromFieldData(entry) 221 | if err != nil { 222 | b.Logger().Error(fmt.Sprintf("Error during venafi secret creation: creating config error: %s", err.Error())) 223 | return logical.ErrorResponse(err.Error()), nil 224 | } 225 | 226 | b.Logger().Info("Refreshing tokens during Venafi secret creation") 227 | tokenInfo, err := getAccessData(cfg) 228 | if err != nil { 229 | b.Logger().Error(fmt.Sprintf("Error during venafi secret creation: refreshing tokens error: %s", err.Error())) 230 | return logical.ErrorResponse(err.Error()), nil 231 | } 232 | 233 | if i == 0 && tokenInfo.Refresh_token != "" { 234 | // ensure refresh interval is proactive by not allowing it to be longer than access token is valid 235 | maxInterval := time.Until(time.Unix(int64(tokenInfo.Expires), 0)).Round(time.Minute) - time.Duration(30)*time.Second 236 | if maxInterval < entry.RefreshInterval { 237 | b.Logger().Info("Refresh interval is not correct since is longer than access token validity. Setting up a proper one") 238 | entry.RefreshInterval = maxInterval 239 | } 240 | 241 | entry.RefreshToken = entry.RefreshToken2 242 | entry.RefreshToken2 = tokenInfo.Refresh_token 243 | entry.NextRefresh = time.Now().Add(entry.RefreshInterval) 244 | } 245 | 246 | if i > 0 { 247 | if tokenInfo.Access_token != "" { 248 | entry.AccessToken = tokenInfo.Access_token 249 | } 250 | if tokenInfo.Refresh_token != "" { 251 | entry.RefreshToken = tokenInfo.Refresh_token 252 | } 253 | } 254 | } 255 | b.Logger().Info("Success setting up refresh token data of Venafi secret") 256 | } 257 | 258 | //Store it 259 | 260 | b.Logger().Info("Setting up data for entry of Venafi secret") 261 | jsonEntry, err := logical.StorageEntryJSON(util.CredentialsRootPath+name, entry) 262 | 263 | if err != nil { 264 | b.Logger().Error(fmt.Sprintf("Error during venafi secret creation: error setting up refresh tokens for storage: %s", err.Error())) 265 | return nil, err 266 | } 267 | 268 | b.Logger().Info("Storing entry of Venafi secret") 269 | err = req.Storage.Put(ctx, jsonEntry) 270 | if err != nil { 271 | b.Logger().Error(fmt.Sprintf("Error during venafi secret creation: error storing refresh tokens: %s", err.Error())) 272 | return nil, err 273 | } 274 | 275 | var logResp *logical.Response 276 | 277 | warnings := getWarnings(entry, name) 278 | 279 | if cap(warnings) > 0 { 280 | logResp = &logical.Response{ 281 | 282 | Data: map[string]interface{}{}, 283 | Redirect: "", 284 | Warnings: warnings, 285 | } 286 | b.Logger().Info(fmt.Sprintf("Sucess on creating Venafi secret %s (with warnings)", name)) 287 | return logResp, nil 288 | } 289 | b.Logger().Info(fmt.Sprintf("Sucess on creating Venafi secret %s", name)) 290 | return nil, nil 291 | } 292 | 293 | func (b *backend) getVenafiSecret(ctx context.Context, s logical.Storage, name string) (*venafiSecretEntry, error) { 294 | entry, err := s.Get(ctx, util.CredentialsRootPath+name) 295 | if err != nil { 296 | return nil, err 297 | } 298 | if entry == nil { 299 | return nil, nil 300 | } 301 | 302 | var result venafiSecretEntry 303 | err = entry.DecodeJSON(&result) 304 | if err != nil { 305 | return nil, err 306 | } 307 | 308 | return &result, nil 309 | } 310 | 311 | func validateVenafiSecretEntry(entry *venafiSecretEntry) error { 312 | if !entry.Fakemode && entry.Apikey == "" && (entry.TppUser == "" || entry.TppPassword == "") && entry.RefreshToken == "" && entry.AccessToken == "" { 313 | return fmt.Errorf(util.ErrorTextInvalidMode) 314 | } 315 | 316 | //Only validate other fields if mode is not fakemode 317 | if !entry.Fakemode { 318 | //When api key is null, that means 319 | if entry.URL == "" && entry.Apikey == "" { 320 | return fmt.Errorf(util.ErrorTextURLEmpty) 321 | } 322 | 323 | if entry.Zone == "" { 324 | return fmt.Errorf(util.ErrorTextZoneEmpty) 325 | } 326 | 327 | if entry.TppUser != "" && entry.Apikey != "" { 328 | return fmt.Errorf(util.ErrorTextMixedTPPAndCloud) 329 | } 330 | 331 | if entry.TppUser != "" && entry.AccessToken != "" { 332 | return fmt.Errorf(util.ErrorTextMixedTPPAndToken) 333 | } 334 | 335 | if entry.AccessToken != "" && entry.Apikey != "" { 336 | return fmt.Errorf(util.ErrorTextMixedTokenAndCloud) 337 | } 338 | 339 | if (entry.RefreshToken != "" && entry.RefreshToken2 == "") || (entry.RefreshToken == "" && entry.RefreshToken2 != "") { 340 | return fmt.Errorf(util.ErrorTextNeed2RefreshTokens) 341 | } 342 | } 343 | return nil 344 | } 345 | 346 | func getWarnings(entry *venafiSecretEntry, name string) []string { 347 | 348 | warnings := []string{} 349 | 350 | if entry.TppURL != "" { 351 | warnings = append(warnings, "tpp_url is deprecated, please use url instead") 352 | } 353 | if entry.CloudURL != "" { 354 | warnings = append(warnings, "cloud_url is deprecated, please use url instead") 355 | } 356 | if entry.TppUser != "" { 357 | warnings = append(warnings, "tpp_user is deprecated, please use access_token token instead") 358 | } 359 | if entry.TppPassword != "" { 360 | warnings = append(warnings, "tpp_password is deprecated, please use access_token instead") 361 | } 362 | //Include success message in warnings 363 | if len(warnings) > 0 { 364 | warnings = append(warnings, "Venafi secret "+name+" saved successfully") 365 | } 366 | return warnings 367 | } 368 | 369 | type venafiSecretEntry struct { 370 | URL string `json:"url"` 371 | Zone string `json:"zone"` 372 | TppURL string `json:"tpp_url"` 373 | TppUser string `json:"tpp_user"` 374 | TppPassword string `json:"tpp_password"` 375 | AccessToken string `json:"access_token"` 376 | RefreshToken string `json:"refresh_token"` 377 | RefreshToken2 string `json:"refresh_token_2"` 378 | RefreshInterval time.Duration `json:"refresh_interval"` 379 | NextRefresh time.Time `json:"next_refresh"` 380 | CloudURL string `json:"cloud_url"` 381 | Apikey string `json:"apikey"` 382 | TrustBundleFile string `json:"trust_bundle_file"` 383 | Fakemode bool `json:"fakemode"` 384 | ClientId string `json:"client_id"` 385 | } 386 | 387 | func (p *venafiSecretEntry) ToResponseData() map[string]interface{} { 388 | responseData := map[string]interface{}{ 389 | //Sensible data will not be disclosed. 390 | //tpp_password, api_key, access_token, refresh_token 391 | 392 | "url": p.URL, 393 | "zone": p.Zone, 394 | "tpp_user": p.TppUser, 395 | "tpp_password": p.getStringMask(), 396 | "access_token": p.getStringMask(), 397 | "refresh_token": p.getStringMask(), 398 | "refresh_token_2": p.getStringMask(), 399 | "refresh_interval": util.ShortDurationString(p.RefreshInterval), 400 | "next_refresh": p.NextRefresh, 401 | "apikey": p.getStringMask(), 402 | "trust_bundle_file": p.TrustBundleFile, 403 | "fakemode": p.Fakemode, 404 | "client_id": p.ClientId, 405 | } 406 | return responseData 407 | } 408 | 409 | func (p *venafiSecretEntry) getStringMask() string { 410 | return stringMask 411 | } 412 | 413 | const ( 414 | stringMask = "********" 415 | pathListVenafiSecretsHelpSyn = `List the existing Venafi Secrets in this backend` // #nosec 416 | pathListVenafiSecretsHelpDesc = `Venafi Secrets will be listed by the secret name.` // #nosec 417 | pathVenafiSecretsHelpSyn = `Manage the Venafi Secrets that can be created with this backend.` // #nosec 418 | pathVenafiSecretsHelpDesc = `This path lets you manage the Venafi Secrets that can be created with this backend.` // #nosec 419 | ) 420 | -------------------------------------------------------------------------------- /plugin/pki/path_venafi_secrets_test.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Venafi/vault-pki-backend-venafi/plugin/util" 7 | ) 8 | 9 | func TestVenafiSecretValidate(t *testing.T) { 10 | entry := &venafiSecretEntry{} 11 | 12 | err := validateVenafiSecretEntry(entry) 13 | if err == nil { 14 | t.Fatalf("Expecting error") 15 | } 16 | if err.Error() != util.ErrorTextInvalidMode { 17 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextInvalidMode, err) 18 | } 19 | 20 | entry = &venafiSecretEntry{ 21 | AccessToken: "foo123bar==", 22 | } 23 | 24 | err = validateVenafiSecretEntry(entry) 25 | if err == nil { 26 | t.Fatalf("Expecting error") 27 | } 28 | if err.Error() != util.ErrorTextURLEmpty { 29 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextURLEmpty, err) 30 | } 31 | 32 | entry = &venafiSecretEntry{ 33 | URL: "https://ha-tpp12.sqlha.com:5008/vedsdk", 34 | AccessToken: "foo123bar==", 35 | } 36 | 37 | err = validateVenafiSecretEntry(entry) 38 | if err == nil { 39 | t.Fatalf("Expecting error") 40 | } 41 | if err.Error() != util.ErrorTextZoneEmpty { 42 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextZoneEmpty, err) 43 | } 44 | 45 | entry = &venafiSecretEntry{ 46 | URL: "https://qa-tpp.exmple.com/vedsdk", 47 | Zone: "devops\\vcert", 48 | Apikey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 49 | TppUser: "admin", 50 | TppPassword: "xxxx", 51 | } 52 | 53 | err = validateVenafiSecretEntry(entry) 54 | if err == nil { 55 | t.Fatalf("Expecting error") 56 | } 57 | if err.Error() != util.ErrorTextMixedTPPAndCloud { 58 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextMixedTPPAndCloud, err) 59 | } 60 | 61 | entry = &venafiSecretEntry{ 62 | URL: "https://qa-tpp.exmple.com/vedsdk", 63 | Zone: "devops\\vcert", 64 | AccessToken: "foo123bar==", 65 | TppUser: "admin", 66 | TppPassword: "xxxx", 67 | } 68 | 69 | err = validateVenafiSecretEntry(entry) 70 | if err == nil { 71 | t.Fatalf("Expecting error") 72 | } 73 | if err.Error() != util.ErrorTextMixedTPPAndToken { 74 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextMixedTPPAndToken, err) 75 | } 76 | 77 | entry = &venafiSecretEntry{ 78 | URL: "https://qa-tpp.exmple.com/vedsdk", 79 | Zone: "devops\\vcert", 80 | AccessToken: "foo123bar==", 81 | Apikey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 82 | } 83 | 84 | err = validateVenafiSecretEntry(entry) 85 | if err == nil { 86 | t.Fatalf("Expecting error") 87 | } 88 | if err.Error() != util.ErrorTextMixedTokenAndCloud { 89 | t.Fatalf("Expecting error %s but got %s", util.ErrorTextMixedTokenAndCloud, err) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /plugin/pki/secret_certs.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "github.com/hashicorp/vault/sdk/framework" 5 | ) 6 | 7 | // SecretCertsType is the name used to identify this type 8 | const SecretCertsType = "pki" 9 | 10 | func secretCerts(b *backend) *framework.Secret { 11 | return &framework.Secret{ 12 | Type: SecretCertsType, 13 | Fields: map[string]*framework.FieldSchema{ 14 | "certificate": &framework.FieldSchema{ 15 | Type: framework.TypeString, 16 | Description: `The PEM-encoded concatenated certificate and 17 | issuing certificate authority`, 18 | }, 19 | "private_key": &framework.FieldSchema{ 20 | Type: framework.TypeString, 21 | Description: "The PEM-encoded private key for the certificate", 22 | }, 23 | "serial": &framework.FieldSchema{ 24 | Type: framework.TypeString, 25 | Description: `The serial number of the certificate, for handy 26 | reference`, 27 | }, 28 | }, 29 | 30 | Revoke: b.venafiCertRevoke, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/pki/util.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "fmt" 8 | "io/ioutil" 9 | "net" 10 | "net/http" 11 | "os" 12 | "strconv" 13 | "time" 14 | 15 | "github.com/Venafi/vcert/v5" 16 | "github.com/Venafi/vcert/v5/pkg/endpoint" 17 | "github.com/Venafi/vcert/v5/pkg/venafi/tpp" 18 | "github.com/hashicorp/vault/sdk/logical" 19 | 20 | "github.com/Venafi/vault-pki-backend-venafi/plugin/pki/vpkierror" 21 | "github.com/Venafi/vault-pki-backend-venafi/plugin/util" 22 | ) 23 | 24 | const ( 25 | HTTP_UNAUTHORIZED = 401 26 | LEGACY_PEM = "der" 27 | ) 28 | 29 | type RunContext struct { 30 | TPPurl string 31 | TPPuser string 32 | TPPPassword string 33 | TPPZone string 34 | CloudUrl string 35 | CloudAPIkey string 36 | CloudZone string 37 | TokenUrl string 38 | AccessToken string 39 | TPPTestingEnabled bool 40 | CloudTestingEnabled bool 41 | FakeTestingEnabled bool 42 | TokenTestingEnabled bool 43 | TPPIssuerCN string 44 | CloudIssuerCN string 45 | FakeIssuerCN string 46 | ClientId string 47 | } 48 | 49 | func GetContext() *RunContext { 50 | 51 | c := RunContext{} 52 | 53 | c.TPPurl = os.Getenv("TPP_URL") 54 | c.TPPuser = os.Getenv("TPP_USER") 55 | c.TPPPassword = os.Getenv("TPP_PASSWORD") 56 | c.TPPZone = os.Getenv("TPP_ZONE") 57 | 58 | c.CloudUrl = os.Getenv("CLOUD_URL") 59 | c.CloudAPIkey = os.Getenv("CLOUD_APIKEY") 60 | c.CloudZone = os.Getenv("CLOUD_ZONE") 61 | 62 | c.TokenUrl = os.Getenv("TPP_TOKEN_URL") 63 | c.AccessToken = os.Getenv("ACCESS_TOKEN") 64 | c.ClientId = os.Getenv("TPP_CLIENT_ID") 65 | 66 | c.TPPTestingEnabled, _ = strconv.ParseBool(os.Getenv("VENAFI_TPP_TESTING")) 67 | c.CloudTestingEnabled, _ = strconv.ParseBool(os.Getenv("VENAFI_CLOUD_TESTING")) 68 | c.FakeTestingEnabled, _ = strconv.ParseBool(os.Getenv("VENAFI_FAKE_TESTING")) 69 | c.TokenTestingEnabled, _ = strconv.ParseBool(os.Getenv("VENAFI_TPP_TOKEN_TESTING")) 70 | 71 | c.TPPIssuerCN = os.Getenv("TPP_ISSUER_CN") 72 | c.CloudIssuerCN = os.Getenv("CLOUD_ISSUER_CN") 73 | c.FakeIssuerCN = os.Getenv("FAKE_ISSUER_CN") 74 | 75 | return &c 76 | } 77 | 78 | func getTppConnector(cfg *vcert.Config) (*tpp.Connector, error) { 79 | 80 | var connectionTrustBundle *x509.CertPool 81 | if cfg.ConnectionTrust != "" { 82 | connectionTrustBundle = x509.NewCertPool() 83 | if !connectionTrustBundle.AppendCertsFromPEM([]byte(cfg.ConnectionTrust)) { 84 | return nil, fmt.Errorf("failed to parse PEM trust bundle") 85 | } 86 | } 87 | tppConnector, err := tpp.NewConnector(cfg.BaseUrl, "", cfg.LogVerbose, connectionTrustBundle) 88 | if err != nil { 89 | return nil, fmt.Errorf("could not create TPP connector: %s", err) 90 | } 91 | 92 | return tppConnector, nil 93 | } 94 | 95 | func isTokenRefreshNeeded(b *backend, ctx context.Context, storage logical.Storage, secretName string) (bool, string, error) { 96 | secretEntry, err := b.getVenafiSecret(ctx, storage, secretName) 97 | if err != nil { 98 | return false, "", err 99 | } 100 | return secretEntry.NextRefresh.Before(time.Now()), secretEntry.RefreshToken2, nil 101 | } 102 | 103 | func updateAccessToken(b *backend, ctx context.Context, req *logical.Request, cfg *vcert.Config, role *roleEntry) error { 104 | b.mux.Lock() 105 | defer b.mux.Unlock() 106 | 107 | b.Logger().Info("Verifying again if token still needs refresh") 108 | refreshNeeded, refreshToken, err := isTokenRefreshNeeded(b, ctx, req.Storage, role.VenafiSecret) 109 | if err != nil { 110 | return err 111 | } 112 | if !refreshNeeded { 113 | b.Logger().Info("Refresh is not needed. Another process updated the tokens already") 114 | return nil // we're done, another thread beat us to it 115 | } 116 | 117 | tppConnector, _ := getTppConnector(cfg) 118 | 119 | var httpClient *http.Client 120 | httpClient, err = getHTTPClient(cfg.ConnectionTrust) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | tppConnector.SetHTTPClient(httpClient) 126 | 127 | b.Logger().Info("Refreshing access_token") 128 | var resp tpp.OauthRefreshAccessTokenResponse 129 | resp, err = tppConnector.RefreshAccessToken(&endpoint.Authentication{ 130 | RefreshToken: refreshToken, 131 | ClientId: cfg.Credentials.ClientId, 132 | Scope: "certificate:manage,revoke", 133 | }) 134 | if resp.Access_token != "" && resp.Refresh_token != "" { 135 | b.Logger().Info("Storing new token") 136 | err = storeAccessData(b, ctx, req, role, resp) 137 | } 138 | return err 139 | } 140 | 141 | func storeAccessData(b *backend, ctx context.Context, req *logical.Request, role *roleEntry, resp tpp.OauthRefreshAccessTokenResponse) error { 142 | 143 | if role.VenafiSecret == "" { 144 | return fmt.Errorf("Role " + role.Name + " does not have any Venafi secret associated") 145 | } 146 | 147 | venafiEntry, err := b.getVenafiSecret(ctx, req.Storage, role.VenafiSecret) 148 | if err != nil { 149 | return err 150 | } 151 | 152 | b.Logger().Info("swapping tokens") 153 | venafiEntry.RefreshToken2 = venafiEntry.RefreshToken 154 | b.Logger().Info("setting new access_token") 155 | venafiEntry.AccessToken = resp.Access_token 156 | b.Logger().Info("setting new refresh_token") 157 | venafiEntry.RefreshToken = resp.Refresh_token 158 | venafiEntry.NextRefresh = time.Now().Add(venafiEntry.RefreshInterval) 159 | b.Logger().Info(fmt.Sprintf("Setting new time refresh: %s", venafiEntry.NextRefresh.String())) 160 | // Store it 161 | b.Logger().Info("preparing tokens for storage") 162 | jsonEntry, err := logical.StorageEntryJSON(util.CredentialsRootPath+role.VenafiSecret, venafiEntry) 163 | 164 | if err != nil { 165 | b.Logger().Error("Error on creating new tokens into venafi secret:", err.Error()) 166 | return err 167 | } 168 | b.Logger().Info("storing new tokens") 169 | if err := req.Storage.Put(ctx, jsonEntry); err != nil { 170 | b.Logger().Error("Error on storing new tokens into Venafi secret:", err.Error()) 171 | return err 172 | } 173 | return nil 174 | } 175 | 176 | func getHTTPClient(trustBundlePem string) (*http.Client, error) { 177 | 178 | var netTransport = &http.Transport{ 179 | Proxy: http.ProxyFromEnvironment, 180 | DialContext: (&net.Dialer{ 181 | Timeout: 30 * time.Second, 182 | KeepAlive: 30 * time.Second, 183 | DualStack: true, 184 | }).DialContext, 185 | MaxIdleConns: 100, 186 | IdleConnTimeout: 90 * time.Second, 187 | TLSHandshakeTimeout: 10 * time.Second, 188 | ExpectContinueTimeout: 1 * time.Second, 189 | } 190 | tlsConfig := http.DefaultTransport.(*http.Transport).TLSClientConfig 191 | 192 | if tlsConfig == nil { 193 | tlsConfig = &tls.Config{} 194 | } else { 195 | tlsConfig = tlsConfig.Clone() 196 | } 197 | 198 | /* #nosec */ 199 | if trustBundlePem != "" { 200 | trustBundle, err := parseTrustBundlePEM(trustBundlePem) 201 | if err != nil { 202 | return nil, err 203 | } 204 | 205 | tlsConfig.RootCAs = trustBundle 206 | } 207 | 208 | tlsConfig.Renegotiation = tls.RenegotiateFreelyAsClient 209 | netTransport.TLSClientConfig = tlsConfig 210 | 211 | client := &http.Client{ 212 | Timeout: time.Second * 30, 213 | Transport: netTransport, 214 | } 215 | return client, nil 216 | } 217 | 218 | func parseTrustBundlePEM(trustBundlePem string) (*x509.CertPool, error) { 219 | var connectionTrustBundle *x509.CertPool 220 | 221 | if trustBundlePem != "" { 222 | connectionTrustBundle = x509.NewCertPool() 223 | if !connectionTrustBundle.AppendCertsFromPEM([]byte(trustBundlePem)) { 224 | return nil, fmt.Errorf("failed to parse PEM trust bundle") 225 | } 226 | } else { 227 | return nil, fmt.Errorf("trust bundle PEM data is empty") 228 | } 229 | 230 | return connectionTrustBundle, nil 231 | } 232 | 233 | func createConfigFromFieldData(data *venafiSecretEntry) (*vcert.Config, error) { 234 | 235 | cfg := &vcert.Config{} 236 | 237 | cfg.BaseUrl = data.URL 238 | cfg.Zone = data.Zone 239 | cfg.LogVerbose = true 240 | 241 | trustBundlePath := data.TrustBundleFile 242 | 243 | if trustBundlePath != "" { 244 | 245 | var trustBundlePEM string 246 | trustBundle, err := ioutil.ReadFile(trustBundlePath) 247 | 248 | if err != nil { 249 | return cfg, err 250 | } 251 | 252 | trustBundlePEM = string(trustBundle) 253 | cfg.ConnectionTrust = trustBundlePEM 254 | } 255 | 256 | cfg.ConnectorType = endpoint.ConnectorTypeTPP 257 | 258 | cfg.Credentials = &endpoint.Authentication{ 259 | AccessToken: data.AccessToken, 260 | RefreshToken: data.RefreshToken, 261 | ClientId: data.ClientId, 262 | } 263 | 264 | return cfg, nil 265 | } 266 | 267 | func getAccessData(cfg *vcert.Config) (tpp.OauthRefreshAccessTokenResponse, error) { 268 | 269 | var tokenInfoResponse tpp.OauthRefreshAccessTokenResponse 270 | tppConnector, _ := getTppConnector(cfg) 271 | httpClient, err := getHTTPClient(cfg.ConnectionTrust) 272 | 273 | if err != nil { 274 | return tokenInfoResponse, err 275 | } 276 | 277 | tppConnector.SetHTTPClient(httpClient) 278 | 279 | tokenInfoResponse, err = tppConnector.RefreshAccessToken(&endpoint.Authentication{ 280 | RefreshToken: cfg.Credentials.RefreshToken, 281 | ClientId: cfg.Credentials.ClientId, 282 | Scope: "certificate:manage,revoke", 283 | }) 284 | 285 | return tokenInfoResponse, err 286 | 287 | } 288 | 289 | func loadCertificateFromStorage(b *backend, ctx context.Context, req *logical.Request, certUID string, keyPassword string) (cert *VenafiCert, err error) { 290 | path := "certs/" + certUID 291 | 292 | entry, err := req.Storage.Get(ctx, path) 293 | if err != nil { 294 | return nil, fmt.Errorf("failed to read Venafi certificate: %s", err) 295 | } 296 | if entry == nil { 297 | return nil, vpkierror.CertEntryNotFound{EntryPath: path} 298 | } 299 | 300 | b.Logger().Info(fmt.Sprintf("Getting venafi certificate from storage with ID: %v", certUID)) 301 | 302 | if err := entry.DecodeJSON(&cert); err != nil { 303 | return nil, fmt.Errorf("error reading venafi configuration: %s", err.Error()) 304 | } 305 | b.Logger().Debug("certificate is:" + cert.Certificate) 306 | b.Logger().Debug("chain is:" + cert.CertificateChain) 307 | 308 | if keyPassword != "" { 309 | encryptedPrivateKeyPem, err := util.EncryptPrivateKey(cert.PrivateKey, keyPassword) 310 | if err != nil { 311 | return nil, fmt.Errorf("error opening private key: %s", err.Error()) 312 | } 313 | cert.PrivateKey = encryptedPrivateKeyPem 314 | } 315 | return cert, nil 316 | } 317 | -------------------------------------------------------------------------------- /plugin/pki/util_test.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Venafi/vault-pki-backend-venafi/plugin/util" 7 | ) 8 | 9 | func TestSHA1SUM(t *testing.T) { 10 | // Known SHA1SUM value of "hello" 11 | // echo -n "hello" | sha1sum 12 | SHA1SUMstringValue := "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d" 13 | 14 | s := "hello" 15 | SHA1SUMvalue := util.Sha1sum(s) 16 | if SHA1SUMstringValue != SHA1SUMvalue { 17 | t.Fatalf("sha1sum function is not outputting expected value") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /plugin/pki/vcert.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "fmt" 8 | "log" 9 | "net" 10 | "net/http" 11 | "os" 12 | "time" 13 | 14 | "github.com/hashicorp/vault/sdk/logical" 15 | 16 | "github.com/Venafi/vcert/v5" 17 | "github.com/Venafi/vcert/v5/pkg/endpoint" 18 | "github.com/Venafi/vcert/v5/pkg/verror" 19 | ) 20 | 21 | func (b *backend) ClientVenafi(ctx context.Context, req *logical.Request, role *roleEntry) ( 22 | 23 | endpoint.Connector, *vcert.Config, error) { 24 | 25 | cfg, err := b.getConfig(ctx, req, role, false) 26 | if err != nil { 27 | return nil, nil, err 28 | } 29 | 30 | client, err := vcert.NewClient(cfg) 31 | if err != nil { 32 | return nil, nil, fmt.Errorf("failed to get Venafi issuer client: %s", err) 33 | } 34 | 35 | return client, cfg, nil 36 | 37 | } 38 | 39 | func (b *backend) getConfig(ctx context.Context, req *logical.Request, role *roleEntry, includeRefreshToken bool) (*vcert.Config, error) { 40 | var cfg *vcert.Config 41 | 42 | venafiSecret, err := b.getVenafiSecret(ctx, req.Storage, role.VenafiSecret) 43 | if err != nil { 44 | return nil, err 45 | } 46 | if venafiSecret == nil { 47 | return nil, fmt.Errorf("unknown venafi secret %v", role.VenafiSecret) 48 | } 49 | 50 | var trustBundlePEM string 51 | if venafiSecret.TrustBundleFile != "" { 52 | b.Logger().Debug(fmt.Sprintf("Reading trust bundle from file: " + venafiSecret.TrustBundleFile)) 53 | 54 | trustBundle, err := os.ReadFile(venafiSecret.TrustBundleFile) 55 | if err != nil { 56 | return cfg, err 57 | } 58 | trustBundlePEM = string(trustBundle) 59 | } 60 | 61 | // If the role has a Zone declared, it takes priority over the Zone in the Venafi secret 62 | var zone string 63 | if role.Zone != "" { 64 | b.Logger().Debug(fmt.Sprintf("Using role zone: [%s]. Overrides venafi Secret zone: [%s]", role.Zone, venafiSecret.Zone)) 65 | zone = role.Zone 66 | } else { 67 | b.Logger().Debug(fmt.Sprintf("Using venafi secret zone: [%s]. Role zone not found. ", venafiSecret.Zone)) 68 | zone = venafiSecret.Zone 69 | } 70 | 71 | var netTransport = &http.Transport{ 72 | Proxy: http.ProxyFromEnvironment, 73 | DialContext: (&net.Dialer{ 74 | Timeout: role.ServerTimeout, 75 | KeepAlive: role.ServerTimeout, 76 | }).DialContext, 77 | MaxIdleConns: 100, 78 | IdleConnTimeout: 90 * time.Second, 79 | TLSHandshakeTimeout: 10 * time.Second, 80 | ExpectContinueTimeout: 1 * time.Second, 81 | } 82 | 83 | cfg = &vcert.Config{} 84 | cfg.BaseUrl = venafiSecret.URL 85 | cfg.Zone = zone 86 | cfg.LogVerbose = true 87 | if trustBundlePEM != "" { 88 | cfg.ConnectionTrust = trustBundlePEM 89 | } 90 | 91 | if venafiSecret.Fakemode { 92 | b.Logger().Debug("Using fakemode to issue certificate") 93 | cfg = &vcert.Config{ 94 | ConnectorType: endpoint.ConnectorTypeFake, 95 | LogVerbose: true, 96 | } 97 | 98 | } else if venafiSecret.URL != "" && venafiSecret.TppUser != "" && venafiSecret.TppPassword != "" { 99 | b.Logger().Debug(fmt.Sprintf("Using Venafi Platform with URL %s to issue certificate", venafiSecret.URL)) 100 | cfg.ConnectorType = endpoint.ConnectorTypeTPP 101 | cfg.Credentials = &endpoint.Authentication{ 102 | User: venafiSecret.TppUser, 103 | Password: venafiSecret.TppPassword, 104 | } 105 | 106 | } else if venafiSecret.URL != "" && venafiSecret.AccessToken != "" { 107 | b.Logger().Debug(fmt.Sprintf("Using Venafi Platform with URL %s to issue certificate", venafiSecret.URL)) 108 | cfg.ConnectorType = endpoint.ConnectorTypeTPP 109 | var refreshToken string 110 | if includeRefreshToken { 111 | refreshToken = venafiSecret.RefreshToken 112 | } 113 | cfg.Credentials = &endpoint.Authentication{ 114 | AccessToken: venafiSecret.AccessToken, 115 | RefreshToken: refreshToken, 116 | ClientId: venafiSecret.ClientId, 117 | } 118 | 119 | } else if venafiSecret.Apikey != "" { 120 | b.Logger().Debug("Using Venafi Cloud to issue certificate") 121 | cfg.ConnectorType = endpoint.ConnectorTypeCloud 122 | cfg.Credentials = &endpoint.Authentication{ 123 | APIKey: venafiSecret.Apikey, 124 | } 125 | 126 | } else { 127 | return nil, fmt.Errorf("failed to build config for Venafi issuer") 128 | } 129 | 130 | if role.ServerTimeout > 0 { 131 | cfg.Client = &http.Client{ 132 | Timeout: role.ServerTimeout, 133 | Transport: netTransport, 134 | } 135 | } 136 | 137 | var connectionTrustBundle *x509.CertPool 138 | 139 | if cfg.ConnectionTrust != "" { 140 | log.Println("Using trust bundle in custom http client") 141 | connectionTrustBundle = x509.NewCertPool() 142 | if !connectionTrustBundle.AppendCertsFromPEM([]byte(cfg.ConnectionTrust)) { 143 | return nil, fmt.Errorf("%w: failed to parse PEM trust bundle", verror.UserDataError) 144 | } 145 | netTransport.TLSClientConfig = &tls.Config{ 146 | RootCAs: connectionTrustBundle, 147 | MinVersion: tls.VersionTLS12, 148 | } 149 | cfg.Client.Transport = netTransport 150 | } 151 | 152 | return cfg, nil 153 | } 154 | -------------------------------------------------------------------------------- /plugin/pki/vcert_test.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "encoding/pem" 9 | "fmt" 10 | "log" 11 | "strings" 12 | "testing" 13 | 14 | "github.com/Venafi/vcert/v5" 15 | "github.com/Venafi/vcert/v5/pkg/certificate" 16 | "github.com/Venafi/vcert/v5/pkg/endpoint" 17 | "github.com/hashicorp/vault/sdk/logical" 18 | ) 19 | 20 | func TestPKIVcertIsWorking(t *testing.T) { 21 | var err error 22 | 23 | const cn = "testfake.example.com" 24 | vencfg := &vcert.Config{ConnectorType: endpoint.ConnectorTypeFake} 25 | client, err := vcert.NewClient(vencfg) 26 | 27 | req := &certificate.Request{} 28 | 29 | req.DNSNames = []string{cn} 30 | 31 | fmt.Printf("%v\n", req.DNSNames) 32 | req.Subject.CommonName = cn 33 | 34 | reader := rand.Reader 35 | bitSize := 2048 36 | 37 | key, err := rsa.GenerateKey(reader, bitSize) 38 | req.PrivateKey = key 39 | 40 | certificateRequest := x509.CertificateRequest{} 41 | certificateRequest.Subject = req.Subject 42 | certificateRequest.DNSNames = req.DNSNames 43 | certificateRequest.EmailAddresses = req.EmailAddresses 44 | certificateRequest.IPAddresses = req.IPAddresses 45 | certificateRequest.Attributes = req.Attributes 46 | 47 | csr, err := x509.CreateCertificateRequest(rand.Reader, &certificateRequest, key) 48 | if err != nil { 49 | t.Fatalf("bad: err: %v resp: %#v", err, nil) 50 | } 51 | err = req.SetCSR(csr) 52 | if err != nil { 53 | t.Fatalf("bad: err: %v \n", err) 54 | } 55 | 56 | pickupId, err := client.RequestCertificate(req) 57 | if err != nil { 58 | t.Fatalf("bad: err: %v resp: %#v", err, nil) 59 | } 60 | var cert *certificate.PEMCollection 61 | 62 | cert, err = client.RetrieveCertificate(&certificate.Request{PickupID: pickupId}) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | 67 | crt := strings.Join([]string{cert.Certificate}, "\n") 68 | pemBlock, _ := pem.Decode([]byte(crt)) 69 | parsedCertificate, err := x509.ParseCertificate(pemBlock.Bytes) 70 | haveCN := parsedCertificate.Subject.CommonName 71 | log.Println("CN is", haveCN) 72 | if haveCN != cn { 73 | t.Fatalf("CommonName doesn't match %s.", cn) 74 | } 75 | } 76 | 77 | func TestPKIVcertConfig(t *testing.T) { 78 | var resp *logical.Response 79 | var err error 80 | b, storage := createBackendWithStorage(t) 81 | 82 | roleData := map[string]interface{}{ 83 | "fakemode": "true", 84 | "ttl": "1h", 85 | "generate_lease": "true", 86 | "store_by_cn": "true", 87 | "store_pkey": "true", 88 | "store_by_serial": "true", 89 | "venafi_secret": "venafi", 90 | } 91 | 92 | roleReq := &logical.Request{ 93 | Operation: logical.UpdateOperation, 94 | Path: "roles/testrole", 95 | Storage: storage, 96 | Data: roleData, 97 | } 98 | 99 | resp, err = b.HandleRequest(context.Background(), roleReq) 100 | if err != nil || (resp != nil && resp.IsError()) { 101 | t.Fatalf("bad: err: %v resp: %#v", err, resp) 102 | } 103 | 104 | } 105 | 106 | func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) { 107 | config := logical.TestBackendConfig() 108 | config.StorageView = &logical.InmemStorage{} 109 | 110 | var err error 111 | b := Backend(config) 112 | err = b.Setup(context.Background(), config) 113 | if err != nil { 114 | t.Fatal(err) 115 | } 116 | return b, config.StorageView 117 | } 118 | -------------------------------------------------------------------------------- /plugin/pki/vpkierror/errors.go: -------------------------------------------------------------------------------- 1 | package vpkierror 2 | 3 | import "fmt" 4 | 5 | type VCertError struct{ error } 6 | type CertEntryNotFound struct { 7 | VCertError 8 | EntryPath string 9 | } 10 | 11 | func (e CertEntryNotFound) Error() string { 12 | return fmt.Sprintf("no entry found in path %s", e.EntryPath) 13 | } 14 | -------------------------------------------------------------------------------- /plugin/util/constants.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "fmt" 4 | 5 | const ( 6 | CredentialsRootPath = `venafi/` 7 | tokenMode = `TPP Token (access_token, refresh_token)` // #nosec G101 8 | tppMode = `TPP Credentials (tpp_user, tpp_password)` 9 | cloudMode = `Cloud API Key (apikey)` 10 | StoreByCNString = "cn" 11 | StoreByHASHstring = "hash" 12 | StoreBySerialString = "serial" 13 | ErrorTextValueMustBeLess = `"ttl" value must be less than "max_ttl" value` 14 | ErrorTextStoreByAndStoreByCNOrSerialConflict = `Can't specify both story_by and store_by_cn or store_by_serial options '` 15 | ErrorTextNoStoreAndStoreByCNOrSerialConflict = `Can't specify both no_store and store_by_cn or store_by_serial options '` 16 | ErrorTextNoStoreAndStoreByConflict = `Can't specify both no_store and store_by options '` 17 | ErrTextStoreByWrongOption = "Option store_by can be %s, %s or %s, not %s" 18 | ErrorTextVenafiSecretEmpty = `"venafi_secret" argument is required` 19 | ErrorTextURLEmpty = `"url" argument is required` 20 | ErrorTextZoneEmpty = `"zone" argument is required` 21 | ErrorTextInvalidMode = "invalid mode: fakemode or apikey or tpp credentials or tpp access token required" 22 | ErrorTextNeed2RefreshTokens = "secrets engine requires 2 refresh tokens for no impact token refresh" 23 | errorMultiModeMessage = `can't specify both: %s and %s modes in the same venafi secret` 24 | ) 25 | 26 | const ( 27 | Role_ttl_test_property = int(120) 28 | Ttl_test_property = int(48) 29 | ) 30 | 31 | var ( 32 | ErrorTextMixedTPPAndToken = fmt.Sprintf(errorMultiModeMessage, tppMode, tokenMode) 33 | ErrorTextMixedTPPAndCloud = fmt.Sprintf(errorMultiModeMessage, tppMode, cloudMode) 34 | ErrorTextMixedTokenAndCloud = fmt.Sprintf(errorMultiModeMessage, tokenMode, cloudMode) 35 | ) 36 | -------------------------------------------------------------------------------- /plugin/util/utils.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/sha1" 7 | "encoding/hex" 8 | "encoding/pem" 9 | "fmt" 10 | mathrand "math/rand" 11 | "net" 12 | "sort" 13 | "strconv" 14 | "strings" 15 | "time" 16 | 17 | "github.com/Venafi/vcert/v5/pkg/util" 18 | "github.com/youmark/pkcs8" 19 | ) 20 | 21 | func SliceContains(slice []string, item string) bool { 22 | set := make(map[string]struct{}, len(slice)) 23 | for _, s := range slice { 24 | set[s] = struct{}{} 25 | } 26 | 27 | _, ok := set[item] 28 | return ok 29 | } 30 | 31 | func GetHexFormatted(buf []byte, sep string) (string, error) { 32 | var ret bytes.Buffer 33 | for _, cur := range buf { 34 | if ret.Len() > 0 { 35 | if _, err := fmt.Fprint(&ret, sep); err != nil { 36 | return "", err 37 | } 38 | } 39 | if _, err := fmt.Fprintf(&ret, "%02x", cur); err != nil { 40 | return "", err 41 | } 42 | } 43 | return ret.String(), nil 44 | } 45 | 46 | // AddSeparatorToHexFormattedString gets a hexadecimal string and adds colon (:) every two characters 47 | // it returns a string with a colon every two chracters and any error during the convertion process 48 | // input: 6800b707811f0befb37f922b9e12f68eab8093 49 | // output: 68:00:b7:07:81:1f:0b:ef:b3:7f:92:2b:9e:12:f6:8e:ab:80:93 50 | func AddSeparatorToHexFormattedString(s string, sep string) (string, error) { 51 | var ret bytes.Buffer 52 | for n, v := range s { 53 | if n > 0 && n%2 == 0 { 54 | if _, err := fmt.Fprint(&ret, sep); err != nil { 55 | return "", err 56 | } 57 | } 58 | if _, err := fmt.Fprintf(&ret, "%c", v); err != nil { 59 | return "", err 60 | } 61 | } 62 | return ret.String(), nil 63 | } 64 | 65 | func NormalizeSerial(serial string) string { 66 | return strings.Replace(strings.ToLower(serial), ":", "-", -1) 67 | } 68 | 69 | func SameIpSlice(x, y []net.IP) bool { 70 | if len(x) != len(y) { 71 | return false 72 | } 73 | x1 := make([]string, len(x)) 74 | y1 := make([]string, len(y)) 75 | for i := range x { 76 | x1[i] = x[i].String() 77 | y1[i] = y[i].String() 78 | } 79 | sort.Strings(x1) 80 | sort.Strings(y1) 81 | for i := range x1 { 82 | if x1[i] != y1[i] { 83 | return false 84 | } 85 | } 86 | return true 87 | } 88 | 89 | func SameStringSlice(x, y []string) bool { 90 | if len(x) != len(y) { 91 | return false 92 | } 93 | x1 := make([]string, len(x)) 94 | y1 := make([]string, len(y)) 95 | copy(x1, x) 96 | copy(y1, y) 97 | sort.Strings(x1) 98 | sort.Strings(y1) 99 | for i := range x1 { 100 | if x1[i] != y1[i] { 101 | return false 102 | } 103 | } 104 | return true 105 | } 106 | 107 | func GetStatusCode(msg string) int64 { 108 | 109 | var statusCode int64 110 | splittedMsg := strings.Split(msg, ":") 111 | 112 | for i := 0; i < len(splittedMsg); i++ { 113 | 114 | current := splittedMsg[i] 115 | current = strings.TrimSpace(current) 116 | 117 | if current == "Invalid status" { 118 | 119 | status := splittedMsg[i+1] 120 | status = strings.TrimSpace(status) 121 | splittedStatus := strings.Split(status, " ") 122 | statusCode, _ = strconv.ParseInt(splittedStatus[0], 10, 64) 123 | break 124 | 125 | } 126 | } 127 | 128 | return statusCode 129 | } 130 | 131 | func CopyMap(m map[string]interface{}) map[string]interface{} { 132 | cp := make(map[string]interface{}) 133 | for k, v := range m { 134 | vm, ok := v.(map[string]interface{}) 135 | if ok { 136 | cp[k] = CopyMap(vm) 137 | } else { 138 | cp[k] = v 139 | } 140 | } 141 | 142 | return cp 143 | } 144 | 145 | func RandRunes(n int) string { 146 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz") 147 | b := make([]rune, n) 148 | for i := range b { 149 | /* #nosec */ 150 | b[i] = letterRunes[mathrand.Intn(len(letterRunes))] 151 | } 152 | return string(b) 153 | } 154 | 155 | func GetPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) { 156 | // this section makes some small changes to code from notary/tuf/utils/x509.go 157 | pemBlock, _ := pem.Decode(keyBytes) 158 | if pemBlock == nil { 159 | return nil, fmt.Errorf("no valid private key found") 160 | } 161 | 162 | var err error 163 | if util.X509IsEncryptedPEMBlock(pemBlock) { 164 | keyBytes, err = util.X509DecryptPEMBlock(pemBlock, []byte(passphrase)) 165 | if err != nil { 166 | return nil, fmt.Errorf("private key is encrypted, but could not decrypt it: %s", err.Error()) 167 | } 168 | keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes}) 169 | } 170 | 171 | return keyBytes, nil 172 | } 173 | 174 | func EncryptPrivateKey(privateKey string, password string) (string, error) { 175 | var encryptedPrivateKeyPem string 176 | var err error 177 | encryptedPrivateKeyPem, err = EncryptPkcs1PrivateKey(privateKey, password) 178 | if err != nil { 179 | // We try PKCS8 180 | encryptedPrivateKeyPem, err = encryptPkcs8PrivateKey(privateKey, password) 181 | if err != nil { 182 | return "", err 183 | } 184 | } 185 | return encryptedPrivateKeyPem, nil 186 | } 187 | 188 | func DecryptPkcs8PrivateKey(privateKey string, password string) (string, error) { 189 | 190 | block, _ := pem.Decode([]byte(privateKey)) 191 | key, _, err := pkcs8.ParsePrivateKey(block.Bytes, []byte(password)) 192 | 193 | if err != nil { 194 | return "", err 195 | } 196 | 197 | pemType := "PRIVATE KEY" 198 | 199 | privateKeyBytes, err := pkcs8.MarshalPrivateKey(key, nil, nil) 200 | 201 | if err != nil { 202 | return "", err 203 | } 204 | 205 | pemBytes := pem.EncodeToMemory(&pem.Block{Type: pemType, Bytes: privateKeyBytes}) 206 | 207 | return string(pemBytes), nil 208 | } 209 | 210 | func EncryptPkcs1PrivateKey(privateKey string, password string) (string, error) { 211 | 212 | block, _ := pem.Decode([]byte(privateKey)) 213 | 214 | keyType := util.GetPrivateKeyType(privateKey, password) 215 | var encrypted *pem.Block 216 | var err error 217 | if keyType == "RSA PRIVATE KEY" { 218 | encrypted, err = util.X509EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", block.Bytes, []byte(password), util.PEMCipherAES256) 219 | if err != nil { 220 | return "", nil 221 | } 222 | } else if keyType == "EC PRIVATE KEY" { 223 | encrypted, err = util.X509EncryptPEMBlock(rand.Reader, "EC PRIVATE KEY", block.Bytes, []byte(password), util.PEMCipherAES256) 224 | if err != nil { 225 | return "", nil 226 | } 227 | } else { 228 | return "", fmt.Errorf("unable to encrypt key in PKCS1 format") 229 | } 230 | return string(pem.EncodeToMemory(encrypted)), nil 231 | } 232 | 233 | func encryptPkcs8PrivateKey(privateKey string, password string) (string, error) { 234 | block, _ := pem.Decode([]byte(privateKey)) 235 | key, _, err := pkcs8.ParsePrivateKey(block.Bytes, []byte("")) 236 | if err != nil { 237 | return "", err 238 | } 239 | privateKeyBytes1, err := pkcs8.MarshalPrivateKey(key, []byte(password), nil) 240 | if err != nil { 241 | return "", err 242 | } 243 | 244 | keyType := "ENCRYPTED PRIVATE KEY" 245 | 246 | // Generate a pem block with the private key 247 | keyPemBytes := pem.EncodeToMemory(&pem.Block{ 248 | Type: keyType, 249 | Bytes: privateKeyBytes1, 250 | }) 251 | encryptedPrivateKeyPem := string(keyPemBytes) 252 | return encryptedPrivateKeyPem, nil 253 | } 254 | 255 | // ShortDurationString will trim 256 | func ShortDurationString(d time.Duration) string { 257 | s := d.String() 258 | if strings.HasSuffix(s, "m0s") { 259 | s = s[:len(s)-2] 260 | } 261 | if strings.HasSuffix(s, "h0m") { 262 | s = s[:len(s)-2] 263 | } 264 | return s 265 | } 266 | 267 | func Sha1sum(s string) string { 268 | //nolint 269 | hash := sha1.New() 270 | buffer := []byte(s) 271 | hash.Write(buffer) 272 | return hex.EncodeToString(hash.Sum(nil)) 273 | } 274 | 275 | // we may want to enhance this function when we update to Go 1.18, since generics are only supported starting from that version 276 | func RemoveDuplicateStr(strSlice *[]string) { 277 | allKeys := make(map[string]bool) 278 | var list []string 279 | for _, item := range *strSlice { 280 | if _, value := allKeys[item]; !value { 281 | allKeys[item] = true 282 | list = append(list, item) 283 | } 284 | } 285 | *strSlice = list 286 | } 287 | 288 | func StringSlicesEqual(a []string, b []string) bool { 289 | if len(a) != len(b) { 290 | return false 291 | } 292 | for i, v := range a { 293 | if v != b[i] { 294 | return false 295 | } 296 | } 297 | return true 298 | } 299 | 300 | func AreDNSNamesCorrect(actualAltNames []string, expectedCNNames []string, expectedAltNames []string) bool { 301 | 302 | //There is no cn names. Check expectedAltNames only. Is it possible? 303 | if len(expectedCNNames) == 0 { 304 | if len(actualAltNames) != len(expectedAltNames) { 305 | return false 306 | 307 | } else if !SameStringSlice(actualAltNames, expectedAltNames) { 308 | return false 309 | } 310 | } else { 311 | 312 | if len(actualAltNames) < len(expectedAltNames) { 313 | return false 314 | } 315 | 316 | for i := range expectedAltNames { 317 | expectedName := expectedAltNames[i] 318 | found := false 319 | 320 | for j := range actualAltNames { 321 | 322 | if actualAltNames[j] == expectedName { 323 | found = true 324 | break 325 | } 326 | } 327 | if !found { 328 | return false 329 | } 330 | } 331 | 332 | //Checking expectedCNNames 333 | allNames := append(expectedAltNames, expectedCNNames...) 334 | for i := range actualAltNames { 335 | name := actualAltNames[i] 336 | found := false 337 | 338 | for j := range allNames { 339 | 340 | if allNames[j] == name { 341 | found = true 342 | break 343 | } 344 | } 345 | if !found { 346 | return false 347 | } 348 | } 349 | 350 | } 351 | 352 | return true 353 | } 354 | -------------------------------------------------------------------------------- /scripts/config/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | EXPOSE 80 443 4 | 5 | ENV CT_VER 0.19.5 6 | ENV CT_URL https://releases.hashicorp.com/consul-template/${CT_VER}/consul-template_${CT_VER}_linux_amd64.zip 7 | RUN curl -O $CT_URL && unzip consul-template_${CT_VER}_linux_amd64.zip -d /usr/local/bin 8 | 9 | ADD nginx.service /etc/service/nginx/run 10 | ADD consul-template.service /etc/service/consul-template/run 11 | RUN chmod +x /etc/service/nginx/run 12 | RUN chmod +x /etc/service/consul-template/run 13 | 14 | 15 | ADD nginx.conf /etc/nginx/conf.d/default.conf 16 | ADD consul-template.hcl /etc/consul-template.hcl 17 | 18 | CMD ["/usr/bin/runsvdir", "/etc/service"] 19 | -------------------------------------------------------------------------------- /scripts/config/nginx/cert/cloud-nginx.crt.ctmpl: -------------------------------------------------------------------------------- 1 | {{ with secret "venafi-pki/issue/cloud" "common_name=cloud.venafi.example.com" }} 2 | {{ .Data.certificate }}{{ end }} 3 | 4 | -------------------------------------------------------------------------------- /scripts/config/nginx/cert/cloud-nginx.key.ctmpl: -------------------------------------------------------------------------------- 1 | {{ with secret "venafi-pki/issue/cloud" "common_name=cloud.venafi.example.com" }} 2 | {{ .Data.private_key }}{{ end }} 3 | -------------------------------------------------------------------------------- /scripts/config/nginx/cert/fake-nginx.crt.ctmpl: -------------------------------------------------------------------------------- 1 | {{ with secret "venafi-pki/issue/fake" "common_name=fake.example.com" }} 2 | {{ .Data.certificate }}{{ end }} 3 | 4 | -------------------------------------------------------------------------------- /scripts/config/nginx/cert/fake-nginx.key.ctmpl: -------------------------------------------------------------------------------- 1 | {{ with secret "venafi-pki/issue/fake" "common_name=fake.example.com" }} 2 | {{ .Data.private_key }}{{ end }} 3 | -------------------------------------------------------------------------------- /scripts/config/nginx/cert/tpp-nginx.crt.ctmpl: -------------------------------------------------------------------------------- 1 | {{ with secret "venafi-pki/issue/tpp" "common_name=tpp.venqa.venafi.com" }} 2 | {{ .Data.certificate }}{{ end }} 3 | 4 | -------------------------------------------------------------------------------- /scripts/config/nginx/cert/tpp-nginx.key.ctmpl: -------------------------------------------------------------------------------- 1 | {{ with secret "venafi-pki/issue/tpp" "common_name=tpp.venqa.venafi.com" }} 2 | {{ .Data.private_key }}{{ end }} 3 | -------------------------------------------------------------------------------- /scripts/config/nginx/consul-template-cloud.hcl: -------------------------------------------------------------------------------- 1 | consul { 2 | auth { 3 | enabled = false 4 | } 5 | address = "127.0.0.1:8500" 6 | 7 | retry { 8 | enabled = true 9 | attempts = 12 10 | backoff = "250ms" 11 | max_backoff = "1m" 12 | } 13 | 14 | ssl { 15 | enabled = false 16 | } 17 | } 18 | 19 | reload_signal = "SIGHUP" 20 | kill_signal = "SIGINT" 21 | max_stale = "10m" 22 | log_level = "info" 23 | pid_file = "/tmp/venafi-demo-consul-template.pid" 24 | 25 | vault { 26 | address = "http://127.0.0.1:8200" 27 | grace = "5m" 28 | unwrap_token = false 29 | renew_token = false 30 | } 31 | 32 | template { 33 | source = "scripts/config/nginx/cert/cloud-nginx.crt.ctmpl" 34 | destination = "scripts/config/nginx/cert/cloud-nginx.crt" 35 | } 36 | 37 | template { 38 | source = "scripts/config/nginx/cert/cloud-nginx.key.ctmpl" 39 | destination = "scripts/config/nginx/cert/cloud-nginx.key" 40 | command = "/bin/sh -c 'scripts/tools/nginx.sh cloud 2443'" 41 | } 42 | -------------------------------------------------------------------------------- /scripts/config/nginx/consul-template-fake.hcl: -------------------------------------------------------------------------------- 1 | consul { 2 | auth { 3 | enabled = false 4 | } 5 | address = "127.0.0.1:8500" 6 | 7 | retry { 8 | enabled = true 9 | attempts = 12 10 | backoff = "250ms" 11 | max_backoff = "1m" 12 | } 13 | 14 | ssl { 15 | enabled = false 16 | } 17 | } 18 | 19 | reload_signal = "SIGHUP" 20 | kill_signal = "SIGINT" 21 | max_stale = "10m" 22 | log_level = "info" 23 | pid_file = "/tmp/venafi-demo-consul-template.pid" 24 | 25 | vault { 26 | address = "http://127.0.0.1:8200" 27 | grace = "5m" 28 | unwrap_token = false 29 | renew_token = false 30 | } 31 | 32 | template { 33 | source = "scripts/config/nginx/cert/fake-nginx.crt.ctmpl" 34 | destination = "scripts/config/nginx/cert/fake-nginx.crt" 35 | } 36 | 37 | template { 38 | source = "scripts/config/nginx/cert/fake-nginx.key.ctmpl" 39 | destination = "scripts/config/nginx/cert/fake-nginx.key" 40 | command = "/bin/sh -c 'scripts/tools/nginx.sh fake 1443'" 41 | } 42 | -------------------------------------------------------------------------------- /scripts/config/nginx/consul-template-tpp.hcl: -------------------------------------------------------------------------------- 1 | consul { 2 | auth { 3 | enabled = false 4 | } 5 | address = "127.0.0.1:8500" 6 | 7 | retry { 8 | enabled = true 9 | attempts = 12 10 | backoff = "250ms" 11 | max_backoff = "1m" 12 | } 13 | 14 | ssl { 15 | enabled = false 16 | } 17 | } 18 | 19 | reload_signal = "SIGHUP" 20 | kill_signal = "SIGINT" 21 | max_stale = "10m" 22 | log_level = "info" 23 | pid_file = "/tmp/venafi-demo-consul-template.pid" 24 | 25 | vault { 26 | address = "http://127.0.0.1:8200" 27 | grace = "5m" 28 | unwrap_token = false 29 | renew_token = false 30 | } 31 | 32 | template { 33 | source = "scripts/config/nginx/cert/tpp-nginx.crt.ctmpl" 34 | destination = "scripts/config/nginx/cert/tpp-nginx.crt" 35 | } 36 | 37 | template { 38 | source = "scripts/config/nginx/cert/tpp-nginx.key.ctmpl" 39 | destination = "scripts/config/nginx/cert/tpp-nginx.key" 40 | command = "/bin/sh -c 'scripts/tools/nginx.sh tpp 3443'" 41 | } 42 | -------------------------------------------------------------------------------- /scripts/config/nginx/consul-template.service: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec consul-template \ 4 | -consul=consul:8500 \ 5 | -template "/etc/consul-templates/nginx.conf:/etc/nginx/conf.d/app.conf:service nginx reload" \ 6 | -template "/etc/consul-templates/index.html:/usr/share/nginx/html/index.html" 7 | -------------------------------------------------------------------------------- /scripts/config/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443 ssl; 3 | 4 | ssl_certificate /etc/nginx/ssl/nginx.crt; 5 | ssl_certificate_key /etc/nginx/ssl/nginx.key; 6 | 7 | location / { 8 | root /usr/share/nginx/html; 9 | index index.html index.htm; 10 | } 11 | 12 | error_page 500 502 503 504 /50x.html; 13 | location = /50x.html { 14 | root /usr/share/nginx/html; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/config/nginx/nginx.service: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | /usr/sbin/nginx -c /etc/nginx/nginx.conf -t && \ 4 | exec /usr/sbin/nginx -c /etc/nginx/nginx.conf -g "daemon off;" 5 | -------------------------------------------------------------------------------- /scripts/config/vault/vault-config-with-consul.hcl: -------------------------------------------------------------------------------- 1 | backend "consul" { 2 | address = "consul:8500" 3 | advertise_addr = "http://127.0.0.1:8200" 4 | path = "vault" 5 | scheme = "http" 6 | } 7 | 8 | listener "tcp" { 9 | address = "0.0.0.0:8200" 10 | tls_disable = 1 11 | } 12 | 13 | disable_mlock = true 14 | ui = true 15 | plugin_directory = "/vault_plugin/" 16 | -------------------------------------------------------------------------------- /scripts/config/vault/vault-config.hcl.sed: -------------------------------------------------------------------------------- 1 | plugin_directory = "__PLUGIN_DIR__" 2 | -------------------------------------------------------------------------------- /scripts/tools/check-certificate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cert=$1 3 | echo "##########################" 4 | echo "" 5 | echo "Checking certificate $cert" 6 | echo "" 7 | echo "##########################" 8 | openssl x509 -in $cert -text -noout -certopt no_header,no_version,no_serial,no_signame,no_pubkey,no_sigdump,no_aux||echo -e "\n!!!!Can't decode certificate.!!!!\n" -------------------------------------------------------------------------------- /scripts/tools/nginx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ROLE=$1 3 | PORT=$2 4 | cont=vault-demo-nginx-${ROLE} 5 | 6 | docker rm -f $cont || echo "Container $cont doesn't exists" 7 | docker run --name $cont -v $(pwd)/scripts/config/nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro \ 8 | -v $(pwd)/scripts/config/nginx/cert/${ROLE}-nginx.key:/etc/nginx/ssl/nginx.key \ 9 | -v $(pwd)/scripts/config/nginx/cert/${ROLE}-nginx.crt:/etc/nginx/ssl/nginx.crt \ 10 | -p ${PORT}:443 -d nginx 11 | echo "NGINX start check URL https://localhost:${PORT}" -------------------------------------------------------------------------------- /scripts/tools/vault/cubbyhole-wrap-token.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 1 ]; then 4 | echo "usage: $0 [vault path]" 5 | exit 1 6 | fi 7 | 8 | : ${VAULT_ADDR?"env variable VAULT_ADDR needs to be set"} 9 | : ${VAULT_TOKEN?"env variable VAULT_TOKEN needs to be set"} 10 | 11 | path=$1 12 | 13 | echo $(curl -s \ 14 | -H "X-Vault-Token: $VAULT_TOKEN" \ 15 | -H "X-Vault-Wrap-TTL: 120s" \ 16 | -H "Content-Type: application/json" \ 17 | -X GET \ 18 | $VAULT_ADDR/v1$path \ 19 | | jq -r .wrap_info.token) 20 | -------------------------------------------------------------------------------- /scripts/tools/vault/unwrap-token.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 1 ]; then 4 | echo "usage: $0 token" 5 | exit 1 6 | fi 7 | 8 | : ${VAULT_ADDR?"env variable VAULT_ADDR needs to be set"} 9 | : ${VAULT_TOKEN?"env variable VAULT_TOKEN needs to be set"} 10 | 11 | token=$1 12 | 13 | echo $(curl -s \ 14 | -H "X-Vault-Token: $VAULT_TOKEN" \ 15 | -X POST \ 16 | --data "{\"token\": \"$token\"}" \ 17 | $VAULT_ADDR/v1/sys/wrapping/unwrap \ 18 | | jq -r '[.data, .errors] | del(.[] | nulls) | .[0]') 19 | -------------------------------------------------------------------------------- /scripts/tools/vault/vault-read.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 1 ]; then 4 | echo "usage: $0 [vault path]" 5 | exit 1 6 | fi 7 | 8 | : ${VAULT_ADDR?"env variable VAULT_ADDR needs to be set"} 9 | : ${VAULT_TOKEN?"env variable VAULT_TOKEN needs to be set"} 10 | 11 | path=$1 12 | 13 | echo $(curl -s \ 14 | -H "X-Vault-Token: $VAULT_TOKEN" \ 15 | -H "Content-Type: application/json" \ 16 | -X GET \ 17 | $VAULT_ADDR/v1$path \ 18 | | jq -r .data) 19 | -------------------------------------------------------------------------------- /scripts/tools/vault/vault-write.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 2 ]; then 4 | echo "usage: $0 [vault path] [creds file path]" 5 | exit 1 6 | fi 7 | 8 | : ${VAULT_ADDR?"env variable VAULT_ADDR needs to be set"} 9 | : ${VAULT_TOKEN?"env variable VAULT_TOKEN needs to be set"} 10 | 11 | path=$1 12 | creds=$2 13 | 14 | echo $(curl -H "X-Vault-Token: $VAULT_TOKEN" \ 15 | -H "Content-Type: application/json" \ 16 | -X POST \ 17 | -sd @$creds \ 18 | $VAULT_ADDR/v1$path) 19 | -------------------------------------------------------------------------------- /scripts/tools/vault/wrap-token.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 1 ]; then 4 | echo "usage: $0 [path to file to wrap/secure]" 5 | exit 1 6 | fi 7 | 8 | : ${VAULT_ADDR?"env variable VAULT_ADDR needs to be set"} 9 | : ${VAULT_TOKEN?"env variable VAULT_TOKEN needs to be set"} 10 | 11 | data=$1 12 | 13 | echo $(curl -s \ 14 | -H "X-Vault-Token: $VAULT_TOKEN" \ 15 | -H "X-Vault-Wrap-TTL: 60" \ 16 | -H "Content-Type: application/json" \ 17 | -X POST \ 18 | --data @$data \ 19 | $VAULT_ADDR/v1/sys/wrapping/wrap \ 20 | | jq -r .wrap_info.token) 21 | -------------------------------------------------------------------------------- /scripts/tools/wait-for-it.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### adopted from https://github.com/ucalgary/wait-for-it 4 | ### so it works for the alpine image the official Vault image derives from 5 | 6 | ########################################################## 7 | # # 8 | # waits until a given TCP host:port is available # 9 | # when available runs a provided command with args # 10 | # # 11 | ########################################################## 12 | 13 | cmdname=$(basename $0) 14 | 15 | echoerr() { if [ $QUIET -ne 1 ]; then echo "$@" 1>&2; fi } 16 | 17 | usage() 18 | { 19 | cat << USAGE >&2 20 | Usage: 21 | $cmdname host:port [-s] [-t timeout] [-- command args] 22 | -h HOST Host or IP under test 23 | -p PORT TCP port under test 24 | -s Only execute subcommand if the test succeeds 25 | -q Do not output any status messages 26 | -t TIMEOUT Timeout in seconds, zero for no timeout 27 | -- COMMAND ARGS Execute command with args after the test finishes 28 | USAGE 29 | exit 1 30 | } 31 | 32 | wait_for() 33 | { 34 | if [ $TIMEOUT -gt 0 ]; then 35 | echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT" 36 | else 37 | echoerr "$cmdname: waiting for $HOST:$PORT without a timeout" 38 | fi 39 | start_ts=$(date +%s) 40 | while : 41 | do 42 | if [ $ISBUSY -eq 1 ]; then 43 | nc -z $HOST $PORT 44 | result=$? 45 | else 46 | (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1 47 | result=$? 48 | fi 49 | if [ $result -eq 0 ]; then 50 | end_ts=$(date +%s) 51 | echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds" 52 | break 53 | fi 54 | sleep 1 55 | done 56 | return $result 57 | } 58 | 59 | wait_for_wrapper() 60 | { 61 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 62 | if [ $QUIET -eq 1 ]; then 63 | timeout $BUSYTIMEFLAG $TIMEOUT $0 -q -c -h $HOST -p $PORT -t $TIMEOUT & 64 | else 65 | timeout $BUSYTIMEFLAG $TIMEOUT $0 -c -h $HOST -p $PORT -t $TIMEOUT & 66 | fi 67 | PID=$! 68 | trap "kill -INT -$PID" INT 69 | wait $PID 70 | RESULT=$? 71 | if [[ $RESULT -ne 0 ]]; then 72 | echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT" 73 | fi 74 | return $RESULT 75 | } 76 | 77 | while getopts csqt:h:p: OPT; do 78 | case "$OPT" in 79 | c) 80 | CHILD=1 81 | ;; 82 | s) 83 | STRICT=1 84 | ;; 85 | q) 86 | QUIET=1 87 | ;; 88 | t) 89 | TIMEOUT=$OPTARG 90 | ;; 91 | h) 92 | HOST=$OPTARG 93 | ;; 94 | p) 95 | PORT=$OPTARG 96 | ;; 97 | esac 98 | done 99 | 100 | shift `expr $OPTIND - 1` 101 | 102 | CLI="$@" 103 | TIMEOUT=${TIMEOUT:-15} 104 | STRICT=${STRICT:-0} 105 | CHILD=${CHILD:-0} 106 | QUIET=${QUIET:-0} 107 | 108 | if [ "$HOST" = "" ] || [ "$PORT" = "" ]; then 109 | echoerr "Error: you need to provide a host and port to test." 110 | usage 111 | fi 112 | 113 | # check to see if timeout is from busybox 114 | TIMEOUT_PATH=$(realpath $(which timeout)) 115 | BUSYBOX="busybox" 116 | if test "${TIMEOUT_PATH#*$BUSYBOX}" != "$BUSYBOX"; then 117 | ISBUSY=1 118 | BUSYTIMEFLAG="-t" 119 | else 120 | ISBUSY=0 121 | BUSYTIMEFLAG="" 122 | fi 123 | 124 | if [ $CHILD -gt 0 ]; then 125 | wait_for 126 | RESULT=$? 127 | exit $RESULT 128 | else 129 | if [ $TIMEOUT -gt 0 ]; then 130 | wait_for_wrapper 131 | RESULT=$? 132 | else 133 | wait_for 134 | RESULT=$? 135 | fi 136 | fi 137 | 138 | if [ ! -z "$CLI" ]; then 139 | if [ $RESULT -ne 0 ] && [ $STRICT -eq 1 ]; then 140 | echoerr "$cmdname: strict mode, refusing to execute subprocess" 141 | exit $RESULT 142 | fi 143 | exec $CLI 144 | else 145 | exit $RESULT 146 | fi 147 | -------------------------------------------------------------------------------- /scripts/tools/what-is-my-host-ip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo `ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1'` 3 | --------------------------------------------------------------------------------