├── .gitignore ├── LICENSE ├── MANIFEST.in ├── Pipfile ├── Pipfile.lock ├── README.md ├── setup.py ├── tests ├── test_common.py └── test_main.py └── vaultssh ├── __init__.py ├── auth.py ├── common.py └── vaultssh.py /.gitignore: -------------------------------------------------------------------------------- 1 | # VSCode Files 2 | .vscode/* 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Joshua Gilman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | exclude .gitignore 2 | exclude Pipfile 3 | exclude Pipfile.lock -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pylint = "*" 8 | pipenv-setup = "*" 9 | autopep8 = "*" 10 | pep8 = "*" 11 | bandit = "*" 12 | pytest = "*" 13 | pytest-cov = "*" 14 | pytest-mock = "*" 15 | twine = "*" 16 | keyring = "*" 17 | 18 | [packages] 19 | hvac = "*" 20 | click = "*" 21 | 22 | [requires] 23 | python_version = "3.6" 24 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "638b6f7b6da1482af0546e41a8b5a5b3fe2c08bf1d3568f10d1c56c432cad8cf" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "certifi": { 20 | "hashes": [ 21 | "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", 22 | "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" 23 | ], 24 | "version": "==2019.11.28" 25 | }, 26 | "chardet": { 27 | "hashes": [ 28 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 29 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 30 | ], 31 | "version": "==3.0.4" 32 | }, 33 | "click": { 34 | "hashes": [ 35 | "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", 36 | "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" 37 | ], 38 | "index": "pypi", 39 | "version": "==7.0" 40 | }, 41 | "hvac": { 42 | "hashes": [ 43 | "sha256:7e25794c6860155fa6f80420d7ac095e770c0d9088d7615048261574979267ec", 44 | "sha256:f0b035f41d9fbf49a14e721b65f495f685060e870084323d1e8e0ee0eb024df5" 45 | ], 46 | "index": "pypi", 47 | "version": "==0.9.6" 48 | }, 49 | "idna": { 50 | "hashes": [ 51 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", 52 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" 53 | ], 54 | "version": "==2.8" 55 | }, 56 | "requests": { 57 | "hashes": [ 58 | "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", 59 | "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" 60 | ], 61 | "version": "==2.22.0" 62 | }, 63 | "six": { 64 | "hashes": [ 65 | "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", 66 | "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" 67 | ], 68 | "version": "==1.14.0" 69 | }, 70 | "urllib3": { 71 | "hashes": [ 72 | "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", 73 | "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" 74 | ], 75 | "version": "==1.25.8" 76 | } 77 | }, 78 | "develop": { 79 | "appdirs": { 80 | "hashes": [ 81 | "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", 82 | "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" 83 | ], 84 | "version": "==1.4.3" 85 | }, 86 | "astroid": { 87 | "hashes": [ 88 | "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", 89 | "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42" 90 | ], 91 | "version": "==2.3.3" 92 | }, 93 | "attrs": { 94 | "hashes": [ 95 | "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", 96 | "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" 97 | ], 98 | "version": "==19.3.0" 99 | }, 100 | "autopep8": { 101 | "hashes": [ 102 | "sha256:0f592a0447acea0c2b0a9602be1e4e3d86db52badd2e3c84f0193bfd89fd3a43" 103 | ], 104 | "index": "pypi", 105 | "version": "==1.5" 106 | }, 107 | "bandit": { 108 | "hashes": [ 109 | "sha256:336620e220cf2d3115877685e264477ff9d9abaeb0afe3dc7264f55fa17a3952", 110 | "sha256:41e75315853507aa145d62a78a2a6c5e3240fe14ee7c601459d0df9418196065" 111 | ], 112 | "index": "pypi", 113 | "version": "==1.6.2" 114 | }, 115 | "black": { 116 | "hashes": [ 117 | "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", 118 | "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539" 119 | ], 120 | "markers": "python_version >= '3.6'", 121 | "version": "==19.10b0" 122 | }, 123 | "bleach": { 124 | "hashes": [ 125 | "sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", 126 | "sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa" 127 | ], 128 | "version": "==3.1.0" 129 | }, 130 | "cached-property": { 131 | "hashes": [ 132 | "sha256:3a026f1a54135677e7da5ce819b0c690f156f37976f3e30c5430740725203d7f", 133 | "sha256:9217a59f14a5682da7c4b8829deadbfc194ac22e9908ccf7c8820234e80a1504" 134 | ], 135 | "version": "==1.5.1" 136 | }, 137 | "cerberus": { 138 | "hashes": [ 139 | "sha256:302e6694f206dd85cb63f13fd5025b31ab6d38c99c50c6d769f8fa0b0f299589" 140 | ], 141 | "version": "==1.3.2" 142 | }, 143 | "certifi": { 144 | "hashes": [ 145 | "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", 146 | "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" 147 | ], 148 | "version": "==2019.11.28" 149 | }, 150 | "cffi": { 151 | "hashes": [ 152 | "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", 153 | "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04", 154 | "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5", 155 | "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54", 156 | "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba", 157 | "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57", 158 | "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396", 159 | "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12", 160 | "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97", 161 | "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43", 162 | "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db", 163 | "sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3", 164 | "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b", 165 | "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579", 166 | "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346", 167 | "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159", 168 | "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652", 169 | "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e", 170 | "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a", 171 | "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506", 172 | "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f", 173 | "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d", 174 | "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c", 175 | "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20", 176 | "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858", 177 | "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc", 178 | "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a", 179 | "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3", 180 | "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e", 181 | "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410", 182 | "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25", 183 | "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b", 184 | "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d" 185 | ], 186 | "version": "==1.13.2" 187 | }, 188 | "chardet": { 189 | "hashes": [ 190 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 191 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 192 | ], 193 | "version": "==3.0.4" 194 | }, 195 | "click": { 196 | "hashes": [ 197 | "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", 198 | "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" 199 | ], 200 | "index": "pypi", 201 | "version": "==7.0" 202 | }, 203 | "colorama": { 204 | "hashes": [ 205 | "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", 206 | "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" 207 | ], 208 | "version": "==0.4.3" 209 | }, 210 | "coverage": { 211 | "hashes": [ 212 | "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3", 213 | "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c", 214 | "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0", 215 | "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477", 216 | "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a", 217 | "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf", 218 | "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691", 219 | "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73", 220 | "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987", 221 | "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894", 222 | "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e", 223 | "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef", 224 | "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf", 225 | "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68", 226 | "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8", 227 | "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954", 228 | "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2", 229 | "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40", 230 | "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc", 231 | "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc", 232 | "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e", 233 | "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d", 234 | "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f", 235 | "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc", 236 | "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301", 237 | "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea", 238 | "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb", 239 | "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af", 240 | "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52", 241 | "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37", 242 | "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0" 243 | ], 244 | "version": "==5.0.3" 245 | }, 246 | "cryptography": { 247 | "hashes": [ 248 | "sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c", 249 | "sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595", 250 | "sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad", 251 | "sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651", 252 | "sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2", 253 | "sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff", 254 | "sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d", 255 | "sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42", 256 | "sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d", 257 | "sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e", 258 | "sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912", 259 | "sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793", 260 | "sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13", 261 | "sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7", 262 | "sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0", 263 | "sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879", 264 | "sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f", 265 | "sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9", 266 | "sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2", 267 | "sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf", 268 | "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8" 269 | ], 270 | "version": "==2.8" 271 | }, 272 | "distlib": { 273 | "hashes": [ 274 | "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21" 275 | ], 276 | "version": "==0.3.0" 277 | }, 278 | "docutils": { 279 | "hashes": [ 280 | "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", 281 | "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" 282 | ], 283 | "version": "==0.16" 284 | }, 285 | "first": { 286 | "hashes": [ 287 | "sha256:8d8e46e115ea8ac652c76123c0865e3ff18372aef6f03c22809ceefcea9dec86", 288 | "sha256:ff285b08c55f8c97ce4ea7012743af2495c9f1291785f163722bd36f6af6d3bf" 289 | ], 290 | "version": "==2.0.2" 291 | }, 292 | "gitdb2": { 293 | "hashes": [ 294 | "sha256:1b6df1433567a51a4a9c1a5a0de977aa351a405cc56d7d35f3388bad1f630350", 295 | "sha256:96bbb507d765a7f51eb802554a9cfe194a174582f772e0d89f4e87288c288b7b" 296 | ], 297 | "version": "==2.0.6" 298 | }, 299 | "gitpython": { 300 | "hashes": [ 301 | "sha256:9c2398ffc3dcb3c40b27324b316f08a4f93ad646d5a6328cafbb871aa79f5e42", 302 | "sha256:c155c6a2653593ccb300462f6ef533583a913e17857cfef8fc617c246b6dc245" 303 | ], 304 | "version": "==3.0.5" 305 | }, 306 | "idna": { 307 | "hashes": [ 308 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", 309 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" 310 | ], 311 | "version": "==2.8" 312 | }, 313 | "importlib-metadata": { 314 | "hashes": [ 315 | "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", 316 | "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" 317 | ], 318 | "markers": "python_version < '3.8'", 319 | "version": "==1.5.0" 320 | }, 321 | "isort": { 322 | "hashes": [ 323 | "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", 324 | "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" 325 | ], 326 | "version": "==4.3.21" 327 | }, 328 | "jeepney": { 329 | "hashes": [ 330 | "sha256:0ba6d8c597e9bef1ebd18aaec595f942a264e25c1a48f164d46120eacaa2e9bb", 331 | "sha256:6f45dce1125cf6c58a1c88123d3831f36a789f9204fbad3172eac15f8ccd08d0" 332 | ], 333 | "markers": "sys_platform == 'linux'", 334 | "version": "==0.4.2" 335 | }, 336 | "keyring": { 337 | "hashes": [ 338 | "sha256:1f393f7466314068961c7e1d508120c092bd71fa54e3d93b76180b526d4abc56", 339 | "sha256:24ae23ab2d6adc59138339e56843e33ec7b0a6b2f06302662477085c6c0aca00" 340 | ], 341 | "index": "pypi", 342 | "version": "==21.1.0" 343 | }, 344 | "lazy-object-proxy": { 345 | "hashes": [ 346 | "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", 347 | "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", 348 | "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", 349 | "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", 350 | "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", 351 | "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", 352 | "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", 353 | "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", 354 | "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", 355 | "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", 356 | "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", 357 | "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", 358 | "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", 359 | "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", 360 | "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", 361 | "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", 362 | "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", 363 | "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", 364 | "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", 365 | "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", 366 | "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" 367 | ], 368 | "version": "==1.4.3" 369 | }, 370 | "mccabe": { 371 | "hashes": [ 372 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 373 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 374 | ], 375 | "version": "==0.6.1" 376 | }, 377 | "more-itertools": { 378 | "hashes": [ 379 | "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c", 380 | "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507" 381 | ], 382 | "version": "==8.2.0" 383 | }, 384 | "orderedmultidict": { 385 | "hashes": [ 386 | "sha256:04070bbb5e87291cc9bfa51df413677faf2141c73c61d2a5f7b26bea3cd882ad", 387 | "sha256:43c839a17ee3cdd62234c47deca1a8508a3f2ca1d0678a3bf791c87cf84adbf3" 388 | ], 389 | "version": "==1.0.1" 390 | }, 391 | "packaging": { 392 | "hashes": [ 393 | "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", 394 | "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" 395 | ], 396 | "version": "==19.2" 397 | }, 398 | "pathspec": { 399 | "hashes": [ 400 | "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424", 401 | "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96" 402 | ], 403 | "version": "==0.7.0" 404 | }, 405 | "pbr": { 406 | "hashes": [ 407 | "sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b", 408 | "sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488" 409 | ], 410 | "version": "==5.4.4" 411 | }, 412 | "pep517": { 413 | "hashes": [ 414 | "sha256:5ce351f3be71d01bb094d63253854b6139931fcaba8e2f380c02102136c51e40", 415 | "sha256:882e2eeeffe39ccd6be6122d98300df18d80950cb5f449766d64149c94c5614a" 416 | ], 417 | "version": "==0.8.1" 418 | }, 419 | "pep8": { 420 | "hashes": [ 421 | "sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee", 422 | "sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374" 423 | ], 424 | "index": "pypi", 425 | "version": "==1.7.1" 426 | }, 427 | "pip-shims": { 428 | "hashes": [ 429 | "sha256:1cc3e2e4e5d5863edd4760d2032b180a6ef81719277fe95404df1bb0e58b7261", 430 | "sha256:b5bb01c4394a2e0260bddb4cfdc7e6fdd9d6e61c8febd18c3594e2ea2596c190" 431 | ], 432 | "version": "==0.5.0" 433 | }, 434 | "pipenv-setup": { 435 | "hashes": [ 436 | "sha256:18ce5474261bab22b9a3cd919d70909b578b57438d452ebb88dbe22ca70f2ef2", 437 | "sha256:5b69f8a91dd922806577d4e0c84acda1ce274657aab800749f088b46fcfe76cb" 438 | ], 439 | "index": "pypi", 440 | "version": "==3.0.1" 441 | }, 442 | "pipfile": { 443 | "hashes": [ 444 | "sha256:f7d9f15de8b660986557eb3cc5391aa1a16207ac41bc378d03f414762d36c984" 445 | ], 446 | "version": "==0.0.2" 447 | }, 448 | "pkginfo": { 449 | "hashes": [ 450 | "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb", 451 | "sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32" 452 | ], 453 | "version": "==1.5.0.1" 454 | }, 455 | "plette": { 456 | "extras": [ 457 | "validation" 458 | ], 459 | "hashes": [ 460 | "sha256:46402c03e36d6eadddad2a5125990e322dd74f98160c8f2dcd832b2291858a26", 461 | "sha256:d6c9b96981b347bddd333910b753b6091a2c1eb2ef85bb373b4a67c9d91dca16" 462 | ], 463 | "version": "==0.2.3" 464 | }, 465 | "pluggy": { 466 | "hashes": [ 467 | "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", 468 | "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" 469 | ], 470 | "version": "==0.13.1" 471 | }, 472 | "py": { 473 | "hashes": [ 474 | "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", 475 | "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" 476 | ], 477 | "version": "==1.8.1" 478 | }, 479 | "pycodestyle": { 480 | "hashes": [ 481 | "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", 482 | "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" 483 | ], 484 | "version": "==2.5.0" 485 | }, 486 | "pycparser": { 487 | "hashes": [ 488 | "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" 489 | ], 490 | "version": "==2.19" 491 | }, 492 | "pygments": { 493 | "hashes": [ 494 | "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", 495 | "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" 496 | ], 497 | "version": "==2.5.2" 498 | }, 499 | "pylint": { 500 | "hashes": [ 501 | "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", 502 | "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4" 503 | ], 504 | "index": "pypi", 505 | "version": "==2.4.4" 506 | }, 507 | "pyparsing": { 508 | "hashes": [ 509 | "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", 510 | "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" 511 | ], 512 | "version": "==2.4.6" 513 | }, 514 | "pytest": { 515 | "hashes": [ 516 | "sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d", 517 | "sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6" 518 | ], 519 | "index": "pypi", 520 | "version": "==5.3.5" 521 | }, 522 | "pytest-cov": { 523 | "hashes": [ 524 | "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b", 525 | "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626" 526 | ], 527 | "index": "pypi", 528 | "version": "==2.8.1" 529 | }, 530 | "pytest-mock": { 531 | "hashes": [ 532 | "sha256:b35eb281e93aafed138db25c8772b95d3756108b601947f89af503f8c629413f", 533 | "sha256:cb67402d87d5f53c579263d37971a164743dc33c159dfb4fb4a86f37c5552307" 534 | ], 535 | "index": "pypi", 536 | "version": "==2.0.0" 537 | }, 538 | "pyyaml": { 539 | "hashes": [ 540 | "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6", 541 | "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf", 542 | "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5", 543 | "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e", 544 | "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811", 545 | "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e", 546 | "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d", 547 | "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20", 548 | "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689", 549 | "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994", 550 | "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615" 551 | ], 552 | "version": "==5.3" 553 | }, 554 | "readme-renderer": { 555 | "hashes": [ 556 | "sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f", 557 | "sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d" 558 | ], 559 | "version": "==24.0" 560 | }, 561 | "regex": { 562 | "hashes": [ 563 | "sha256:07b39bf943d3d2fe63d46281d8504f8df0ff3fe4c57e13d1656737950e53e525", 564 | "sha256:0932941cdfb3afcbc26cc3bcf7c3f3d73d5a9b9c56955d432dbf8bbc147d4c5b", 565 | "sha256:0e182d2f097ea8549a249040922fa2b92ae28be4be4895933e369a525ba36576", 566 | "sha256:10671601ee06cf4dc1bc0b4805309040bb34c9af423c12c379c83d7895622bb5", 567 | "sha256:23e2c2c0ff50f44877f64780b815b8fd2e003cda9ce817a7fd00dea5600c84a0", 568 | "sha256:26ff99c980f53b3191d8931b199b29d6787c059f2e029b2b0c694343b1708c35", 569 | "sha256:27429b8d74ba683484a06b260b7bb00f312e7c757792628ea251afdbf1434003", 570 | "sha256:3e77409b678b21a056415da3a56abfd7c3ad03da71f3051bbcdb68cf44d3c34d", 571 | "sha256:4e8f02d3d72ca94efc8396f8036c0d3bcc812aefc28ec70f35bb888c74a25161", 572 | "sha256:4eae742636aec40cf7ab98171ab9400393360b97e8f9da67b1867a9ee0889b26", 573 | "sha256:6a6ae17bf8f2d82d1e8858a47757ce389b880083c4ff2498dba17c56e6c103b9", 574 | "sha256:6a6ba91b94427cd49cd27764679024b14a96874e0dc638ae6bdd4b1a3ce97be1", 575 | "sha256:7bcd322935377abcc79bfe5b63c44abd0b29387f267791d566bbb566edfdd146", 576 | "sha256:98b8ed7bb2155e2cbb8b76f627b2fd12cf4b22ab6e14873e8641f266e0fb6d8f", 577 | "sha256:bd25bb7980917e4e70ccccd7e3b5740614f1c408a642c245019cff9d7d1b6149", 578 | "sha256:d0f424328f9822b0323b3b6f2e4b9c90960b24743d220763c7f07071e0778351", 579 | "sha256:d58e4606da2a41659c84baeb3cfa2e4c87a74cec89a1e7c56bee4b956f9d7461", 580 | "sha256:e3cd21cc2840ca67de0bbe4071f79f031c81418deb544ceda93ad75ca1ee9f7b", 581 | "sha256:e6c02171d62ed6972ca8631f6f34fa3281d51db8b326ee397b9c83093a6b7242", 582 | "sha256:e7c7661f7276507bce416eaae22040fd91ca471b5b33c13f8ff21137ed6f248c", 583 | "sha256:ecc6de77df3ef68fee966bb8cb4e067e84d4d1f397d0ef6fce46913663540d77" 584 | ], 585 | "version": "==2020.1.8" 586 | }, 587 | "requests": { 588 | "hashes": [ 589 | "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", 590 | "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" 591 | ], 592 | "version": "==2.22.0" 593 | }, 594 | "requests-toolbelt": { 595 | "hashes": [ 596 | "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f", 597 | "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0" 598 | ], 599 | "version": "==0.9.1" 600 | }, 601 | "requirementslib": { 602 | "hashes": [ 603 | "sha256:50731ac1052473e4c7df59a44a1f3aa20f32e687110bc05d73c3b4109eebc23d", 604 | "sha256:8b594ab8b6280ee97cffd68fc766333345de150124d5b76061dd575c3a21fe5a" 605 | ], 606 | "version": "==1.5.3" 607 | }, 608 | "secretstorage": { 609 | "hashes": [ 610 | "sha256:15da8a989b65498e29be338b3b279965f1b8f09b9668bd8010da183024c8bff6", 611 | "sha256:b5ec909dde94d4ae2fa26af7c089036997030f0cf0a5cb372b4cccabd81c143b" 612 | ], 613 | "markers": "sys_platform == 'linux'", 614 | "version": "==3.1.2" 615 | }, 616 | "six": { 617 | "hashes": [ 618 | "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", 619 | "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" 620 | ], 621 | "version": "==1.14.0" 622 | }, 623 | "smmap2": { 624 | "hashes": [ 625 | "sha256:0555a7bf4df71d1ef4218e4807bbf9b201f910174e6e08af2e138d4e517b4dde", 626 | "sha256:29a9ffa0497e7f2be94ca0ed1ca1aa3cd4cf25a1f6b4f5f87f74b46ed91d609a" 627 | ], 628 | "version": "==2.0.5" 629 | }, 630 | "stevedore": { 631 | "hashes": [ 632 | "sha256:01d9f4beecf0fbd070ddb18e5efb10567801ba7ef3ddab0074f54e3cd4e91730", 633 | "sha256:e0739f9739a681c7a1fda76a102b65295e96a144ccdb552f2ae03c5f0abe8a14" 634 | ], 635 | "version": "==1.31.0" 636 | }, 637 | "toml": { 638 | "hashes": [ 639 | "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", 640 | "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" 641 | ], 642 | "version": "==0.10.0" 643 | }, 644 | "tomlkit": { 645 | "hashes": [ 646 | "sha256:32c10cc16ded7e4101c79f269910658cc2a0be5913f1252121c3cd603051c269", 647 | "sha256:96e6369288571799a3052c1ef93b9de440e1ab751aa045f435b55e9d3bcd0690" 648 | ], 649 | "version": "==0.5.8" 650 | }, 651 | "tqdm": { 652 | "hashes": [ 653 | "sha256:01464d5950e9a07a8e463c2767883d9616c099c6502f6c7ef4e2e11d3065bd35", 654 | "sha256:5865f5fef9d739864ff341ddaa69894173ebacedb1aaafcf014de56343d01d5c" 655 | ], 656 | "version": "==4.42.0" 657 | }, 658 | "twine": { 659 | "hashes": [ 660 | "sha256:c1af8ca391e43b0a06bbc155f7f67db0bf0d19d284bfc88d1675da497a946124", 661 | "sha256:d561a5e511f70275e5a485a6275ff61851c16ffcb3a95a602189161112d9f160" 662 | ], 663 | "index": "pypi", 664 | "version": "==3.1.1" 665 | }, 666 | "typed-ast": { 667 | "hashes": [ 668 | "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", 669 | "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", 670 | "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", 671 | "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", 672 | "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", 673 | "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", 674 | "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", 675 | "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", 676 | "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", 677 | "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", 678 | "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", 679 | "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", 680 | "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", 681 | "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", 682 | "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", 683 | "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", 684 | "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", 685 | "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", 686 | "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", 687 | "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", 688 | "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" 689 | ], 690 | "markers": "implementation_name == 'cpython' and python_version < '3.8'", 691 | "version": "==1.4.1" 692 | }, 693 | "typing": { 694 | "hashes": [ 695 | "sha256:91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23", 696 | "sha256:c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36", 697 | "sha256:f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714" 698 | ], 699 | "version": "==3.7.4.1" 700 | }, 701 | "urllib3": { 702 | "hashes": [ 703 | "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", 704 | "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" 705 | ], 706 | "version": "==1.25.8" 707 | }, 708 | "vistir": { 709 | "hashes": [ 710 | "sha256:33f8e905d40a77276b3d5310c8b57c1479a4e46930042b4894fcf7ed60ad76c4", 711 | "sha256:e47afdec8baf35032a8d17116765f751ecd2f2146d47e5af457c5de1fe5a334e" 712 | ], 713 | "version": "==0.5.0" 714 | }, 715 | "wcwidth": { 716 | "hashes": [ 717 | "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603", 718 | "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8" 719 | ], 720 | "version": "==0.1.8" 721 | }, 722 | "webencodings": { 723 | "hashes": [ 724 | "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", 725 | "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" 726 | ], 727 | "version": "==0.5.1" 728 | }, 729 | "wheel": { 730 | "hashes": [ 731 | "sha256:8788e9155fe14f54164c1b9eb0a319d98ef02c160725587ad60f14ddc57b6f96", 732 | "sha256:df277cb51e61359aba502208d680f90c0493adec6f0e848af94948778aed386e" 733 | ], 734 | "version": "==0.34.2" 735 | }, 736 | "wrapt": { 737 | "hashes": [ 738 | "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" 739 | ], 740 | "version": "==1.11.2" 741 | }, 742 | "zipp": { 743 | "hashes": [ 744 | "sha256:ccc94ed0909b58ffe34430ea5451f07bc0c76467d7081619a454bf5c98b89e28", 745 | "sha256:feae2f18633c32fc71f2de629bfb3bd3c9325cd4419642b1f1da42ee488d9b98" 746 | ], 747 | "markers": "python_version < '3.8'", 748 | "version": "==2.1.0" 749 | } 750 | } 751 | } 752 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VaultSSH 2 | > CLI tool for signing SSH public keys using the Vault SSH endpoint 3 | 4 | **NOTE: This has been deprecated in favor of a pure Go version of the client: https://github.com/jmgilman/vssh*** 5 | 6 | VaultSSH is a simple command line tool written in Python which automates the process of signing SSH public keys using the [Hashicorp Vault](https://www.vaultproject.io/) SSH backend. In environments which have configured Vault as a trusted CA and use it to issue signed keys for authenticating against SSH servers, this tool provides a simple wrapper which handles all the backend communication and produces a signed public key ready for the end-user to authenticate with. 7 | 8 | This tool assumes that your Vault environment has been properly configured for signing SSH keys. Hashicorp provides extensive documentation along with examples on how to perform this configuration [in their docs](https://www.vaultproject.io/docs/secrets/ssh/signed-ssh-certificates/). 9 | 10 | ## Installation 11 | 12 | ```sh 13 | pip install vaultssh 14 | ``` 15 | 16 | ## Usage example 17 | 18 | VaultSSH takes two arguments: a path to the public SSH key to sign and the [Vault role](https://learn.hashicorp.com/vault/identity-access-management/iam-policies) that should be used to sign it. Please refer to the [Vault documentation](https://www.vaultproject.io/docs/secrets/ssh/signed-ssh-certificates/) to learn more about configuring SSH key signing. 19 | 20 | ```sh 21 | $ vaultssh ~/.ssh/id_rsa.pub myrole 22 | ``` 23 | 24 | VaultSSH will automatically detect if you had previously authenticated with the Vault server by looking for an existing token in the default Vault environment variable (`VAULT_TOKEN`) or Vault token file (~/.vault-token). You can override this behavior and provide your own token by passing the --token flag. If a token is not found, or has expired, the tool will prompt you to authenticate with the Vault backend to fetch a new token (Note: only RADIUS is currently supported). By default the tool will persist the newly acquired token in the Vault token file, however this can be disabled by passing the --no-persist flag. 25 | 26 | VaultSSH will automatically detect the location of the Vault server by using the default Vault environment variable (`VAULT_ADDR`). You can override this behavior by passing the --server flag. 27 | 28 | ```sh 29 | $ vaultssh --server https://myvault.com:8200 ~/.ssh/id_rsa.pub myrole 30 | ``` 31 | 32 | If the signing process succeeds, VaultSSH will automatically write the signed certificate to the same directory as the given public key: 33 | 34 | ```sh 35 | $ vaultssh ~/.ssh/id_rsa.pub myrole 36 | Signed key saved to /home/josh/.ssh/id_rsa-cert.pub 37 | ``` 38 | 39 | ## Development setup 40 | 41 | This project was developed using Pipenv as the virtual environment wrapper. To install all dependencies, run the following command at the root of the project: 42 | 43 | ```sh 44 | pipenv install --dev 45 | ``` 46 | 47 | ## Release History 48 | 49 | * 1.0.0 50 | * Initial release 51 | 52 | ## Meta 53 | 54 | Joshua Gilman – joshuagilman@gmail.com 55 | 56 | Distributed under the MIT license. See ``LICENSE`` for more information. 57 | 58 | [https://github.com/jmgilman](https://github.com/jmgilman) 59 | 60 | ## Contributing 61 | 62 | 1. Fork it () 63 | 2. Create your feature branch (`git checkout -b feature/fooBar`) 64 | 3. Commit your changes (`git commit -am 'Add some fooBar'`) 65 | 4. Push to the branch (`git push origin feature/fooBar`) 66 | 5. Create a new Pull Request 67 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | from setuptools import setup, find_packages 3 | 4 | # The directory containing this file 5 | HERE = pathlib.Path(__file__).parent 6 | 7 | # The text of the README file 8 | README = (HERE / "README.md").read_text() 9 | 10 | setup( 11 | name="vaultssh", 12 | version="1.0.0", 13 | description="CLI tool for signing SSH public keys using the Vault SSH endpoint", 14 | long_description=README, 15 | long_description_content_type="text/markdown", 16 | url="https://github.com/jmgilman/VaultSSH", 17 | author="Joshua Gilman", 18 | author_email="joshuagilman@gmail.com", 19 | license="MIT", 20 | packages=find_packages(), 21 | include_package_data=True, 22 | install_requires=[ 23 | "certifi==2019.11.28", 24 | "chardet==3.0.4", 25 | "click==7.0", 26 | "hvac==0.9.6", 27 | "idna==2.8", 28 | "requests==2.22.0", 29 | "six==1.14.0", 30 | "urllib3==1.25.8", 31 | ], 32 | dependency_links=[], 33 | entry_points={"console_scripts": ["vaultssh=vaultssh.vaultssh:main"]}, 34 | ) 35 | -------------------------------------------------------------------------------- /tests/test_common.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import vaultssh.common as common 4 | 5 | 6 | def test_get_signed_key_path(): 7 | test_path = "/home/user/.ssh/id_rsa.pub" 8 | correct_path = "/home/user/.ssh/id_rsa-cert.pub" 9 | 10 | assert common.get_signed_key_path(test_path) == correct_path 11 | 12 | 13 | def test_get_token_file(): 14 | path = common.get_token_file() 15 | assert path == os.path.join(os.path.expanduser("~"), ".vault-token") 16 | 17 | 18 | def test_write_signed_key(): 19 | filename = "test.pub" 20 | contents = "test" 21 | 22 | common.write_signed_key(filename, contents) 23 | 24 | with open(common.get_signed_key_path(filename), "r") as f: 25 | assert f.read() == "test" 26 | 27 | os.remove(common.get_signed_key_path(filename)) 28 | -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from click.testing import CliRunner 4 | from vaultssh.vaultssh import main 5 | 6 | 7 | def test_main(mocker): 8 | runner = CliRunner() 9 | with runner.isolated_filesystem(): 10 | result = runner.invoke(main) 11 | assert 'Missing argument "SSH_PUBLIC_KEY"' in result.output 12 | 13 | with open("test.pub", "w") as f: 14 | f.write("Fake key data") 15 | result = runner.invoke(main, ["test.pub"]) 16 | assert 'Missing argument "ROLE".' in result.output 17 | 18 | os.environ["VAULT_ADDR"] = "" 19 | result = runner.invoke(main, ["test.pub", "role"]) 20 | assert "No URL found" in result.output 21 | 22 | os.environ["VAULT_ADDR"] = "htttp://foo.bar" 23 | mocker.patch("vaultssh.auth.authenticate", return_value=None) 24 | mocker.patch("hvac.Client.is_authenticated", return_value=True) 25 | mocker.patch( 26 | "hvac.Client.write", return_value={"data": {"signed_key": "test"}} 27 | ) 28 | mocker.patch( 29 | "vaultssh.common.get_signed_key_path", return_value="test.txt" 30 | ) 31 | 32 | result = runner.invoke(main, ["test.pub", "role"]) 33 | assert "Signed key saved to test.txt" in result.output 34 | with open("test.txt", "r") as f: 35 | assert f.read() == "test" 36 | assert result.exit_code == 0 37 | -------------------------------------------------------------------------------- /vaultssh/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmgilman/VaultSSH-Python/beb7b6fc6d964da6295216c3ebd9faea89b310b4/vaultssh/__init__.py -------------------------------------------------------------------------------- /vaultssh/auth.py: -------------------------------------------------------------------------------- 1 | import getpass 2 | import logging 3 | import os 4 | 5 | import click 6 | import hvac 7 | import vaultssh.common as common 8 | 9 | 10 | def authenticate(client, persist): 11 | """ Attempts to authenticate using the user provided authentication method 12 | 13 | Args: 14 | client (hvac.Client): The client to authenticate with 15 | persist (bool): Whether or not to persist the new token 16 | 17 | Returns: 18 | None 19 | """ 20 | # List possible authentication methods 21 | click.echo("Available authentication types:") 22 | methods = sorted([key.title() for key in AUTH_METHODS]) 23 | for method in methods: 24 | click.echo(f"* {method}\n") 25 | 26 | # Collect which one to use 27 | chosen_method = "" 28 | while chosen_method.lower() not in AUTH_METHODS: 29 | chosen_method = click.prompt( 30 | "Please select the authentication method to use: " 31 | ) 32 | 33 | # Attempt to authenticate 34 | logging.debug(f"Calling function for {chosen_method.lower()}") 35 | token = AUTH_METHODS[chosen_method.lower()](client) 36 | 37 | # Persist the token 38 | if persist: 39 | common.write_token(token) 40 | 41 | 42 | def radius(client): 43 | """ Attempts to authenticate against a Vault RADIUS backend 44 | 45 | Args: 46 | client (hvac.Client): The hvac client to authenticate with 47 | 48 | Returns: 49 | The newly retrieved token 50 | """ 51 | success = False 52 | result = [] 53 | 54 | # Attempt to login using provided username/password 55 | while not success: 56 | click.echo("Please enter your RADIUS username and password:") 57 | username = click.prompt("Username: ") 58 | password = getpass.getpass("Password: ") 59 | 60 | try: 61 | result = client.auth.radius.login(username, password) 62 | except hvac.exceptions.InvalidRequest: # Thrown when a login fails 63 | click.echo("Invalid username/password") 64 | logging.debug("Server threw InvalidRequest", exc_info=True) 65 | continue 66 | 67 | success = True 68 | 69 | logging.info(f"Server returned token: {result['auth']['client_token']}") 70 | return result["auth"]["client_token"] # Newly retrieved token 71 | 72 | 73 | AUTH_METHODS = { 74 | "radius": radius, 75 | } 76 | -------------------------------------------------------------------------------- /vaultssh/common.py: -------------------------------------------------------------------------------- 1 | import getpass 2 | import logging 3 | import os 4 | 5 | import click 6 | import hvac 7 | 8 | 9 | def configure_logging(verbosity): 10 | """ Configures the root logger 11 | 12 | Args: 13 | verbosity (int): The level of verbosity specified by the user 14 | 15 | Returns: 16 | None 17 | """ 18 | levels = [logging.WARNING, logging.INFO, logging.DEBUG] 19 | 20 | # Ignore values higher than 2 21 | verbosity = 2 if verbosity >= 3 else verbosity 22 | 23 | format = "%(levelname)s: %(message)s" 24 | logging.basicConfig(format=format, level=levels[verbosity]) 25 | 26 | 27 | def get_signed_key_path(key_file): 28 | """ Builds the correct path for a signed SSH public key 29 | 30 | Takes a file object pointing towards an SSH public key and breaks it down 31 | to build the correct path for the associated signed SSH public key. By 32 | default, SSH expects the file to be named as: -cert.pub 33 | 34 | Args: 35 | key_file (str): The SSH public key file to base the cert file off of 36 | 37 | Returns: 38 | A string file path to the correct SSH public key cert file 39 | """ 40 | key_dir = os.path.dirname(key_file) 41 | key_parts = os.path.splitext(os.path.basename(key_file)) 42 | new_name = key_parts[0] + "-cert" + key_parts[1] 43 | 44 | return os.path.join(key_dir, new_name) 45 | 46 | 47 | def get_token_file(): 48 | """ Returns the default location for the Vault token file 49 | 50 | Default location as defined by Vault is ~/.vault-token 51 | 52 | Returns: 53 | A string path to the token file 54 | """ 55 | user_home = os.path.expanduser("~") 56 | return os.path.join(user_home, ".vault-token") 57 | 58 | 59 | def write_signed_key(public_key_path, contents): 60 | """ Writes a signed SSH public key to the correct path 61 | 62 | Given the path to the original SSH public key, determines the correct 63 | location to write the signed SSH public key and then writes the contents to 64 | that location. 65 | 66 | Args: 67 | public_key_path (str): The path to the original SSH public key 68 | contents (str): The contents of the signed public SSH key 69 | 70 | Returns: 71 | None 72 | """ 73 | path = get_signed_key_path(public_key_path) 74 | 75 | logging.info(f"Writing signed key to {path}") 76 | try: 77 | with open(path, "w") as f: 78 | f.write(contents) 79 | except Exception: 80 | logging.fatal("Failed to write signed public key", exc_info=True) 81 | exit(1) 82 | 83 | click.echo("Signed key saved to " + path) 84 | 85 | 86 | def write_token(token): 87 | """ Persists a token by writing it to the default Vault token file 88 | 89 | Args: 90 | token (string): The token to write 91 | 92 | Returns: 93 | None 94 | """ 95 | token_file = get_token_file() 96 | logging.info(f"Persisting token to {token_file}") 97 | 98 | try: 99 | with open(token_file, "w") as f: 100 | f.write(token) 101 | except: 102 | logging.warning( 103 | f"Failed to persist token at {token_file}", exc_info=True 104 | ) 105 | -------------------------------------------------------------------------------- /vaultssh/vaultssh.py: -------------------------------------------------------------------------------- 1 | """ Main file """ 2 | 3 | import getpass 4 | import logging 5 | import os 6 | 7 | import click 8 | import hvac 9 | import vaultssh.auth as auth 10 | import vaultssh.common as common 11 | 12 | 13 | @click.command() 14 | @click.option( 15 | "--persist/--no-persist", 16 | help="Whether to persist newly acquired tokens", 17 | default=True, 18 | ) 19 | @click.option( 20 | "-s", "--server", help="The URL for the Vault server to query against" 21 | ) 22 | @click.option("-t", "--token", help="The Vault token to authenticate with") 23 | @click.option("-v", "--verbose", count=True) 24 | @click.argument("ssh_public_key", type=click.File("r")) 25 | @click.argument("role") 26 | def main(ssh_public_key, role, persist, server, token, verbose): 27 | """ Sign SSH_PUBLIC_KEY using the given Vault ROLE 28 | 29 | \b 30 | SSH_PUBLIC_KEY must be a file path to a valid SSH public key file 31 | ROLE must be a valid configured role in the Vault server 32 | """ 33 | # Configure logging 34 | common.configure_logging(verbose) 35 | 36 | # Instantiate client 37 | client = hvac.Client() 38 | 39 | # Check for url 40 | client.url = server if server else client.url 41 | if not client.url: 42 | logging.info("No url address to Vault server supplied") 43 | click.echo( 44 | "No URL found - please set VAULT_ADDR environment variable or manually pass a server url" 45 | ) 46 | exit(1) 47 | 48 | # Check for authentication 49 | client.token = token if token else client.token 50 | 51 | logging.debug(f"Token set to {client.token}") 52 | logging.debug(f"URL set to {client.url}") 53 | 54 | if not client.is_authenticated(): 55 | auth.authenticate(client, persist) 56 | 57 | # Sign key 58 | try: 59 | result = client.write( 60 | "ssh/sign/" + role, public_key=ssh_public_key.read() 61 | ) 62 | except hvac.exceptions.InvalidRequest: 63 | logging.fatal("Error signing SSH key", exc_info=True) 64 | exit(1) 65 | 66 | # Write the signed certificate 67 | common.write_signed_key(ssh_public_key.name, result["data"]["signed_key"]) 68 | --------------------------------------------------------------------------------