castellan-3.0.1/0000775000175000017500000000000013645116331013506 5ustar zuulzuul00000000000000castellan-3.0.1/ChangeLog0000664000175000017500000002361213645116331015264 0ustar zuulzuul00000000000000CHANGES ======= 3.0.1 ----- * Fix stale references to renamed constant 3.0.0 ----- * Removes context "validation" * Moving common objects under KeyManager * Drop use of six * Implements KeyManager's option discovery * Fix coverage tests 2.0.0 ----- * [ussuri][goal] Drop python 2.7 support and testing * Add Wiki links to readme 1.4.0 ----- * Fix "is" usage with literals * Switch to Ussuri jobs * PDF Documentation Build tox target * Update master for stable/train 1.3.1 ----- * Add bindep.txt * Add Python 3 Train unit tests * Reuse existing token from RequestContext 1.3.0 ----- * Cap Bandit below 1.6.0 and update Sphinx requirement * List requests as explicit dependency * Use opendev repository * OpenDev Migration Patch * Dropping the py35 testing * Replace openstack.org git:// URLs with https:// * Update master for stable/stein 1.2.2 ----- * Set py3 tests according to Stein runtimes 1.2.1 ----- * Fix length usage in VaultKeyManager.create\_key * add python 3.7 unit test job 1.2.0 ----- * Change openstack-dev to openstack-discuss 1.1.0 ----- * Add Castellan Oslo Config Driver * Use template for lower-constraints * Update min tox version to 2.0 * vault: support configuration of KV mountpoint * vault: add AppRole support * Don't quote {posargs} in tox.ini 1.0.0 ----- * Add method to wrap HashiCorp Vault HTTP API calls 0.20.1 ------ * Bump HashiCorp Vault version for tests * Fix Vault K/V API compatibility * add python 3.6 unit test job * import zuul job settings from project-config * Update reno for stable/rocky * Switch to stestr * Add release note link in README * Add code to generate private keys * fix tox python3 overrides 0.18.0 ------ * Add config option for Barbican endpoint type * Promote castellan's barbican-tempest-plugin job * pypy is not checked at gate * fix list of default virtualenvs * set default python to python3 * Updated from global requirements * Add barbican-tempest experimental job * add lower-constraints job * Updated from global requirements * Update links in README * Updated from global requirements * Updated from global requirements * Update unreachable links in contributor document * Zuul: Remove project name * Update reno for stable/queens * Updated from global requirements * Updated from global requirements * Use Zuul v3 fetch-subunit-output 0.17.0 ------ * Updated from global requirements * Updated from global requirements 0.16.0 ------ * Avoid tox\_install.sh for constraints support * Remove use of tox-siblings role * Updated from global requirements 0.15.1 ------ * Include domain info when creating identity token * Support handling legacy all-zeros key ID 0.15.0 ------ * Remove setting of version/release from releasenotes * Updated from global requirements * Add a functional vault job * Migrate to zuulv3 * Updated from global requirements * Vault based key manager * Updated from global requirements * Use generic user for both zuul v2 and v3 * Updated from global requirements 0.14.1 ------ * Remove genconfig from functional tests 0.14.0 ------ * Updated from global requirements * Makes list method not abstract * Updated from global requirements * Updated from global requirements * Add releasenotes for castellan * Add ID to managed objects * Append a forward slash to the base\_url 0.13.0 ------ * Updated from global requirements * allow redirects in .htaccess files on the static web servers * Use Stevedore for better extensions * Updated from global requirements * Rename barbican client import * Updated from global requirements * Updated from global requirements * Fix retrieving barbican endpoint from service catalog * Replace LOG.warn with LOG.warning * Add list capability * Improve docs around configuring Castellan 0.12.0 ------ * Update the doc URL in the documents 0.11.1 ------ * rearrange existing documentation to fit the new standard layout * Enable warning-is-error in doc build * Remove log translations 0.11.0 ------ * Switch from oslosphinx to openstackdocstheme * Updated from global requirements * Replaces uuid.uuid4 with uuidutils.generate\_uuid() * Enable some off-by-default checks * Update docs on config generation 0.10.0 ------ * Updated from global requirements * Optimize the link address * Updated from global requirements * Correct config path in functional test * Fix error in credential\_factory 0.9.0 ----- * Fix incorrect config in usage doc * Updated from global requirements * Updated from global requirements * Updated from global requirements 0.8.0 ----- * MockKeyManager should return a copy of the object instead of actual object * Replacing six.iteritems() with .items() * Updated from global requirements * Updated from global requirements * Updated from global requirements 0.7.0 ----- * Updated from global requirements * Change keystone endpoint * Updated from global requirements 0.6.0 ----- * Updated from global requirements * Updated from global requirements * Updated from global requirements * removed the older version of python * Updated from global requirements * Fix gate failure on pep8 * Removes unnecessary utf-8 encoding * Update .gitignore * Updated from global requirements * Add ability to get only metadata * Remove outdated comment * Updated from global requirements * Fixes all current typo errors on Castellan project 0.5.0 ----- * Add option for verifying TLS (https) requests * Updated from global requirements * Update doc * Enable coverage report in console output * H803 hacking has been removed * Use generic keystoneauth plugin identity interfaces * Remove tempest plugin from Castellan * Show team and repo badges on README * Add keystoneauth to requirements.txt * Updated from global requirements * Cookiecutter commit for Security Tempest plugin * remove obsolete oslo incubator code * Cleanup created secrets after functional test * Updated from global requirements * Updated from global requirements * Updated from global requirements * Update .gitignore * Updated from global requirements * Support upper-constraints in tox.ini * Update .gitignore * Remove default=None when set value in Config * Add prefix "$" for command examples * Update flake8 ignore list * Remove discover from test-requirements * Use international logging message * Add Python 3.5 classifier and venv for castellan * Correct castellan reraising of exception * Remove unused requirements * Modify the home-page info * Use international logging message * Add Barbicanclient dependancy * Updated from global requirements * Remove discover from test-requirements * Updated from global requirements * Use keystoneauth1 instead of keystoneclient * Updated from global requirements * Add a bandit environment to tox * Making sensitive parameters secret to avoid logging * Updated from global requirements * Updated from global requirements * Updated from global requirements * MockKeyManager create\_key change key\_length to length * Refactor Barbican Key Manager Tests * Update docs for reading conf files 0.4.0 ----- * Add Credential Authentication Usage Documentation * Allow Barbican Key Manager to accept different auth credentials * Updated from global requirements * Add help to Castellan Credential Factory Opts * Updated from global requirements * Introduce Castellan Credential Factory * Updated from global requirements * Introduce Castellan Credential Objects * Add created property to Managed Objects * Update MockKeyManager to use given algorithm * Remove functional test dependency on config file * Mock key manager takes configuration as an arg * Updated from global requirements * Clean up removed hacking rule from [flake8] ignore lists * Use Keystone V3 Identity Plugins for Functional Tests * Updated from global requirements * Add logic to error out of key creation if order errors out * Update docs with parsing config files * Removes MANIFEST.in as it is not needed explicitely by PBR * Remove py26 support from castellan * Updated from global requirements * Move line of code to ensure context and client stay in sync 0.3.1 ----- * Add some documentation on enabling logging * Allow for default logging configuration to be user enabled * Mark castellan as being a universal wheel * Add gate hooks for Castellan functional tests * Update managed object \_\_eq\_\_ and \_\_ne\_\_ 0.3.0 ----- * Updated from global requirements * Add the debug flag to tox.ini * Can not set auth\_endpoint in runtime * Updated from global requirements * Allow log statements to be printed out in stdout * Add documentation links and fixup README.rst * Updated from global requirements * Add testing documentation to Castellan * Add contributing documentation to Castellan * Fix typo and add name to not\_implemented and mock key\_manager * Adds documentation on creating Oslo RequestContext in Castellan * Updated from global requirements * Add name to Castellan Objects and Barbican Key Manager * Fix some spelling typo in manual * Updated from global requirements * Update Barbican functional tests * Add ManagedObjectNotFoundError * Change ignore-errors to ignore\_errors * Standardize Barbican error messages 0.2.1 ----- * Updated from global requirements 0.2.0 ----- * Update Barbican wrapper * Fixing error in documentation example * Adding documentation for basic usage * Add unit tests for managed objects * Remove copy\_key operation * refactoring castellan configuration * Update mock key manager * Update the key manager API * Add managed objects hierarchy * Activate pep8 check that \_ is imported * Add functional tests for Barbican key manager wrapper * Move unit tests to unit test folder * Add Barbican key manager * Migrate to oslo\_context * Removing SymmetricKey docs from key module * Drop use of 'oslo' namespace package 0.1.0 ----- * Start using oslo.policy * Remove Python 3.3 from setup.cfg and tox.ini * Renames for consistent namespaces * Fixing some warning about oslo namespace * Remove placeholder test * Copy cinder.keymgr to castellan * Updating HACKING.rst * Add openstack/common log and policy modules * Workflow documentation is now in infra-manual * Initial Cookiecutter Commit castellan-3.0.1/castellan.egg-info/0000775000175000017500000000000013645116331017146 5ustar zuulzuul00000000000000castellan-3.0.1/castellan.egg-info/PKG-INFO0000664000175000017500000000312213645116331020241 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: castellan Version: 3.0.1 Summary: Generic Key Manager interface for OpenStack Home-page: https://docs.openstack.org/castellan/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ========= Castellan ========= Generic Key Manager interface for OpenStack. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/castellan/latest * Source: https://opendev.org/openstack/castellan * Bugs: https://bugs.launchpad.net/castellan * Release notes: https://docs.openstack.org/releasenotes/castellan * Wiki: https://wiki.openstack.org/wiki/Castellan Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/castellan.svg :target: https://governance.openstack.org/tc/reference/tags/index.html Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.6 castellan-3.0.1/castellan.egg-info/pbr.json0000664000175000017500000000005613645116331020625 0ustar zuulzuul00000000000000{"git_version": "f8bcee0", "is_release": true}castellan-3.0.1/castellan.egg-info/not-zip-safe0000664000175000017500000000000113645116331021374 0ustar zuulzuul00000000000000 castellan-3.0.1/castellan.egg-info/top_level.txt0000664000175000017500000000001213645116331021671 0ustar zuulzuul00000000000000castellan castellan-3.0.1/castellan.egg-info/requires.txt0000664000175000017500000000036613645116331021553 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 Babel!=2.4.0,>=2.3.4 cryptography>=2.1 python-barbicanclient>=4.5.2 oslo.config>=6.4.0 oslo.context>=2.19.2 oslo.i18n>=3.15.3 oslo.log>=3.36.0 oslo.utils>=3.33.0 stevedore>=1.20.0 keystoneauth1>=3.4.0 requests!=2.20.0,>=2.18.0 castellan-3.0.1/castellan.egg-info/SOURCES.txt0000664000175000017500000001061413645116331021034 0ustar zuulzuul00000000000000.coveragerc .mailmap .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel.cfg bindep.txt lower-constraints.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini castellan/__init__.py castellan/_config_driver.py castellan/i18n.py castellan/options.py castellan.egg-info/PKG-INFO castellan.egg-info/SOURCES.txt castellan.egg-info/dependency_links.txt castellan.egg-info/entry_points.txt castellan.egg-info/not-zip-safe castellan.egg-info/pbr.json castellan.egg-info/requires.txt castellan.egg-info/top_level.txt castellan/common/__init__.py castellan/common/exception.py castellan/common/utils.py castellan/common/credentials/__init__.py castellan/common/credentials/credential.py castellan/common/credentials/keystone_password.py castellan/common/credentials/keystone_token.py castellan/common/credentials/password.py castellan/common/credentials/token.py castellan/common/objects/__init__.py castellan/common/objects/certificate.py castellan/common/objects/key.py castellan/common/objects/managed_object.py castellan/common/objects/opaque_data.py castellan/common/objects/passphrase.py castellan/common/objects/private_key.py castellan/common/objects/public_key.py castellan/common/objects/symmetric_key.py castellan/common/objects/x_509.py castellan/key_manager/__init__.py castellan/key_manager/barbican_key_manager.py castellan/key_manager/key_manager.py castellan/key_manager/migration.py castellan/key_manager/not_implemented_key_manager.py castellan/key_manager/vault_key_manager.py castellan/tests/__init__.py castellan/tests/base.py castellan/tests/utils.py castellan/tests/contrib/gate_hook.sh castellan/tests/contrib/post_test_hook.sh castellan/tests/functional/__init__.py castellan/tests/functional/config.py castellan/tests/functional/key_manager/__init__.py castellan/tests/functional/key_manager/test_barbican_key_manager.py castellan/tests/functional/key_manager/test_key_manager.py castellan/tests/functional/key_manager/test_vault_key_manager.py castellan/tests/unit/__init__.py castellan/tests/unit/test_config_driver.py castellan/tests/unit/test_options.py castellan/tests/unit/test_utils.py castellan/tests/unit/credentials/__init__.py castellan/tests/unit/credentials/test_keystone_password.py castellan/tests/unit/credentials/test_keystone_token.py castellan/tests/unit/credentials/test_password.py castellan/tests/unit/credentials/test_token.py castellan/tests/unit/key_manager/__init__.py castellan/tests/unit/key_manager/fake.py castellan/tests/unit/key_manager/mock_key_manager.py castellan/tests/unit/key_manager/test_barbican_key_manager.py castellan/tests/unit/key_manager/test_key_manager.py castellan/tests/unit/key_manager/test_migration_key_manager.py castellan/tests/unit/key_manager/test_mock_key_manager.py castellan/tests/unit/key_manager/test_not_implemented_key_manager.py castellan/tests/unit/objects/__init__.py castellan/tests/unit/objects/test_opaque.py castellan/tests/unit/objects/test_passphrase.py castellan/tests/unit/objects/test_private_key.py castellan/tests/unit/objects/test_public_key.py castellan/tests/unit/objects/test_symmetric_key.py castellan/tests/unit/objects/test_x_509.py doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/_extra/.htaccess doc/source/contributor/contributing.rst doc/source/contributor/index.rst doc/source/contributor/testing.rst doc/source/install/index.rst doc/source/user/index.rst etc/castellan/functional-config-generator.conf etc/castellan/sample-config-generator.conf playbooks/devstack/post.yaml playbooks/devstack/pre.yaml playbooks/devstack/run.yaml releasenotes/notes/add-vault-provider-29a4c19fe67ab51f.yaml releasenotes/notes/deprecate-auth-endpoint-b91a3e67b5c7263f.yaml releasenotes/notes/drop-python-2-7-73d3113c69d724d6.yaml releasenotes/notes/fix-vault-create-key-b4340a3067cbd93c.yaml releasenotes/notes/implements-keymanager-option-discovery-13a46c1dfc036a3f.yaml releasenotes/notes/support-legacy-fixed-key-id-9fa897b547111610.yaml releasenotes/notes/vault-approle-support-5ea04daea07a152f.yaml releasenotes/notes/vault-kv-mountpoint-919eb547764a0c74.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/setup-vault-env.shcastellan-3.0.1/castellan.egg-info/dependency_links.txt0000664000175000017500000000000113645116331023214 0ustar zuulzuul00000000000000 castellan-3.0.1/castellan.egg-info/entry_points.txt0000664000175000017500000000061713645116331022450 0ustar zuulzuul00000000000000[castellan.drivers] barbican = castellan.key_manager.barbican_key_manager:BarbicanKeyManager vault = castellan.key_manager.vault_key_manager:VaultKeyManager [oslo.config.driver] castellan = castellan._config_driver:CastellanConfigurationSourceDriver [oslo.config.opts] castellan.config = castellan.options:list_opts castellan.tests.functional.config = castellan.tests.functional.config:list_opts castellan-3.0.1/bindep.txt0000664000175000017500000000025713645116250015514 0ustar zuulzuul00000000000000# This is a cross-platform list tracking distribution packages needed for install and tests; # see https://docs.openstack.org/infra/bindep/ for additional information. unzip castellan-3.0.1/.mailmap0000664000175000017500000000013113645116250015122 0ustar zuulzuul00000000000000# Format is: # # castellan-3.0.1/CONTRIBUTING.rst0000664000175000017500000000374013645116250016153 0ustar zuulzuul00000000000000============ Contributing ============ The best way to join the community and get involved is to talk with others online or at a meetup and offer contributions. Here are some of the many ways you can contribute to the Castellan project: * Development and Code Reviews * Bug reporting/Bug fixes * Wiki and Documentation * Blueprints/Specifications * Testing * Deployment scripts Before you start contributing take a look at the `Openstack Developers Guide`_. .. _`Openstack Developers Guide`: https://docs.openstack.org/infra/manual/developers.html Freenode IRC (Chat) ------------------- You can find Castellaneers in our publicly accessible channel on `freenode`_ ``#openstack-barbican``. All conversations are logged and stored for your convenience at `eavesdrop.openstack.org`_. For more information regarding OpenStack IRC channels please visit the `OpenStack IRC Wiki`_. .. _`freenode`: https://freenode.net .. _`OpenStack IRC Wiki`: https://wiki.openstack.org/wiki/IRC .. _`eavesdrop.openstack.org`: http://eavesdrop.openstack.org/irclogs/ %23openstack-barbican/ Launchpad --------- Like other OpenStack related projects, we utilize Launchpad for our bug and release tracking. * `Castellan Launchpad Project`_ .. _`Castellan Launchpad Project`: https://launchpad.net/castellan .. note:: Bugs should be filed on Launchpad, not Github. Source Repository ----------------- Like other OpenStack related projects, the official Git repository is available on `Castellan on GitHub`_. .. _`Castellan on GitHub`: https://github.com/openstack/castellan Gerrit ------ Like other OpenStack related projects, we utilize the OpenStack Gerrit review system for all code reviews. If you're unfamiliar with using the OpenStack Gerrit review system, please review the `Gerrit Workflow`_ wiki documentation. .. _`Gerrit Workflow`: https://docs.openstack.org/infra/manual/developers.html#development-workflow .. note:: Pull requests submitted through GitHub will be ignored. castellan-3.0.1/tox.ini0000664000175000017500000000747413645116250015035 0ustar zuulzuul00000000000000[tox] minversion = 3.1.1 envlist = py37,pep8 ignore_basepython_conflict = True skipsdist = True [testenv] usedevelop = True basepython = python3 setenv = VIRTUAL_ENV={envdir} OS_TEST_PATH=./castellan/tests/unit deps = -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run --slowest {posargs} [testenv:pep8] commands = flake8 bandit -r castellan -x tests -s B105,B106,B107,B607 [testenv:bandit] # This command runs the bandit security linter against the castellan # codebase minus the tests directory. Some tests are being excluded to # reduce the number of positives before a team inspection, and to ensure a # passing gate job for initial addition. The excluded tests are: # B105-B107: hardcoded password checks - likely to generate false positives # in a gate environment # B607: start process with a partial path - this should be a project level # decision commands = bandit -r castellan -x tests -s B105,B106,B107,B607 [testenv:venv] commands = {posargs} [testenv:debug] commands = oslo_debug_helper {posargs} [testenv:cover] setenv = PYTHON=coverage run --source castellan --parallel-mode commands = coverage erase {[testenv]commands} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report --show-missing [testenv:docs] # This environment is called from CI scripts to test and publish # the main docs to https://docs.openstack.org/castellan description = Build main documentation deps = -r{toxinidir}/doc/requirements.txt commands= rm -rf doc/build doc/build/doctrees sphinx-build -W -b html -d doc/build/doctrees doc/source doc/build/html whitelist_externals = rm [testenv:pdf-docs] deps = {[testenv:docs]deps} envdir = {toxworkdir}/docs whitelist_externals = rm make commands = rm -rf doc/build/pdf sphinx-build -W -b latex doc/source doc/build/pdf make -C doc/build/pdf [testenv:releasenotes] deps = {[testenv:docs]deps} envdir = {toxworkdir}/docs commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:functional] usedevelop = True install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} OS_TEST_PATH=./castellan/tests/functional commands = stestr run --slowest {posargs} [testenv:functional-vault] passenv = HOME usedevelop = True install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} OS_TEST_PATH=./castellan/tests/functional commands = {toxinidir}/tools/setup-vault-env.sh pifpaf -e VAULT_TEST run vault -- stestr run --slowest {posargs} [testenv:genconfig] commands = oslo-config-generator --config-file=etc/castellan/functional-config-generator.conf oslo-config-generator --config-file=etc/castellan/sample-config-generator.conf [flake8] # [H106] Don't put vim configuration in source files. # [H203] Use assertIs(Not)None to check for None. show-source = True exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build enable-extensions = H106,H203 [hacking] import_exceptions = castellan.i18n [testenv:lower-constraints] deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt [testenv:bindep] # Do not install any requirements. We want this to be fast and work even if # system dependencies are missing, since it's used to tell you what system # dependencies are missing! This also means that bindep must be installed # separately, outside of the requirements files, and develop mode disabled # explicitly to avoid unnecessarily installing the checked-out repo too (this # further relies on "tox.skipsdist = True" above). deps = bindep commands = bindep test usedevelop = False castellan-3.0.1/PKG-INFO0000664000175000017500000000312213645116331014601 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: castellan Version: 3.0.1 Summary: Generic Key Manager interface for OpenStack Home-page: https://docs.openstack.org/castellan/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ========= Castellan ========= Generic Key Manager interface for OpenStack. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/castellan/latest * Source: https://opendev.org/openstack/castellan * Bugs: https://bugs.launchpad.net/castellan * Release notes: https://docs.openstack.org/releasenotes/castellan * Wiki: https://wiki.openstack.org/wiki/Castellan Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/castellan.svg :target: https://governance.openstack.org/tc/reference/tags/index.html Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.6 castellan-3.0.1/LICENSE0000664000175000017500000002363713645116250014526 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. castellan-3.0.1/setup.cfg0000664000175000017500000000300613645116331015326 0ustar zuulzuul00000000000000[metadata] name = castellan summary = Generic Key Manager interface for OpenStack description-file = README.rst author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/castellan/latest/ python-requires = >=3.6 classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython [files] packages = castellan [entry_points] oslo.config.opts = castellan.tests.functional.config = castellan.tests.functional.config:list_opts castellan.config = castellan.options:list_opts oslo.config.driver = castellan = castellan._config_driver:CastellanConfigurationSourceDriver castellan.drivers = barbican = castellan.key_manager.barbican_key_manager:BarbicanKeyManager vault = castellan.key_manager.vault_key_manager:VaultKeyManager [compile_catalog] directory = castellan/locale domain = castellan [update_catalog] domain = castellan output_dir = castellan/locale input_file = castellan/locale/castellan.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = castellan/locale/castellan.pot [egg_info] tag_build = tag_date = 0 castellan-3.0.1/babel.cfg0000664000175000017500000000002113645116250015225 0ustar zuulzuul00000000000000[python: **.py] castellan-3.0.1/lower-constraints.txt0000664000175000017500000000223113645116250017742 0ustar zuulzuul00000000000000alabaster==0.7.10 appdirs==1.3.0 asn1crypto==0.23.0 Babel==2.3.4 bandit==1.1.0 cffi==1.7.0 cliff==2.8.0 cmd2==0.8.0 coverage==4.0 cryptography==2.1 debtcollector==1.2.0 docutils==0.11 dulwich==0.15.0 extras==1.0.0 fixtures==3.0.0 flake8==2.5.5 gitdb==0.6.4 GitPython==1.0.1 hacking==0.12.0 idna==2.5 imagesize==0.7.1 iso8601==0.1.11 Jinja2==2.10 keystoneauth1==3.4.0 linecache2==1.0.0 MarkupSafe==1.0 mccabe==0.2.1 mock==2.0.0 monotonic==0.6 mox3==0.20.0 msgpack-python==0.4.0 netaddr==0.7.18 netifaces==0.10.4 os-client-config==1.28.0 oslo.config==6.4.0 oslo.context==2.19.2 oslo.i18n==3.15.3 oslo.log==3.36.0 oslo.serialization==2.18.0 oslo.utils==3.33.0 oslotest==3.2.0 pbr==2.0.0 pep8==1.5.7 pifpaf==0.10.0 prettytable==0.7.2 pycparser==2.18 pyflakes==0.8.1 Pygments==2.2.0 pyinotify==0.9.6 pyparsing==2.1.0 pyperclip==1.5.27 python-barbicanclient==4.5.2 python-dateutil==2.5.3 python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2013.6 PyYAML==3.12 requests==2.18.0 requestsexceptions==1.2.0 rfc3986==0.3.1 smmap==0.9.0 snowballstemmer==1.2.1 stevedore==1.20.0 stestr==2.0.0 testscenarios==0.4 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 wrapt==1.7.0 xattr==0.9.2 castellan-3.0.1/playbooks/0000775000175000017500000000000013645116331015511 5ustar zuulzuul00000000000000castellan-3.0.1/playbooks/devstack/0000775000175000017500000000000013645116331017315 5ustar zuulzuul00000000000000castellan-3.0.1/playbooks/devstack/post.yaml0000664000175000017500000000011013645116250021156 0ustar zuulzuul00000000000000- hosts: all roles: - fetch-tox-output - fetch-subunit-output castellan-3.0.1/playbooks/devstack/pre.yaml0000664000175000017500000000024113645116250020764 0ustar zuulzuul00000000000000- hosts: all roles: - run-devstack - role: bindep bindep_profile: test bindep_dir: "{{ zuul_work_dir }}" - test-setup - ensure-tox castellan-3.0.1/playbooks/devstack/run.yaml0000664000175000017500000000004013645116250020777 0ustar zuulzuul00000000000000- hosts: all roles: - tox castellan-3.0.1/test-requirements.txt0000664000175000017500000000110313645116250017742 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 python-barbicanclient>=4.5.2 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD oslotest>=3.2.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT bandit>=1.1.0,<1.6.0 # Apache-2.0 pifpaf>=0.10.0 # Apache-2.0 castellan-3.0.1/releasenotes/0000775000175000017500000000000013645116331016177 5ustar zuulzuul00000000000000castellan-3.0.1/releasenotes/notes/0000775000175000017500000000000013645116331017327 5ustar zuulzuul00000000000000castellan-3.0.1/releasenotes/notes/drop-python-2-7-73d3113c69d724d6.yaml0000664000175000017500000000021013645116250025052 0ustar zuulzuul00000000000000--- upgrade: - | Python 2.7 support has been dropped. The minimum version of Python now supported by castellan is Python 3.6. castellan-3.0.1/releasenotes/notes/support-legacy-fixed-key-id-9fa897b547111610.yaml0000664000175000017500000000070313645116250027447 0ustar zuulzuul00000000000000--- features: - | Enhance the key manager to handle requests containing the special (all zeros) managed object ID associated with Cinder's and Nova's legacy ConfKeyManager. The purpose of this feature is to help users migrate from the ConfKeyManager to a modern key manager such as Barbican. The feature works by ensuring the ConfKeyManager's all-zeros key ID continues to function when Barbican or Vault is the key manager. castellan-3.0.1/releasenotes/notes/add-vault-provider-29a4c19fe67ab51f.yaml0000664000175000017500000000012513645116250026223 0ustar zuulzuul00000000000000--- features: - | Added a new provider for Vault (https://www.vaultproject.io/)castellan-3.0.1/releasenotes/notes/deprecate-auth-endpoint-b91a3e67b5c7263f.yaml0000664000175000017500000000022713645116250027146 0ustar zuulzuul00000000000000--- deprecations: - | Config option barbican/auth_endpoint is unnecessary and deprecated in favor of the more standard key_manager/auth_url. castellan-3.0.1/releasenotes/notes/vault-approle-support-5ea04daea07a152f.yaml0000664000175000017500000000035613645116250027061 0ustar zuulzuul00000000000000--- features: - | Added support for AppRole based authentication to the Vault key manager configured using new approle_role_id and optional approle_secret_id options. (https://www.vaultproject.io/docs/auth/approle.html) castellan-3.0.1/releasenotes/notes/vault-kv-mountpoint-919eb547764a0c74.yaml0000664000175000017500000000027713645116250026273 0ustar zuulzuul00000000000000--- features: - | Added configuration option to the Vault key manager to allow the KV store mountpoint in Vault to be specified; the existing default of 'secret' is maintained. castellan-3.0.1/releasenotes/notes/fix-vault-create-key-b4340a3067cbd93c.yaml0000664000175000017500000000102413645116250026361 0ustar zuulzuul00000000000000--- fixes: - | Fixed VaultKeyManager.create_key() to consider the `length` param as bits instead of bytes for the key length. This was causing a discrepancy between keys generated by the HashiCorp Vault backend and the OpenStack Barbican backend. Considering `km` as an instance of a key manager, the following code `km.create_key(ctx, "AES", 256)` was generating a 256 bit AES key when Barbican is configured as the backend, but generating a 2048 bit AES key when Vault was configured as the backend. castellan-3.0.1/releasenotes/notes/implements-keymanager-option-discovery-13a46c1dfc036a3f.yaml0000664000175000017500000000127413645116250032310 0ustar zuulzuul00000000000000--- features: - | Enhance the global option listing to discover available key managers and their options. The purpose of this feature is to have a correct listing of the supported key managers, now each key manager is responsible for advertising the oslo.config groups/options they consume. other: - | The visibility of module variables and constants related to oslo.config options changed to private in both barbican and vault key managers. The key managers are only responsible for overloading the method list_options_for_discovery() in order to advertise their own options. This way, the global options doesn't need to know which variables to look for. castellan-3.0.1/releasenotes/source/0000775000175000017500000000000013645116331017477 5ustar zuulzuul00000000000000castellan-3.0.1/releasenotes/source/stein.rst0000664000175000017500000000022113645116250021346 0ustar zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein castellan-3.0.1/releasenotes/source/index.rst0000664000175000017500000000024713645116250021343 0ustar zuulzuul00000000000000========================= Castellan Release Notes ========================= .. toctree:: :maxdepth: 1 unreleased train stein rocky queens pike castellan-3.0.1/releasenotes/source/queens.rst0000664000175000017500000000022313645116250021526 0ustar zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens castellan-3.0.1/releasenotes/source/_static/0000775000175000017500000000000013645116331021125 5ustar zuulzuul00000000000000castellan-3.0.1/releasenotes/source/_static/.placeholder0000664000175000017500000000000013645116250023376 0ustar zuulzuul00000000000000castellan-3.0.1/releasenotes/source/_templates/0000775000175000017500000000000013645116331021634 5ustar zuulzuul00000000000000castellan-3.0.1/releasenotes/source/_templates/.placeholder0000664000175000017500000000000013645116250024105 0ustar zuulzuul00000000000000castellan-3.0.1/releasenotes/source/rocky.rst0000664000175000017500000000022113645116250021353 0ustar zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky castellan-3.0.1/releasenotes/source/train.rst0000664000175000017500000000017613645116250021352 0ustar zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train castellan-3.0.1/releasenotes/source/unreleased.rst0000664000175000017500000000016013645116250022355 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: castellan-3.0.1/releasenotes/source/pike.rst0000664000175000017500000000021713645116250021161 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike castellan-3.0.1/releasenotes/source/conf.py0000664000175000017500000002137613645116250021007 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. repository_name = 'openstack/castellan' bug_project = 'castellan' bug_tag = 'doc' project = u'Castellan Release Notes' copyright = u'2017, Castellan Developers' # Release notes do not need a version number in the title, they # cover multiple releases. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'CastellanReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'CastellanReleaseNotes.tex', u'Castellan Release Notes Documentation', u'Castellan Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'castellanreleasenotes', u'Castellan Release Notes Documentation', [u'Castellan Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'CastellanReleaseNotes', u'Castellan Release Notes Documentation', u'Castellan Developers', 'CastellanReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] castellan-3.0.1/.coveragerc0000664000175000017500000000013713645116250015630 0ustar zuulzuul00000000000000[run] branch = True source = castellan omit = castellan/tests/* [report] ignore_errors = True castellan-3.0.1/AUTHORS0000664000175000017500000000537713645116331014572 0ustar zuulzuul00000000000000Ade Lee Akihiro Motoki Alan Bishop Alexandra Settle Andreas Jaeger Anusree Ben Nemec Brianna Poulos Brianna Poulos Chris Solis Christopher Solis Corey Bryant Dai Dang Van Davanum Srinivas Dave McCowan Doug Hellmann Douglas Mendizábal Douglas Mendizábal Dung Ha Ellen Batbouta Eric Harney Fernando Diaz Flavio Percoco Ghanshyam Mann Ian Wienand James E. Blair James Page Jamie Lennox Jeremy Liu Jeremy Stanley Jiong Liu Joel Coffman Juan Antonio Osorio Robles Juan Antonio Osorio Robles Kaitlin Farr Kiran_totad Le Hou Michael McCune Moises Guimaraes de Medeiros Moisés Guimarães de Medeiros Monty Taylor Nguyen Van Trung Niall Bunting OpenStack Release Bot Paul Bourke Pavlo Shchelokovskyy Robert Clark Steve Martinelli Sungjin Yook Swapnil Kulkarni (coolsvap) Tim Kelsey Tom Cocozzello Van Hung Pham Vladislav Kuzmin Vu Cong Tuan Yushiro FURUKAWA Zhao Lei bhavani.cr dane-fichter gaofei gecong1973 gengchc2 lioplhp liujiong melissaml rajat29 sonu.kumar ting.wang wu.chunyang xhzhf xuanyandong yushangbin zhangboye zhangzs “Fernando castellan-3.0.1/tools/0000775000175000017500000000000013645116331014646 5ustar zuulzuul00000000000000castellan-3.0.1/tools/setup-vault-env.sh0000775000175000017500000000152513645116250020267 0ustar zuulzuul00000000000000#!/bin/bash set -eux if [ -z "$(which vault)" ]; then VAULT_VERSION=0.10.4 SUFFIX=zip case `uname -s` in Darwin) OS=darwin ;; Linux) OS=linux ;; *) echo "Unsupported OS" exit 1 esac case `uname -m` in x86_64) MACHINE=amd64 ;; *) echo "Unsupported machine" exit 1 esac TARBALL_NAME=vault_${VAULT_VERSION}_${OS}_${MACHINE} test ! -d "$TARBALL_NAME" && mkdir ${TARBALL_NAME} && wget https://releases.hashicorp.com/vault/${VAULT_VERSION}/${TARBALL_NAME}.${SUFFIX} && unzip -d ${TARBALL_NAME} ${TARBALL_NAME}.${SUFFIX} && rm ${TARBALL_NAME}.${SUFFIX} export VAULT_CONFIG_PATH=$(pwd)/$TARBALL_NAME/vault.json export PATH=$PATH:$(pwd)/$TARBALL_NAME fi $* castellan-3.0.1/.stestr.conf0000664000175000017500000000006213645116250015755 0ustar zuulzuul00000000000000[DEFAULT] test_path=./castellan/tests top_dir=./ castellan-3.0.1/castellan/0000775000175000017500000000000013645116331015454 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/i18n.py0000664000175000017500000000152613645116250016611 0ustar zuulzuul00000000000000# Copyright 2014 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/usage.html . """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='castellan') # The primary translation function using the well-known name "_" _ = _translators.primary castellan-3.0.1/castellan/tests/0000775000175000017500000000000013645116331016616 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/base.py0000664000175000017500000000223713645116250020106 0ustar zuulzuul00000000000000 # Copyright 2010-2011 OpenStack Foundation # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslotest import base class TestCase(base.BaseTestCase): """Test case base class for all unit tests.""" class CertificateTestCase(TestCase): def _create_cert(self): raise NotImplementedError() def setUp(self): super(CertificateTestCase, self).setUp() self.cert = self._create_cert() class KeyTestCase(TestCase): def _create_key(self): raise NotImplementedError() def setUp(self): super(KeyTestCase, self).setUp() self.key = self._create_key() castellan-3.0.1/castellan/tests/utils.py0000664000175000017500000003761013645116250020337 0ustar zuulzuul00000000000000# Copyright (c) 2015 # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """These utility functions are borrowed from Barbican's testing utilities.""" import functools import types def construct_new_test_function(original_func, name, build_params): """Builds a new test function based on parameterized data. :param original_func: The original test function that is used as a template :param name: The fullname of the new test function :param build_params: A dictionary or list containing args or kwargs for the new test :return: A new function object """ new_func = types.FunctionType( original_func.__code__, original_func.__globals__, name=name, argdefs=original_func.__defaults__, ) for key, val in original_func.__dict__.items(): if key != 'build_data': new_func.__dict__[key] = val # Support either an arg list or kwarg dict for our data build_args = build_params if isinstance(build_params, list) else [] build_kwargs = build_params if isinstance(build_params, dict) else {} # Build a test wrapper to execute with our kwargs def test_wrapper(func, test_args, test_kwargs): @functools.wraps(func) def wrapper(self): return func(self, *test_args, **test_kwargs) return wrapper return test_wrapper(new_func, build_args, build_kwargs) def process_parameterized_function(name, func_obj, build_data): """Build lists of functions to add and remove to a test case.""" to_remove = [] to_add = [] for subtest_name, params in build_data.items(): # Build new test function func_name = '{0}_{1}'.format(name, subtest_name) new_func = construct_new_test_function(func_obj, func_name, params) # Mark the new function as needed to be added to the class to_add.append((func_name, new_func)) # Mark key for removal to_remove.append(name) return to_remove, to_add def parameterized_test_case(cls): """Class decorator to process parameterized tests This allows for parameterization to be used for potentially any unittest compatible runner; including testr and py.test. """ tests_to_remove = [] tests_to_add = [] for key, val in vars(cls).items(): # Only process tests with build data on them if key.startswith('test_') and val.__dict__.get('build_data'): to_remove, to_add = process_parameterized_function( name=key, func_obj=val, build_data=val.__dict__.get('build_data') ) tests_to_remove.extend(to_remove) tests_to_add.extend(to_add) # Add all new test functions [setattr(cls, name, func) for name, func in tests_to_add] # Remove all old test function templates (if they still exist) [delattr(cls, key) for key in tests_to_remove if hasattr(cls, key)] return cls def parameterized_dataset(build_data): """Simple decorator to mark a test method for processing.""" def decorator(func): func.__dict__['build_data'] = build_data return func return decorator def get_certificate_der(): """Returns an X509 certificate in DER format This certificate was created by issuing the following openssl commands: openssl genrsa -out private.pem 2048 openssl req -new -x509 -key private.pem -out cert.pem \ -days 1000 -subj '/CN=example.com' openssl x509 -outform der -in cert.pem -out cert.der The byte string returned by this function is the contents of the cert.der file. """ cert_der = ( b'\x30\x82\x02\xff\x30\x82\x01\xe7\xa0\x03\x02\x01\x02\x02\x09' b'\x00\xe2\xea\x5c\xa2\x7d\xab\xdf\xe7\x30\x0d\x06\x09\x2a\x86' b'\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x16\x31\x14\x30\x12' b'\x06\x03\x55\x04\x03\x0c\x0b\x65\x78\x61\x6d\x70\x6c\x65\x2e' b'\x63\x6f\x6d\x30\x1e\x17\x0d\x31\x35\x30\x34\x31\x31\x30\x32' b'\x31\x35\x32\x39\x5a\x17\x0d\x31\x38\x30\x31\x30\x35\x30\x32' b'\x31\x35\x32\x39\x5a\x30\x16\x31\x14\x30\x12\x06\x03\x55\x04' b'\x03\x0c\x0b\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d\x30' b'\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01' b'\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01' b'\x01\x00\xb3\x6b\x65\x68\x0d\x79\x81\x50\xc9\xb0\x8c\x5b\xbd' b'\x17\xa3\x0c\xe6\xaf\xc0\x67\x55\xa3\x9d\x60\x36\x60\xd7\x4d' b'\xcb\x6d\xfb\x4e\xb1\x8d\xfe\x7a\x1b\x0c\x3b\xfc\x14\x10\x69' b'\x50\xf9\x87\x35\x9d\x38\x1f\x52\xf2\xc4\x57\x0f\xf1\x17\x85' b'\xad\xc2\x17\xa6\x27\xec\x45\xeb\xb6\x94\x05\x9a\xa9\x13\xf1' b'\xa2\xfb\xb9\x0a\xe0\x21\x7d\xe7\x0a\xbf\xe4\x61\x8c\xb5\x4b' b'\x27\x42\x3e\x31\x92\x1b\xef\x64\x4e\x2a\x97\xd9\x4e\x66\xfb' b'\x76\x19\x45\x80\x60\xf7\xbe\x40\xb9\xd4\x10\x9f\x84\x65\x56' b'\xdf\x9c\x39\xd8\xe6\x3f\xdb\x7c\x79\x31\xe3\xb8\xca\xfc\x79' b'\x9b\x23\xdc\x72\x7c\x4c\x55\x0e\x36\x2a\xe0\xeb\xcc\xaa\xa3' b'\x06\x54\xa3\x98\x19\xdc\xa4\x66\x31\xd0\x98\x02\x4f\xeb\x32' b'\x16\x61\xec\x97\xca\xce\x92\xa0\x8f\x3c\x52\xe8\xdb\x86\x10' b'\x9f\xee\x3f\xa6\xbd\x40\x63\x06\x99\x01\xb3\x13\x97\xdc\xe8' b'\x2e\xd1\x10\x8f\xab\x31\x49\xcb\x87\x71\x2f\x5e\xf2\x78\xa9' b'\xb4\x3c\x65\xb1\xb2\xd0\x82\xa1\x95\x68\x67\x44\xd7\x5e\xec' b'\xb4\x2f\x79\x40\x7e\xd4\xbc\x84\xdb\xb9\x8c\xdd\x8d\x9c\x01' b'\x15\xcd\x52\x83\x3f\x06\x67\xfd\xa1\x2d\x2b\x07\xba\x32\x62' b'\x21\x07\x2f\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06' b'\x03\x55\x1d\x0e\x04\x16\x04\x14\x94\xab\x60\x34\x6f\x65\xe8' b'\xfa\xc2\xaf\x98\xa8\x0d\xf1\x6a\xbc\x97\xa8\xfc\xda\x30\x1f' b'\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x94\xab\x60\x34' b'\x6f\x65\xe8\xfa\xc2\xaf\x98\xa8\x0d\xf1\x6a\xbc\x97\xa8\xfc' b'\xda\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff' b'\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00' b'\x03\x82\x01\x01\x00\x63\x8a\xea\xa1\x97\x33\x55\x39\x52\xeb' b'\x1c\x34\x32\x1a\xbd\x1f\x4c\x00\x85\x25\xd0\xd1\x12\x7b\xa1' b'\x66\x9e\x1d\xf7\x5f\xbe\x0e\x63\x02\x4f\xe6\xdc\x4c\x6d\x3e' b'\x18\x2a\x77\xad\xf1\x4e\xb8\x45\xa9\x24\xb2\xcb\x3d\xd4\x8e' b'\x9c\x8b\x27\x89\xbb\x0e\xb3\x22\x8f\x5e\xe0\x41\x5f\x99\x26' b'\x75\x82\x28\x8d\xb7\x63\x51\x34\xb0\x9e\x17\x31\xf4\x94\xc0' b'\x7c\xa4\xa6\xc5\x75\x92\x0b\x4a\xe7\x28\x27\x9f\x01\xfe\x38' b'\x32\x6e\x9f\xaa\xfa\x13\xc9\x36\xde\x19\x24\x0f\xea\x71\xf3' b'\x73\xb7\x8b\x68\xaf\xde\x7d\xca\xcc\xbd\x87\x5c\xb7\xe4\xde' b'\x4e\x41\xe3\xa9\x1f\x0b\xbb\x8a\x63\x66\xf4\x5d\x51\x06\x9d' b'\x40\x78\x43\xc8\xdf\x8e\x34\xa7\x4a\x0f\xd4\xeb\x8e\xf7\xcf' b'\x8a\x6d\x1b\xec\x0a\xbc\xf3\x93\xe3\x48\xde\x90\xa3\x86\x7d' b'\x1d\x74\x7a\xfa\x72\xbe\x6d\x3c\xfd\x1f\x25\x00\x4c\xc7\xc3' b'\x18\xd4\x2d\xd0\xbd\xef\xc9\xf5\x71\x6c\xd3\xb1\x90\x20\x5c' b'\x60\x8e\x21\x16\xd1\x9f\x90\xec\xdd\xe8\x1e\xeb\xda\xc6\x35' b'\xc0\x62\x9d\x4c\xb1\xe4\xb9\x3e\x26\xe3\xff\x40\xfd\x23\xb3' b'\xbe\x71\xfe\x7a\x99\xc9\xa8\x84\xbd\x8f\x0f\xb5\x89\x18\xfc' b'\xc5\xc0\xc0\xe8\xf3\x53') return cert_der def get_private_key_der(): """Returns a private key in DER format This key was created by issuing the following openssl commands: openssl genrsa -out private.pem 2048 openssl pkcs8 -in private.pem -topk8 -nocrypt \ -outform DER -out private_pk8.der The byte string returned by this function is the contents of the private_pk8.der file. """ key_der = ( b'\x30\x82\x04\xbf\x02\x01\x00\x30\x0d\x06\x09\x2a\x86\x48\x86' b'\xf7\x0d\x01\x01\x01\x05\x00\x04\x82\x04\xa9\x30\x82\x04\xa5' b'\x02\x01\x00\x02\x82\x01\x01\x00\xb3\x6b\x65\x68\x0d\x79\x81' b'\x50\xc9\xb0\x8c\x5b\xbd\x17\xa3\x0c\xe6\xaf\xc0\x67\x55\xa3' b'\x9d\x60\x36\x60\xd7\x4d\xcb\x6d\xfb\x4e\xb1\x8d\xfe\x7a\x1b' b'\x0c\x3b\xfc\x14\x10\x69\x50\xf9\x87\x35\x9d\x38\x1f\x52\xf2' b'\xc4\x57\x0f\xf1\x17\x85\xad\xc2\x17\xa6\x27\xec\x45\xeb\xb6' b'\x94\x05\x9a\xa9\x13\xf1\xa2\xfb\xb9\x0a\xe0\x21\x7d\xe7\x0a' b'\xbf\xe4\x61\x8c\xb5\x4b\x27\x42\x3e\x31\x92\x1b\xef\x64\x4e' b'\x2a\x97\xd9\x4e\x66\xfb\x76\x19\x45\x80\x60\xf7\xbe\x40\xb9' b'\xd4\x10\x9f\x84\x65\x56\xdf\x9c\x39\xd8\xe6\x3f\xdb\x7c\x79' b'\x31\xe3\xb8\xca\xfc\x79\x9b\x23\xdc\x72\x7c\x4c\x55\x0e\x36' b'\x2a\xe0\xeb\xcc\xaa\xa3\x06\x54\xa3\x98\x19\xdc\xa4\x66\x31' b'\xd0\x98\x02\x4f\xeb\x32\x16\x61\xec\x97\xca\xce\x92\xa0\x8f' b'\x3c\x52\xe8\xdb\x86\x10\x9f\xee\x3f\xa6\xbd\x40\x63\x06\x99' b'\x01\xb3\x13\x97\xdc\xe8\x2e\xd1\x10\x8f\xab\x31\x49\xcb\x87' b'\x71\x2f\x5e\xf2\x78\xa9\xb4\x3c\x65\xb1\xb2\xd0\x82\xa1\x95' b'\x68\x67\x44\xd7\x5e\xec\xb4\x2f\x79\x40\x7e\xd4\xbc\x84\xdb' b'\xb9\x8c\xdd\x8d\x9c\x01\x15\xcd\x52\x83\x3f\x06\x67\xfd\xa1' b'\x2d\x2b\x07\xba\x32\x62\x21\x07\x2f\x02\x03\x01\x00\x01\x02' b'\x82\x01\x00\x30\xe9\x54\x29\xbb\x92\xa6\x28\x29\xf3\x91\x2f' b'\xe9\x2a\xaa\x6e\x77\xec\xed\x9c\xbe\x01\xee\x83\x2e\x0f\xd4' b'\x62\x06\xd5\x22\xaf\x5f\x44\x00\x5d\xb5\x45\xee\x8c\x57\xc3' b'\xe9\x92\x03\x94\x52\x8f\x5b\x9f\x5e\x73\x84\x06\xdf\xf7\xaf' b'\x9b\xe7\xb4\x83\xd1\xee\x0c\x41\x3b\x72\xf8\x83\x56\x98\x45' b'\x31\x98\x66\xdb\x19\x15\xe4\xcb\x77\xd2\xbc\x61\x3c\x1e\xa9' b'\xc5\xa5\x1c\x2f\xec\x3f\x92\x91\xfe\x5c\x38\xcc\x50\x97\x49' b'\x07\xc0\x38\x3f\x74\x31\xfb\x17\xc8\x79\x60\x50\x6f\xcc\x1d' b'\xfc\x42\xd5\x4a\x07\xd1\x2d\x13\x5e\xa9\x82\xf4\xd0\xa5\xd5' b'\xb3\x4e\x3f\x14\xe0\x44\x86\xa4\xa2\xaa\x2f\xe8\x1d\x82\x78' b'\x83\x13\x6b\x4a\x82\x0d\x5f\xbd\x4f\x1d\x56\xda\x12\x29\x08' b'\xca\x0c\xe2\xe0\x76\x55\xc8\xcb\xad\xdc\xb1\x3a\x71\xe1\xf3' b'\x7d\x28\xfb\xd5\xfb\x67\xf9\x48\xb4\x4f\x39\x0b\x39\xbf\x8d' b'\xa0\x13\xf7\xd6\x16\x87\x0b\xfb\x1f\x0a\xba\x4a\x83\xb4\x2d' b'\x50\xff\x6a\xf5\xd4\x6a\xe9\xd6\x5c\x23\x5e\xea\xe5\xde\xe8' b'\x11\xd1\x13\x78\x34\x4a\x85\x3d\xaf\x9b\xb6\xf1\xd9\xb2\xc6' b'\x78\x5d\x70\xd8\x7f\x41\xfd\x5f\x35\xba\x98\xe2\x01\xa8\x76' b'\x45\x59\xde\x71\x02\x81\x81\x00\xec\x7c\x74\xa3\x47\x58\x1d' b'\xf9\x21\xf0\xff\x60\x3d\x49\xa5\xd2\xd6\x4f\x4b\x79\x72\xed' b'\xf9\x46\xc3\x41\xd6\xe3\x60\xeb\x21\xe4\xba\x13\xf8\x43\x7f' b'\xba\xd3\xbb\xd1\x1c\x83\x62\xa8\xe5\x87\x3a\x89\xcd\xc8\x8a' b'\x4e\xe0\x16\xe5\x25\x4f\x0b\xa8\x10\xb8\x2a\x69\x03\x6f\x4a' b'\x9e\xda\xbb\xc7\x5f\x8b\xc3\xfe\x30\x1b\xde\x3b\xa6\x85\xdb' b'\xeb\x4b\x4b\x76\x0d\xc1\x2b\x99\x81\x15\x33\x91\x93\x90\x13' b'\xa8\x0c\x15\xab\xbb\x7e\xd8\xdb\x52\xe5\x2f\xc9\xba\x7c\xec' b'\xe7\x1a\xd1\xa2\x50\xc5\x9d\x25\xf8\x2a\x7b\xd5\x97\xa2\x63' b'\xdd\x02\x81\x81\x00\xc2\x39\x76\x53\x55\x74\x4f\x10\x58\x67' b'\xaa\x7a\x8b\x12\xb6\x5e\xe8\x42\x64\xc9\x2c\x06\xf3\x08\x2d' b'\x39\xd0\xa6\xaf\xae\xb4\x6e\x87\x18\xd6\x2f\x6f\x57\xe4\x5a' b'\x33\x58\x80\x44\x75\xfa\xbb\xfb\x2e\x32\x19\x33\xfb\x72\x91' b'\x8a\x7c\xf1\x20\x6e\x60\x42\xcc\xa2\x5a\x64\xe9\x15\x5d\xbd' b'\xf1\x6f\x6f\x91\x1b\x66\xb0\x24\x03\x9f\x69\xb2\xf7\x4c\xaf' b'\xe1\xee\xac\x2c\x8d\x27\x83\xb9\x7f\x37\x7a\xfb\x0b\x02\xcb' b'\x34\x85\x7f\x0a\xa7\xb2\x68\xde\x34\xb2\xec\xc4\xf0\x08\xe0' b'\x12\x06\xb9\x8d\x3b\x9a\xe9\xb3\xf9\x9b\xec\x7c\x7b\x02\x81' b'\x81\x00\x9e\xb9\x6d\xc3\xc5\x77\xe4\x2e\x39\xd4\xba\x63\x0a' b'\xdf\xaa\x97\xd7\x55\xc3\x6f\x91\x6f\x1e\x37\x9b\x88\x4e\x45' b'\xb0\xe0\x40\x90\x77\x40\x3e\x0a\x77\xe9\x9a\x81\x5d\xfa\x08' b'\x49\x28\xd9\x5d\xa9\x31\xa2\xd7\xed\xd4\xc0\xdd\x3d\x11\x8c' b'\x7b\x63\x63\x4d\x68\xd1\xb1\x07\x7a\x8b\x22\x7e\x94\x73\x91' b'\xa8\x8b\xac\x18\x98\x51\x6b\x14\x3f\x26\x2f\x14\x47\xf9\x35' b'\x65\x21\x13\x9d\x7a\x4e\x44\x3f\x98\xa1\xda\xf2\x94\xa0\x34' b'\xa4\x32\x98\xf1\xd0\xe0\x51\xf5\xd5\x3f\xcc\x25\x56\x0f\x66' b'\x83\x72\x5f\x9d\x8c\x1e\x31\x37\x42\x55\x02\x81\x81\x00\xb1' b'\xd7\x7d\xe2\x36\x68\x26\x91\x37\xf1\xcc\x67\x22\xfb\x02\x64' b'\x8a\xd5\x68\x85\xd0\x3b\x98\xc3\x8e\xed\xd6\x81\x1a\x72\xa5' b'\x22\x63\xaf\xb9\x47\x7b\xf3\x85\xd3\x96\x1a\x5e\x70\xd1\x7a' b'\xc2\x2f\xf0\x0f\xcd\x86\x0c\xa2\xce\x63\x79\x9e\x2c\xed\x04' b'\x55\x86\x1c\xcf\x1a\x81\x56\xa0\x1c\x71\x7b\x71\x33\xf4\x5c' b'\x25\xc3\x04\x52\x2e\xad\xc1\xc5\xc5\x72\xe2\x61\x62\xf5\xe9' b'\x0d\xb3\x87\xaa\x5c\x80\x8c\x87\x85\x5b\xd5\x35\x0b\xa3\x9c' b'\x38\x6b\xe6\xe3\x42\xeb\xdd\x42\xb3\x31\xae\x58\xae\xda\xba' b'\x31\x6e\x2b\x8b\xbb\x92\x0b\x02\x81\x81\x00\xdf\x76\xa5\x63' b'\x4f\x8b\x97\x98\x6c\x0e\x87\x5c\xf8\x3f\x3b\xfa\x18\x2a\x1c' b'\xfb\xa1\xa8\x6d\x78\x38\x0e\xfb\xc2\x52\x33\xfd\x31\x1f\xb6' b'\xfb\x9b\x17\xd0\x06\x3f\x7f\xe6\x95\x08\x3d\x39\xfc\xd8\xf4' b'\x46\xaa\x40\xc1\x47\x34\xdf\x36\x54\xe5\x9b\x4b\xda\xe3\x5e' b'\xe9\x70\xe3\x12\xe8\x1f\x16\xd9\x73\x79\xae\xbe\xad\xb0\xfa' b'\x2a\x91\x52\xfa\x7c\x4f\x24\x0f\x18\xc9\x66\x11\xa4\xd8\x69' b'\x45\x61\x96\x41\xa9\x07\x79\xda\xf7\x06\xd3\x2d\x1a\xcd\x21' b'\xa4\xa3\x40\x40\x6e\xf6\x1c\xa5\xad\x49\xf2\x50\x31\x7b\xe7' b'\xd9\x19\x62\x70') return key_der def get_public_key_der(): """Returns a public key in DER format This key was created by issuing the following openssl commands: openssl genrsa -out private.pem 2048 openssl rsa -in private.pem -pubout > public.pem The byte string returned by this function is the contents of the public.der file. """ key_der = ( b'\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01' b'\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82' b'\x01\x01\x00\xb3\x6b\x65\x68\x0d\x79\x81\x50\xc9\xb0\x8c\x5b' b'\xbd\x17\xa3\x0c\xe6\xaf\xc0\x67\x55\xa3\x9d\x60\x36\x60\xd7' b'\x4d\xcb\x6d\xfb\x4e\xb1\x8d\xfe\x7a\x1b\x0c\x3b\xfc\x14\x10' b'\x69\x50\xf9\x87\x35\x9d\x38\x1f\x52\xf2\xc4\x57\x0f\xf1\x17' b'\x85\xad\xc2\x17\xa6\x27\xec\x45\xeb\xb6\x94\x05\x9a\xa9\x13' b'\xf1\xa2\xfb\xb9\x0a\xe0\x21\x7d\xe7\x0a\xbf\xe4\x61\x8c\xb5' b'\x4b\x27\x42\x3e\x31\x92\x1b\xef\x64\x4e\x2a\x97\xd9\x4e\x66' b'\xfb\x76\x19\x45\x80\x60\xf7\xbe\x40\xb9\xd4\x10\x9f\x84\x65' b'\x56\xdf\x9c\x39\xd8\xe6\x3f\xdb\x7c\x79\x31\xe3\xb8\xca\xfc' b'\x79\x9b\x23\xdc\x72\x7c\x4c\x55\x0e\x36\x2a\xe0\xeb\xcc\xaa' b'\xa3\x06\x54\xa3\x98\x19\xdc\xa4\x66\x31\xd0\x98\x02\x4f\xeb' b'\x32\x16\x61\xec\x97\xca\xce\x92\xa0\x8f\x3c\x52\xe8\xdb\x86' b'\x10\x9f\xee\x3f\xa6\xbd\x40\x63\x06\x99\x01\xb3\x13\x97\xdc' b'\xe8\x2e\xd1\x10\x8f\xab\x31\x49\xcb\x87\x71\x2f\x5e\xf2\x78' b'\xa9\xb4\x3c\x65\xb1\xb2\xd0\x82\xa1\x95\x68\x67\x44\xd7\x5e' b'\xec\xb4\x2f\x79\x40\x7e\xd4\xbc\x84\xdb\xb9\x8c\xdd\x8d\x9c' b'\x01\x15\xcd\x52\x83\x3f\x06\x67\xfd\xa1\x2d\x2b\x07\xba\x32' b'\x62\x21\x07\x2f\x02\x03\x01\x00\x01') return key_der def get_symmetric_key(): """Returns symmetric key bytes 16 bytes that were randomly generated. Form a 128 bit key. """ symmetric_key = ( b'\x92\xcf\x1e\xd9\x54\xea\x30\x70\xd8\xc2\x48\xae\xc1\xc8\x72\xa3') return symmetric_key castellan-3.0.1/castellan/tests/unit/0000775000175000017500000000000013645116331017575 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/unit/test_config_driver.py0000664000175000017500000000755413645116250024041 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Functional test cases for the Castellan Oslo Config Driver. Note: This requires local running instance of Vault. """ import tempfile from oslo_config import cfg from oslo_config import fixture from oslotest import base from castellan import _config_driver from castellan.common.objects import opaque_data from castellan.tests.unit.key_manager import fake class CastellanSourceTestCase(base.BaseTestCase): def setUp(self): super(CastellanSourceTestCase, self).setUp() self.driver = _config_driver.CastellanConfigurationSourceDriver() self.conf = cfg.ConfigOpts() self.conf_fixture = self.useFixture(fixture.Config(self.conf)) def test_incomplete_driver(self): # The group exists, but does not specify the # required options for this driver. self.conf_fixture.load_raw_values( group='incomplete_driver', driver='castellan', ) source = self.conf._open_source_from_opt_group('incomplete_driver') self.assertIsNone(source) self.assertEqual(self.conf.incomplete_driver.driver, 'castellan') def test_complete_driver(self): self.conf_fixture.load_raw_values( group='castellan_source', driver='castellan', config_file='config.conf', mapping_file='mapping.conf', ) with base.mock.patch.object( _config_driver, 'CastellanConfigurationSource') as source_class: self.driver.open_source_from_opt_group( self.conf, 'castellan_source') source_class.assert_called_once_with( 'castellan_source', self.conf.castellan_source.config_file, self.conf.castellan_source.mapping_file) def test_fetch_secret(self): # fake KeyManager populated with secret km = fake.fake_api() secret_id = km.store("fake_context", opaque_data.OpaqueData(b"super_secret!")) # driver config config = "[key_manager]\nbackend=vault" mapping = "[DEFAULT]\nmy_secret=" + secret_id # creating temp files with tempfile.NamedTemporaryFile() as config_file: config_file.write(config.encode("utf-8")) config_file.flush() with tempfile.NamedTemporaryFile() as mapping_file: mapping_file.write(mapping.encode("utf-8")) mapping_file.flush() self.conf_fixture.load_raw_values( group='castellan_source', driver='castellan', config_file=config_file.name, mapping_file=mapping_file.name, ) source = self.driver.open_source_from_opt_group( self.conf, 'castellan_source') # replacing key_manager with fake one source._mngr = km # testing if the source is able to retrieve # the secret value stored in the key_manager # using the secret_id from the mapping file self.assertEqual("super_secret!", source.get("DEFAULT", "my_secret", cfg.StrOpt(""))[0]) castellan-3.0.1/castellan/tests/unit/credentials/0000775000175000017500000000000013645116331022072 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/unit/credentials/test_keystone_token.py0000664000175000017500000002351713645116250026554 0ustar zuulzuul00000000000000# Copyright (c) 2015 IBM # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the keystone token credential """ from castellan.common.credentials import keystone_token from castellan.tests import base class KeystoneTokenTestCase(base.TestCase): def _create_ks_token_credential(self): return keystone_token.KeystoneToken( self.token, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) def setUp(self): self.token = "8a4aa147d58141c39a7a22905b90ba4e", self.trust_id = "14b38a8296f144148138466ce9280940", self.domain_id = "default", self.domain_name = "default", self.project_id = "1099302ec608486f9879ba2466c60720", self.project_name = "demo", self.project_domain_id = "default", self.project_domain_name = "default", self.reauthenticate = True self.ks_token_credential = self._create_ks_token_credential() super(KeystoneTokenTestCase, self).setUp() def test_get_token(self): self.assertEqual(self.token, self.ks_token_credential.token) def test_get_trust_id(self): self.assertEqual(self.trust_id, self.ks_token_credential.trust_id) def test_get_domain_id(self): self.assertEqual(self.domain_id, self.ks_token_credential.domain_id) def test_get_domain_name(self): self.assertEqual(self.domain_name, self.ks_token_credential.domain_name) def test_get_project_id(self): self.assertEqual(self.project_id, self.ks_token_credential.project_id) def test_get_project_name(self): self.assertEqual(self.project_name, self.ks_token_credential.project_name) def test_get_project_domain_id(self): self.assertEqual(self.project_domain_id, self.ks_token_credential.project_domain_id) def test_get_project_domain_name(self): self.assertEqual(self.project_domain_name, self.ks_token_credential.project_domain_name) def test_get_reauthenticate(self): self.assertEqual(self.reauthenticate, self.ks_token_credential.reauthenticate) def test___eq__(self): self.assertTrue(self.ks_token_credential == self.ks_token_credential) self.assertTrue(self.ks_token_credential is self.ks_token_credential) self.assertFalse(self.ks_token_credential is None) self.assertFalse(None == self.ks_token_credential) other_ks_token_credential = keystone_token.KeystoneToken( self.token, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_token_credential == other_ks_token_credential) self.assertFalse(self.ks_token_credential is other_ks_token_credential) def test___ne___none(self): self.assertTrue(self.ks_token_credential is not None) self.assertTrue(None != self.ks_token_credential) def test___ne___token(self): other_token = "5c59e3217d3d4dd297589b297aee2a6f" other_ks_token_credential = keystone_token.KeystoneToken( other_token, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_token_credential != other_ks_token_credential) def test___ne___trust_id(self): other_trust_id = "00000000000000" other_ks_token_credential = keystone_token.KeystoneToken( self.token, trust_id=other_trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_token_credential != other_ks_token_credential) def test___ne___domain_id(self): other_domain_id = "domain0" other_ks_token_credential = keystone_token.KeystoneToken( self.token, trust_id=self.trust_id, domain_id=other_domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_token_credential != other_ks_token_credential) def test___ne___domain_name(self): other_domain_name = "domain0" other_ks_token_credential = keystone_token.KeystoneToken( self.token, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=other_domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_token_credential != other_ks_token_credential) def test___ne___project_id(self): other_project_id = "00000000000000" other_ks_token_credential = keystone_token.KeystoneToken( self.token, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=other_project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_token_credential != other_ks_token_credential) def test___ne___project_name(self): other_project_name = "proj0" other_ks_token_credential = keystone_token.KeystoneToken( self.token, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=other_project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_token_credential != other_ks_token_credential) def test___ne___project_domain_id(self): other_project_domain_id = "domain0" other_ks_token_credential = keystone_token.KeystoneToken( self.token, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=other_project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_token_credential != other_ks_token_credential) def test___ne___project_domain_name(self): other_project_domain_name = "domain0" other_ks_token_credential = keystone_token.KeystoneToken( self.token, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=other_project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_token_credential != other_ks_token_credential) def test___ne___reauthenticate(self): other_reauthenticate = False other_ks_token_credential = keystone_token.KeystoneToken( self.token, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=other_reauthenticate) self.assertTrue(self.ks_token_credential != other_ks_token_credential) castellan-3.0.1/castellan/tests/unit/credentials/test_password.py0000664000175000017500000000521013645116250025343 0ustar zuulzuul00000000000000# Copyright (c) 2015 IBM # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the password credential """ from castellan.common.credentials import password from castellan.tests import base class PasswordTestCase(base.TestCase): def _create_password_credential(self): return password.Password(self.username, self.password) def setUp(self): self.username = "admin" self.password = "Pa$$w0rd1" self.password_credential = self._create_password_credential() super(PasswordTestCase, self).setUp() def test_get_username(self): self.assertEqual(self.username, self.password_credential.username) def test_get_password(self): self.assertEqual(self.password, self.password_credential.password) def test___eq__(self): self.assertTrue(self.password_credential == self.password_credential) self.assertTrue(self.password_credential is self.password_credential) self.assertFalse(self.password_credential is None) self.assertFalse(None == self.password_credential) other_password_credential = password.Password(self.username, self.password) self.assertTrue(self.password_credential == other_password_credential) self.assertFalse(self.password_credential is other_password_credential) def test___ne___none(self): self.assertTrue(self.password_credential is not None) self.assertTrue(None != self.password_credential) def test___ne___username(self): other_username = "service" other_password_credential = password.Password(other_username, self.password) self.assertTrue(self.password_credential != other_password_credential) def test___ne___password(self): other_password = "i143Cats" other_password_credential = password.Password(self.username, other_password) self.assertTrue(self.password_credential != other_password_credential) castellan-3.0.1/castellan/tests/unit/credentials/test_keystone_password.py0000664000175000017500000003754013645116250027277 0ustar zuulzuul00000000000000# Copyright (c) 2015 IBM # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the keystone password credential """ from castellan.common.credentials import keystone_password from castellan.tests import base class KeystonePasswordTestCase(base.TestCase): def _create_ks_password_credential(self): return keystone_password.KeystonePassword( self.password, username=self.username, user_id=self.user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) def setUp(self): self.password = "Pa$$w0rd1", self.username = "admin", self.user_id = "1adb2391c009443aa5224b316d4a06ae", self.user_domain_id = "default", self.user_domain_name = "default", self.trust_id = "14b38a8296f144148138466ce9280940", self.domain_id = "default", self.domain_name = "default", self.project_id = "1099302ec608486f9879ba2466c60720", self.project_name = "demo", self.project_domain_id = "default", self.project_domain_name = "default", self.reauthenticate = True self.ks_password_credential = self._create_ks_password_credential() super(KeystonePasswordTestCase, self).setUp() def test_get_password(self): self.assertEqual(self.password, self.ks_password_credential.password) def test_get_username(self): self.assertEqual(self.username, self.ks_password_credential.username) def test_get_user_id(self): self.assertEqual(self.user_id, self.ks_password_credential.user_id) def test_get_user_domain_id(self): self.assertEqual(self.user_domain_id, self.ks_password_credential.user_domain_id) def test_get_user_domain_name(self): self.assertEqual(self.user_domain_name, self.ks_password_credential.user_domain_name) def test_get_trust_id(self): self.assertEqual(self.trust_id, self.ks_password_credential.trust_id) def test_get_domain_id(self): self.assertEqual(self.domain_id, self.ks_password_credential.domain_id) def test_get_domain_name(self): self.assertEqual(self.domain_name, self.ks_password_credential.domain_name) def test_get_project_id(self): self.assertEqual(self.project_id, self.ks_password_credential.project_id) def test_get_project_name(self): self.assertEqual(self.project_name, self.ks_password_credential.project_name) def test_get_project_domain_id(self): self.assertEqual(self.project_domain_id, self.ks_password_credential.project_domain_id) def test_get_project_domain_name(self): self.assertEqual(self.project_domain_name, self.ks_password_credential.project_domain_name) def test_get_reauthenticate(self): self.assertEqual(self.reauthenticate, self.ks_password_credential.reauthenticate) def test___eq__(self): self.assertTrue(self.ks_password_credential == self.ks_password_credential) self.assertTrue(self.ks_password_credential is self.ks_password_credential) self.assertFalse(self.ks_password_credential is None) self.assertFalse(None == self.ks_password_credential) other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=self.username, user_id=self.user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential == other_ks_password_credential) self.assertFalse(self.ks_password_credential is other_ks_password_credential) def test___ne___none(self): self.assertTrue(self.ks_password_credential is not None) self.assertTrue(None != self.ks_password_credential) def test___ne___password(self): other_password = "wheresmyCat??" other_ks_password_credential = keystone_password.KeystonePassword( other_password, username=self.username, user_id=self.user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) def test___ne___username(self): other_username = "service" other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=other_username, user_id=self.user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) def test___ne___user_id(self): other_user_id = "service" other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=self.username, user_id=other_user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) def test___ne___user_domain_id(self): other_user_domain_id = "domain0" other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=self.username, user_id=self.user_id, user_domain_id=other_user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) def test___ne___user_domain_name(self): other_user_domain_name = "domain0" other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=self.username, user_id=self.user_id, user_domain_id=self.domain_id, user_domain_name=other_user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) def test___ne___trust_id(self): other_trust_id = "00000000000000" other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=self.username, user_id=self.user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=other_trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) def test___ne___domain_id(self): other_domain_id = "domain0" other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=self.username, user_id=self.user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=other_domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) def test___ne___domain_name(self): other_domain_name = "domain0" other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=self.username, user_id=self.user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=other_domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) def test___ne___project_id(self): other_project_id = "00000000000000" other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=self.username, user_id=self.user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=other_project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) def test___ne___project_name(self): other_project_name = "proj0" other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=self.username, user_id=self.user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=other_project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) def test___ne___project_domain_id(self): other_project_domain_id = "domain0" other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=self.username, user_id=self.user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=other_project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) def test___ne___project_domain_name(self): other_project_domain_name = "domain0" other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=self.username, user_id=self.user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=other_project_domain_name, reauthenticate=self.reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) def test___ne___reauthenticate(self): other_reauthenticate = False other_ks_password_credential = keystone_password.KeystonePassword( self.password, username=self.username, user_id=self.user_id, user_domain_id=self.user_domain_id, user_domain_name=self.user_domain_name, trust_id=self.trust_id, domain_id=self.domain_id, domain_name=self.domain_name, project_id=self.project_id, project_name=self.project_name, project_domain_id=self.project_domain_id, project_domain_name=self.project_domain_name, reauthenticate=other_reauthenticate) self.assertTrue(self.ks_password_credential != other_ks_password_credential) castellan-3.0.1/castellan/tests/unit/credentials/test_token.py0000664000175000017500000000372713645116250024634 0ustar zuulzuul00000000000000# Copyright (c) 2015 IBM # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the token credential """ from castellan.common.credentials import token from castellan.tests import base class TokenTestCase(base.TestCase): def _create_token_credential(self): return token.Token(self.token) def setUp(self): self.token = "8a4aa147d58141c39a7a22905b90ba4e" self.token_credential = self._create_token_credential() super(TokenTestCase, self).setUp() def test_get_token(self): self.assertEqual(self.token, self.token_credential.token) def test___eq__(self): self.assertTrue(self.token_credential == self.token_credential) self.assertTrue(self.token_credential is self.token_credential) self.assertFalse(self.token_credential is None) self.assertFalse(None == self.token_credential) other_token_credential = token.Token(self.token) self.assertTrue(self.token_credential == other_token_credential) self.assertFalse(self.token_credential is other_token_credential) def test___ne___none(self): self.assertTrue(self.token_credential is not None) self.assertTrue(None != self.token_credential) def test___ne___token(self): other_token = "fe32af1fe47e4744a48254e60ae80012" other_token_credential = token.Token(other_token) self.assertTrue(self.token_credential != other_token_credential) castellan-3.0.1/castellan/tests/unit/credentials/__init__.py0000664000175000017500000000000013645116250024171 0ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/unit/test_options.py0000664000175000017500000000553013645116250022704 0ustar zuulzuul00000000000000# Copyright (c) 2015 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_config import cfg from castellan import key_manager from castellan.key_manager import barbican_key_manager as bkm from castellan import options from castellan.tests import base from castellan.tests.unit.key_manager import mock_key_manager class TestOptions(base.TestCase): def test_set_defaults(self): conf = cfg.ConfigOpts() self.assertTrue(isinstance(key_manager.API(conf), bkm.BarbicanKeyManager)) cls = mock_key_manager.MockKeyManager backend = '%s.%s' % (cls.__module__, cls.__name__) options.set_defaults(conf, backend=backend) self.assertEqual(backend, conf.key_manager.backend) self.assertIsInstance(key_manager.API(conf), mock_key_manager.MockKeyManager) barbican_endpoint = 'http://test-server.org:9311/' options.set_defaults(conf, barbican_endpoint=barbican_endpoint) self.assertEqual(barbican_endpoint, conf.barbican.barbican_endpoint) barbican_api_version = 'vSomething' options.set_defaults(conf, barbican_api_version=barbican_api_version) self.assertEqual(barbican_api_version, conf.barbican.barbican_api_version) auth_endpoint = 'http://test-server.org/identity' options.set_defaults(conf, auth_endpoint=auth_endpoint) self.assertEqual(auth_endpoint, conf.barbican.auth_endpoint) retry_delay = 3 options.set_defaults(conf, retry_delay=retry_delay) self.assertEqual(retry_delay, conf.barbican.retry_delay) number_of_retries = 10 options.set_defaults(conf, number_of_retries=number_of_retries) self.assertEqual(number_of_retries, conf.barbican.number_of_retries) verify_ssl = True options.set_defaults(conf, verify_ssl=True) self.assertEqual(verify_ssl, conf.barbican.verify_ssl) barbican_endpoint_type = 'internal' options.set_defaults(conf, barbican_endpoint_type='internal') result_type = conf.barbican.barbican_endpoint_type self.assertEqual(barbican_endpoint_type, result_type) castellan-3.0.1/castellan/tests/unit/objects/0000775000175000017500000000000013645116331021226 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/unit/objects/test_x_509.py0000664000175000017500000000511213645116250023502 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the X.509 certificate class. """ from castellan.common.objects import x_509 from castellan.tests import base from castellan.tests import utils class X509TestCase(base.CertificateTestCase): def _create_cert(self): return x_509.X509(self.data, self.name, self.created) def setUp(self): self.data = utils.get_certificate_der() self.name = 'my cert' self.created = 1448088699 super(X509TestCase, self).setUp() def test_is_not_only_metadata(self): self.assertFalse(self.cert.is_metadata_only()) def test_is_only_metadata(self): c = x_509.X509(None, self.name, self.created) self.assertTrue(c.is_metadata_only()) def test_get_format(self): self.assertEqual('X.509', self.cert.format) def test_get_name(self): self.assertEqual(self.name, self.cert.name) def test_get_encoded(self): self.assertEqual(self.data, self.cert.get_encoded()) def test_get_created(self): self.assertEqual(self.created, self.cert.created) def test_get_created_none(self): created = None cert = x_509.X509(self.data, self.name, created) self.assertEqual(created, cert.created) def test___eq__(self): self.assertTrue(self.cert == self.cert) self.assertTrue(self.cert is self.cert) self.assertFalse(self.cert is None) self.assertFalse(None == self.cert) other_x_509 = x_509.X509(self.data) self.assertTrue(self.cert == other_x_509) self.assertFalse(self.cert is other_x_509) def test___ne___none(self): self.assertTrue(self.cert is not None) self.assertTrue(None != self.cert) def test___ne___data(self): other_x509 = x_509.X509(b'\x00\x00\x00', self.name) self.assertTrue(self.cert != other_x509) castellan-3.0.1/castellan/tests/unit/objects/test_opaque.py0000664000175000017500000000551413645116250024136 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the opaque data class. """ from castellan.common.objects import opaque_data from castellan.tests import base class OpaqueDataTestCase(base.TestCase): def _create_data(self): return opaque_data.OpaqueData(self.data, self.name, self.created) def setUp(self): self.data = bytes(b"secret opaque data") self.name = 'my opaque' self.created = 1448088699 self.opaque_data = self._create_data() super(OpaqueDataTestCase, self).setUp() def test_is_not_only_metadata(self): self.assertFalse(self.opaque_data.is_metadata_only()) def test_is_only_metadata(self): d = opaque_data.OpaqueData(None, self.name, self.created) self.assertTrue(d.is_metadata_only()) def test_get_format(self): self.assertEqual('Opaque', self.opaque_data.format) def test_get_encoded(self): self.assertEqual(self.data, self.opaque_data.get_encoded()) def test_get_name(self): self.assertEqual(self.name, self.opaque_data.name) def test_get_created(self): self.assertEqual(self.created, self.opaque_data.created) def test_get_created_none(self): created = None data = opaque_data.OpaqueData(self.data, self.name, created) self.assertEqual(created, data.created) def test___eq__(self): self.assertTrue(self.opaque_data == self.opaque_data) self.assertTrue(self.opaque_data is self.opaque_data) self.assertFalse(self.opaque_data is None) self.assertFalse(None == self.opaque_data) other_opaque_data = opaque_data.OpaqueData(self.data) self.assertTrue(self.opaque_data == other_opaque_data) self.assertFalse(self.opaque_data is other_opaque_data) def test___ne___none(self): self.assertTrue(self.opaque_data is not None) self.assertTrue(None != self.opaque_data) def test___ne___data(self): other_opaque = opaque_data.OpaqueData(b'other data', self.name) self.assertTrue(self.opaque_data != other_opaque) castellan-3.0.1/castellan/tests/unit/objects/test_public_key.py0000664000175000017500000001031713645116250024767 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the public key class. """ from castellan.common.objects import public_key from castellan.tests import base from castellan.tests import utils class PublicKeyTestCase(base.KeyTestCase): def _create_key(self): return public_key.PublicKey(self.algorithm, self.bit_length, self.encoded, self.name, self.created) def setUp(self): self.algorithm = 'RSA' self.bit_length = 2048 self.encoded = bytes(utils.get_public_key_der()) self.name = 'my key' self.created = 1448088699 super(PublicKeyTestCase, self).setUp() def test_is_not_only_metadata(self): self.assertFalse(self.key.is_metadata_only()) def test_is_only_metadata(self): k = public_key.PublicKey(self.algorithm, self.bit_length, None, self.name, self.created) self.assertTrue(k.is_metadata_only()) def test_get_algorithm(self): self.assertEqual(self.algorithm, self.key.algorithm) def test_get_bit_length(self): self.assertEqual(self.bit_length, self.key.bit_length) def test_get_name(self): self.assertEqual(self.name, self.key.name) def test_get_format(self): self.assertEqual('SubjectPublicKeyInfo', self.key.format) def test_get_encoded(self): self.assertEqual(self.encoded, self.key.get_encoded()) def test_get_created(self): self.assertEqual(self.created, self.key.created) def test_get_created_none(self): created = None key = public_key.PublicKey(self.algorithm, self.bit_length, self.encoded, self.name, created) self.assertEqual(created, key.created) def test___eq__(self): self.assertTrue(self.key == self.key) self.assertTrue(self.key is self.key) self.assertFalse(self.key is None) self.assertFalse(None == self.key) other_key = public_key.PublicKey(self.algorithm, self.bit_length, self.encoded) self.assertTrue(self.key == other_key) self.assertFalse(self.key is other_key) def test___ne___none(self): self.assertTrue(self.key is not None) self.assertTrue(None != self.key) def test___ne___algorithm(self): other_key = public_key.PublicKey('DSA', self.bit_length, self.encoded, self.name) self.assertTrue(self.key != other_key) def test___ne___bit_length(self): other_key = public_key.PublicKey(self.algorithm, 4096, self.encoded, self.name) self.assertTrue(self.key != other_key) def test___ne___encoded(self): different_encoded = bytes(utils.get_public_key_der()) + b'\x00' other_key = public_key.PublicKey(self.algorithm, self.bit_length, different_encoded, self.name) self.assertTrue(self.key != other_key) castellan-3.0.1/castellan/tests/unit/objects/test_symmetric_key.py0000664000175000017500000001024713645116250025527 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the symmetric key class. """ from castellan.common.objects import symmetric_key as sym_key from castellan.tests import base class SymmetricKeyTestCase(base.KeyTestCase): def _create_key(self): return sym_key.SymmetricKey(self.algorithm, self.bit_length, self.encoded, self.name, self.created) def setUp(self): self.algorithm = 'AES' self.encoded = bytes(b'0' * 64) self.bit_length = len(self.encoded) * 8 self.name = 'my key' self.created = 1448088699 super(SymmetricKeyTestCase, self).setUp() def test_is_not_only_metadata(self): self.assertFalse(self.key.is_metadata_only()) def test_is_only_metadata(self): k = sym_key.SymmetricKey(self.algorithm, self.bit_length, None, self.name, self.created) self.assertTrue(k.is_metadata_only()) def test_get_format(self): self.assertEqual('RAW', self.key.format) def test_get_name(self): self.assertEqual(self.name, self.key.name) def test_get_encoded(self): self.assertEqual(self.encoded, self.key.get_encoded()) def test_get_algorithm(self): self.assertEqual(self.algorithm, self.key.algorithm) def test_get_bit_length(self): self.assertEqual(self.bit_length, self.key.bit_length) def test_get_created(self): self.assertEqual(self.created, self.key.created) def test_get_created_none(self): created = None key = sym_key.SymmetricKey(self.algorithm, self.bit_length, self.encoded, self.name, created) self.assertEqual(created, key.created) def test___eq__(self): self.assertTrue(self.key == self.key) self.assertTrue(self.key is self.key) self.assertFalse(self.key is None) self.assertFalse(None == self.key) other_key = sym_key.SymmetricKey(self.algorithm, self.bit_length, self.encoded) self.assertTrue(self.key == other_key) self.assertFalse(self.key is other_key) def test___ne___none(self): self.assertTrue(self.key is not None) self.assertTrue(None != self.key) def test___ne___algorithm(self): other_key = sym_key.SymmetricKey('DES', self.bit_length, self.encoded, self.name) self.assertTrue(self.key != other_key) def test___ne___bit_length(self): other_key = sym_key.SymmetricKey(self.algorithm, self.bit_length * 2, self.encoded, self.name) self.assertTrue(self.key != other_key) def test___ne___encoded(self): different_encoded = self.encoded * 2 other_key = sym_key.SymmetricKey(self.algorithm, self.bit_length, different_encoded, self.name) self.assertTrue(self.key != other_key) castellan-3.0.1/castellan/tests/unit/objects/test_private_key.py0000664000175000017500000001040213645116250025156 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the private key class. """ from castellan.common.objects import private_key from castellan.tests import base from castellan.tests import utils class PrivateKeyTestCase(base.KeyTestCase): def _create_key(self): return private_key.PrivateKey(self.algorithm, self.bit_length, self.encoded, self.name, self.created) def setUp(self): self.algorithm = 'RSA' self.bit_length = 2048 self.encoded = bytes(utils.get_private_key_der()) self.name = 'my key' self.created = 1448088699 super(PrivateKeyTestCase, self).setUp() def test_is_not_only_metadata(self): self.assertFalse(self.key.is_metadata_only()) def test_is_only_metadata(self): k = private_key.PrivateKey(self.algorithm, self.bit_length, None, self.name, self.created) self.assertTrue(k.is_metadata_only()) def test_get_algorithm(self): self.assertEqual(self.algorithm, self.key.algorithm) def test_get_bit_length(self): self.assertEqual(self.bit_length, self.key.bit_length) def test_get_name(self): self.assertEqual(self.name, self.key.name) def test_get_format(self): self.assertEqual('PKCS8', self.key.format) def test_get_encoded(self): self.assertEqual(self.encoded, self.key.get_encoded()) def test_get_created(self): self.assertEqual(self.created, self.key.created) def test_get_created_none(self): created = None key = private_key.PrivateKey(self.algorithm, self.bit_length, self.encoded, self.name, created) self.assertEqual(created, key.created) def test___eq__(self): self.assertTrue(self.key == self.key) self.assertTrue(self.key is self.key) self.assertFalse(self.key is None) self.assertFalse(None == self.key) other_key = private_key.PrivateKey(self.algorithm, self.bit_length, self.encoded) self.assertTrue(self.key == other_key) self.assertFalse(self.key is other_key) def test___ne___none(self): self.assertTrue(self.key is not None) self.assertTrue(None != self.key) def test___ne___algorithm(self): other_key = private_key.PrivateKey('DSA', self.bit_length, self.encoded, self.name) self.assertTrue(self.key != other_key) def test___ne___bit_length(self): other_key = private_key.PrivateKey(self.algorithm, 4096, self.encoded, self.name) self.assertTrue(self.key != other_key) def test___ne___encoded(self): different_encoded = bytes(utils.get_private_key_der()) + b'\x00' other_key = private_key.PrivateKey(self.algorithm, self.bit_length, different_encoded, self.name) self.assertTrue(self.key != other_key) castellan-3.0.1/castellan/tests/unit/objects/test_passphrase.py0000664000175000017500000000557213645116250025021 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the passphrase class. """ from castellan.common.objects import passphrase from castellan.tests import base class PassphraseTestCase(base.TestCase): def _create_passphrase(self): return passphrase.Passphrase(self.passphrase_data, self.name, self.created) def setUp(self): self.passphrase_data = bytes(b"secret passphrase") self.name = 'my phrase' self.created = 1448088699 self.passphrase = self._create_passphrase() super(PassphraseTestCase, self).setUp() def test_is_not_only_metadata(self): self.assertFalse(self.passphrase.is_metadata_only()) def test_is_only_metadata(self): p = passphrase.Passphrase(None, self.name, self.created) self.assertTrue(p.is_metadata_only()) def test_get_format(self): self.assertEqual('RAW', self.passphrase.format) def test_get_encoded(self): self.assertEqual(self.passphrase_data, self.passphrase.get_encoded()) def test_get_name(self): self.assertEqual(self.name, self.passphrase.name) def test_get_created(self): self.assertEqual(self.created, self.passphrase.created) def test_get_created_none(self): created = None phrase = passphrase.Passphrase(self.passphrase_data, self.name, created) self.assertEqual(created, phrase.created) def test___eq__(self): self.assertTrue(self.passphrase == self.passphrase) self.assertTrue(self.passphrase is self.passphrase) self.assertFalse(self.passphrase is None) self.assertFalse(None == self.passphrase) other_passphrase = passphrase.Passphrase(self.passphrase_data) self.assertTrue(self.passphrase == other_passphrase) self.assertFalse(self.passphrase is other_passphrase) def test___ne___none(self): self.assertTrue(self.passphrase is not None) self.assertTrue(None != self.passphrase) def test___ne___data(self): other_phrase = passphrase.Passphrase(b"other passphrase", self.name) self.assertTrue(self.passphrase != other_phrase) castellan-3.0.1/castellan/tests/unit/objects/__init__.py0000664000175000017500000000000013645116250023325 0ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/unit/test_utils.py0000664000175000017500000001573513645116250022361 0ustar zuulzuul00000000000000# Copyright (c) 2016 IBM # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """ Test Common utilities for Castellan. """ from castellan.common import exception from castellan.common import utils from castellan.tests import base from oslo_config import cfg from oslo_config import fixture as config_fixture from oslo_context import context CONF = cfg.CONF class TestUtils(base.TestCase): def setUp(self): super(TestUtils, self).setUp() self.config_fixture = self.useFixture(config_fixture.Config(CONF)) CONF.register_opts(utils.credential_opts, group=utils.OPT_GROUP) def test_token_credential(self): token_value = 'ec9799cd921e4e0a8ab6111c08ebf065' self.config_fixture.config( auth_type='token', token=token_value, group='key_manager' ) token_context = utils.credential_factory(conf=CONF) token_context_class = token_context.__class__.__name__ self.assertEqual('Token', token_context_class) self.assertEqual(token_value, token_context.token) def test_token_credential_with_context(self): token_value = 'ec9799cd921e4e0a8ab6111c08ebf065' ctxt = context.RequestContext(auth_token=token_value) self.config_fixture.config( auth_type='token', group='key_manager' ) token_context = utils.credential_factory(conf=CONF, context=ctxt) token_context_class = token_context.__class__.__name__ self.assertEqual('Token', token_context_class) self.assertEqual(token_value, token_context.token) def test_token_credential_config_override_context(self): ctxt_token_value = '00000000000000000000000000000000' ctxt = context.RequestContext(auth_token=ctxt_token_value) conf_token_value = 'ec9799cd921e4e0a8ab6111c08ebf065' self.config_fixture.config( auth_type='token', token=conf_token_value, group='key_manager' ) token_context = utils.credential_factory(conf=CONF, context=ctxt) token_context_class = token_context.__class__.__name__ self.assertEqual('Token', token_context_class) self.assertEqual(conf_token_value, token_context.token) def test_token_credential_exception(self): self.config_fixture.config( auth_type='token', group='key_manager' ) self.assertRaises(exception.InsufficientCredentialDataError, utils.credential_factory, CONF) def test_password_credential(self): password_value = 'p4ssw0rd' self.config_fixture.config( auth_type='password', password=password_value, group='key_manager' ) password_context = utils.credential_factory(conf=CONF) password_context_class = password_context.__class__.__name__ self.assertEqual('Password', password_context_class) self.assertEqual(password_value, password_context.password) def test_keystone_token_credential(self): token_value = 'ec9799cd921e4e0a8ab6111c08ebf065' self.config_fixture.config( auth_type='keystone_token', token=token_value, group='key_manager' ) ks_token_context = utils.credential_factory(conf=CONF) ks_token_context_class = ks_token_context.__class__.__name__ self.assertEqual('KeystoneToken', ks_token_context_class) self.assertEqual(token_value, ks_token_context.token) def test_keystone_token_credential_with_context(self): token_value = 'ec9799cd921e4e0a8ab6111c08ebf065' ctxt = context.RequestContext(auth_token=token_value) self.config_fixture.config( auth_type='keystone_token', group='key_manager' ) ks_token_context = utils.credential_factory(conf=CONF, context=ctxt) ks_token_context_class = ks_token_context.__class__.__name__ self.assertEqual('KeystoneToken', ks_token_context_class) self.assertEqual(token_value, ks_token_context.token) def test_keystone_token_credential_config_override_context(self): ctxt_token_value = 'ec9799cd921e4e0a8ab6111c08ebf065' ctxt = context.RequestContext(auth_token=ctxt_token_value) conf_token_value = 'ec9799cd921e4e0a8ab6111c08ebf065' self.config_fixture.config( auth_type='keystone_token', token=conf_token_value, group='key_manager' ) ks_token_context = utils.credential_factory(conf=CONF, context=ctxt) ks_token_context_class = ks_token_context.__class__.__name__ self.assertEqual('KeystoneToken', ks_token_context_class) self.assertEqual(conf_token_value, ks_token_context.token) def test_keystone_token_credential_exception(self): self.config_fixture.config( auth_type='keystone_token', group='key_manager' ) self.assertRaises(exception.InsufficientCredentialDataError, utils.credential_factory, CONF) def test_keystone_password_credential(self): password_value = 'p4ssw0rd' self.config_fixture.config( auth_type='keystone_password', password=password_value, group='key_manager' ) ks_password_context = utils.credential_factory(conf=CONF) ks_password_context_class = ks_password_context.__class__.__name__ self.assertEqual('KeystonePassword', ks_password_context_class) self.assertEqual(password_value, ks_password_context.password) def test_oslo_context_to_keystone_token(self): auth_token_value = '16bd612f28ec479b8ffe8e124fc37b43' tenant_value = '00c6ef5ad2984af2acd7d42c299935c0' ctxt = context.RequestContext( auth_token=auth_token_value, tenant=tenant_value) ks_token_context = utils.credential_factory(context=ctxt) ks_token_context_class = ks_token_context.__class__.__name__ self.assertEqual('KeystoneToken', ks_token_context_class) self.assertEqual(auth_token_value, ks_token_context.token) self.assertEqual(tenant_value, ks_token_context.project_id) def test_invalid_auth_type(self): self.config_fixture.config( auth_type='hotdog', group='key_manager' ) self.assertRaises(exception.AuthTypeInvalidError, utils.credential_factory, conf=CONF) castellan-3.0.1/castellan/tests/unit/key_manager/0000775000175000017500000000000013645116331022057 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/unit/key_manager/fake.py0000664000175000017500000000151513645116250023341 0ustar zuulzuul00000000000000# Copyright 2011 Justin Santa Barbara # Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Implementation of a fake key manager.""" from castellan.tests.unit.key_manager import mock_key_manager def fake_api(): return mock_key_manager.MockKeyManager() castellan-3.0.1/castellan/tests/unit/key_manager/test_not_implemented_key_manager.py0000664000175000017500000000367713645116250031232 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the not implemented key manager. """ from castellan.key_manager import not_implemented_key_manager from castellan.tests.unit.key_manager import test_key_manager class NotImplementedKeyManagerTestCase(test_key_manager.KeyManagerTestCase): def _create_key_manager(self): return not_implemented_key_manager.NotImplementedKeyManager() def test_create_key(self): self.assertRaises(NotImplementedError, self.key_mgr.create_key, None) def test_create_key_pair(self): self.assertRaises(NotImplementedError, self.key_mgr.create_key_pair, None, None, None) def test_store(self): self.assertRaises(NotImplementedError, self.key_mgr.store, None, None) def test_copy(self): self.assertRaises(NotImplementedError, self.key_mgr.copy, None, None) def test_get(self): self.assertRaises(NotImplementedError, self.key_mgr.get, None, None) def test_list(self): self.assertRaises(NotImplementedError, self.key_mgr.list, None) def test_delete(self): self.assertRaises(NotImplementedError, self.key_mgr.delete, None, None) castellan-3.0.1/castellan/tests/unit/key_manager/mock_key_manager.py0000664000175000017500000002153213645116250025727 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ A mock implementation of a key manager that stores keys in a dictionary. This key manager implementation is primarily intended for testing. In particular, it does not store keys persistently. Lack of a centralized key store also makes this implementation unsuitable for use among different services. Note: Instantiating this class multiple times will create separate key stores. Keys created in one instance will not be accessible from other instances of this class. """ import binascii import copy import random from cryptography.hazmat import backends from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization from oslo_utils import uuidutils from castellan.common import exception from castellan.common.objects import private_key as pri_key from castellan.common.objects import public_key as pub_key from castellan.common.objects import symmetric_key as sym_key from castellan.key_manager import key_manager class MockKeyManager(key_manager.KeyManager): """Mocking manager for integration tests. This mock key manager implementation supports all the methods specified by the key manager interface. This implementation stores keys within a dictionary, and as a result, it is not acceptable for use across different services. Side effects (e.g., raising exceptions) for each method are handled as specified by the key manager interface. This key manager is not suitable for use in production deployments. """ def __init__(self, configuration=None): self.conf = configuration self.keys = {} def _generate_hex_key(self, key_length): # hex digit => 4 bits length = int(key_length / 4) hex_encoded = self._generate_password(length=length, symbolgroups='0123456789ABCDEF') return hex_encoded def _generate_key(self, **kwargs): name = kwargs.get('name', None) algorithm = kwargs.get('algorithm', 'AES') key_length = kwargs.get('length', 256) _hex = self._generate_hex_key(key_length) return sym_key.SymmetricKey( algorithm, key_length, bytes(binascii.unhexlify(_hex)), name) def create_key(self, context, **kwargs): """Creates a symmetric key. This implementation returns a UUID for the created key. The algorithm for the key will always be AES. A Forbidden exception is raised if the specified context is None. """ if context is None: raise exception.Forbidden() key = self._generate_key(**kwargs) return self.store(context, key) def _generate_public_and_private_key(self, length, name): crypto_private_key = rsa.generate_private_key( public_exponent=65537, key_size=length, backend=backends.default_backend()) private_der = crypto_private_key.private_bytes( encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption()) crypto_public_key = crypto_private_key.public_key() public_der = crypto_public_key.public_bytes( encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo) private_key = pri_key.PrivateKey( algorithm='RSA', bit_length=length, key=bytearray(private_der), name=name) public_key = pub_key.PublicKey( algorithm='RSA', bit_length=length, key=bytearray(public_der), name=name) return private_key, public_key def create_key_pair(self, context, algorithm, length, expiration=None, name=None): """Creates an asymmetric key pair. This implementation returns UUIDs for the created keys in the order: (private, public) Forbidden is raised if the context is None. """ if context is None: raise exception.Forbidden() if algorithm.lower() != 'rsa': msg = 'Invalid algorithm: {}, only RSA supported'.format(algorithm) raise ValueError(msg) valid_lengths = [2048, 3072, 4096] if length not in valid_lengths: msg = 'Invalid bit length: {}, only {} supported'.format( length, valid_lengths) raise ValueError(msg) private_key, public_key = self._generate_public_and_private_key(length, name) private_key_uuid = self.store(context, private_key) public_key_uuid = self.store(context, public_key) return private_key_uuid, public_key_uuid def _generate_key_id(self): key_id = uuidutils.generate_uuid() while key_id in self.keys: key_id = uuidutils.generate_uuid() return key_id def store(self, context, managed_object, **kwargs): """Stores (i.e., registers) a key with the key manager.""" if context is None: raise exception.Forbidden() key_id = self._generate_key_id() managed_object._id = key_id self.keys[key_id] = managed_object return key_id def get(self, context, managed_object_id, metadata_only=False, **kwargs): """Retrieves the key identified by the specified id. This implementation returns the key that is associated with the specified UUID. A Forbidden exception is raised if the specified context is None; a KeyError is raised if the UUID is invalid. """ if context is None: raise exception.Forbidden() obj = copy.deepcopy(self.keys[managed_object_id]) if metadata_only: if hasattr(obj, "_key"): obj._key = None if hasattr(obj, "_data"): obj._data = None if hasattr(obj, "_passphrase"): obj._passphrase = None return obj def delete(self, context, managed_object_id, **kwargs): """Deletes the object identified by the specified id. A Forbidden exception is raised if the context is None and a KeyError is raised if the UUID is invalid. """ if context is None: raise exception.Forbidden() del self.keys[managed_object_id] def _generate_password(self, length, symbolgroups): """Generate a random password from the supplied symbol groups. At least one symbol from each group will be included. Unpredictable results if length is less than the number of symbol groups. Believed to be reasonably secure (with a reasonable password length!) """ # NOTE(jerdfelt): Some password policies require at least one character # from each group of symbols, so start off with one random character # from each symbol group password = [random.choice(s) for s in symbolgroups] # If length < len(symbolgroups), the leading characters will only # be from the first length groups. Try our best to not be predictable # by shuffling and then truncating. random.shuffle(password) password = password[:length] length -= len(password) # then fill with random characters from all symbol groups symbols = ''.join(symbolgroups) password.extend([random.choice(symbols) for _i in range(length)]) # Finally, shuffle to ensure first x characters aren't from a # predictable group random.shuffle(password) return ''.join(password) def list(self, context, object_type=None, metadata_only=False): """Retrieves a list of managed objects that match the criteria. A Forbidden exception is raised if the context is None. If no search criteria is given, all objects are returned. """ if context is None: raise exception.Forbidden() objects = [] for obj_id in self.keys: obj = self.get(context, obj_id, metadata_only=metadata_only) if type(obj) == object_type or object_type is None: objects.append(obj) return objects castellan-3.0.1/castellan/tests/unit/key_manager/test_migration_key_manager.py0000664000175000017500000001166713645116250030036 0ustar zuulzuul00000000000000# Copyright 2017 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the migration key manager. """ import binascii import mock from oslo_config import cfg from castellan.common import exception from castellan.common.objects import symmetric_key as key from castellan import key_manager from castellan.key_manager import not_implemented_key_manager from castellan.tests.unit.key_manager import test_key_manager CONF = cfg.CONF class ConfKeyManager(not_implemented_key_manager.NotImplementedKeyManager): pass class MigrationKeyManagerTestCase(test_key_manager.KeyManagerTestCase): def _create_key_manager(self): self.fixed_key = '1' * 64 try: self.conf.register_opt(cfg.StrOpt('fixed_key'), group='key_manager') except cfg.DuplicateOptError: pass self.conf.set_override('fixed_key', self.fixed_key, group='key_manager') return key_manager.API(self.conf) def setUp(self): super(MigrationKeyManagerTestCase, self).setUp() # Create fake context (actual contents doesn't matter). self.ctxt = mock.Mock() fixed_key_bytes = bytes(binascii.unhexlify(self.fixed_key)) fixed_key_length = len(fixed_key_bytes) * 8 self.fixed_key_secret = key.SymmetricKey('AES', fixed_key_length, fixed_key_bytes) self.fixed_key_id = '00000000-0000-0000-0000-000000000000' self.other_key_id = "d152fa13-2b41-42ca-a934-6c21566c0f40" def test_get_fixed_key(self): self.assertEqual('MigrationKeyManager', type(self.key_mgr).__name__) secret = self.key_mgr.get(self.ctxt, self.fixed_key_id) self.assertEqual(self.fixed_key_secret, secret) def test_get_fixed_key_fail_bad_context(self): self.assertRaises(exception.Forbidden, self.key_mgr.get, context=None, managed_object_id=self.fixed_key_id) def test_delete_fixed_key(self): self.key_mgr.delete(self.ctxt, self.fixed_key_id) # Delete looks like it succeeded, but nothing actually happened. secret = self.key_mgr.get(self.ctxt, self.fixed_key_id) self.assertEqual(self.fixed_key_secret, secret) def test_delete_fixed_key_fail_bad_context(self): self.assertRaises(exception.Forbidden, self.key_mgr.delete, context=None, managed_object_id=self.fixed_key_id) def test_get_other_key(self): # Request to get other_key_id should be passed on to the backend, # who will throw an error because we don't have a valid context. self.assertRaises(exception.KeyManagerError, self.key_mgr.get, context=self.ctxt, managed_object_id=self.other_key_id) def test_delete_other_key(self): # Request to delete other_key_id should be passed on to the backend, # who will throw an error because we don't have a valid context. self.assertRaises(exception.KeyManagerError, self.key_mgr.delete, context=self.ctxt, managed_object_id=self.other_key_id) def test_no_fixed_key(self): conf = self.conf conf.set_override('fixed_key', None, group='key_manager') key_mgr = key_manager.API(conf) self.assertNotEqual('MigrationKeyManager', type(key_mgr).__name__) self.assertRaises(exception.KeyManagerError, key_mgr.get, context=self.ctxt, managed_object_id=self.fixed_key_id) def test_using_conf_key_manager(self): conf = self.conf ckm_backend = 'castellan.tests.unit.key_manager.' \ 'test_migration_key_manager.ConfKeyManager' conf.set_override('backend', ckm_backend, group='key_manager') key_mgr = key_manager.API(conf) self.assertNotEqual('MigrationKeyManager', type(key_mgr).__name__) self.assertRaises(NotImplementedError, key_mgr.get, context=self.ctxt, managed_object_id=self.fixed_key_id) castellan-3.0.1/castellan/tests/unit/key_manager/test_key_manager.py0000664000175000017500000000312013645116250025746 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the key manager. """ from oslo_config import cfg from oslo_config import fixture from castellan import key_manager from castellan.key_manager import barbican_key_manager from castellan.tests import base CONF = cfg.CONF class KeyManagerTestCase(base.TestCase): def _create_key_manager(self): raise NotImplementedError() def setUp(self): super(KeyManagerTestCase, self).setUp() self.conf = self.useFixture(fixture.Config()).conf self.key_mgr = self._create_key_manager() class DefaultKeyManagerImplTestCase(KeyManagerTestCase): def _create_key_manager(self): return key_manager.API(self.conf) def test_default_key_manager(self): self.assertEqual("barbican", self.conf.key_manager.backend) self.assertIsNotNone(self.key_mgr) self.assertIsInstance(self.key_mgr, barbican_key_manager.BarbicanKeyManager) castellan-3.0.1/castellan/tests/unit/key_manager/test_mock_key_manager.py0000664000175000017500000002261513645116250026771 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the mock key manager. """ from cryptography.hazmat import backends from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from oslo_context import context from castellan.common import exception from castellan.common.objects import symmetric_key as sym_key from castellan.tests.unit.key_manager import mock_key_manager as mock_key_mgr from castellan.tests.unit.key_manager import test_key_manager as test_key_mgr def get_cryptography_private_key(private_key): crypto_private_key = serialization.load_der_private_key( bytes(private_key.get_encoded()), password=None, backend=backends.default_backend()) return crypto_private_key def get_cryptography_public_key(public_key): crypto_public_key = serialization.load_der_public_key( bytes(public_key.get_encoded()), backend=backends.default_backend()) return crypto_public_key class MockKeyManagerTestCase(test_key_mgr.KeyManagerTestCase): def _create_key_manager(self): return mock_key_mgr.MockKeyManager() def setUp(self): super(MockKeyManagerTestCase, self).setUp() self.context = context.RequestContext('fake', 'fake') def cleanUp(self): super(MockKeyManagerTestCase, self).cleanUp() self.key_mgr.keys = {} def test_create_key(self): key_id_1 = self.key_mgr.create_key(self.context) key_id_2 = self.key_mgr.create_key(self.context) # ensure that the UUIDs are unique self.assertNotEqual(key_id_1, key_id_2) def test_create_key_with_length(self): for length in [64, 128, 256]: key_id = self.key_mgr.create_key(self.context, length=length) key = self.key_mgr.get(self.context, key_id) self.assertEqual(length / 8, len(key.get_encoded())) self.assertIsNotNone(key.id) def test_create_key_with_name(self): name = 'my key' key_id = self.key_mgr.create_key(self.context, name=name) key = self.key_mgr.get(self.context, key_id) self.assertEqual(name, key.name) self.assertIsNotNone(key.id) def test_create_key_with_algorithm(self): algorithm = 'DES' key_id = self.key_mgr.create_key(self.context, algorithm=algorithm) key = self.key_mgr.get(self.context, key_id) self.assertEqual(algorithm, key.algorithm) self.assertIsNotNone(key.id) def test_create_key_null_context(self): self.assertRaises(exception.Forbidden, self.key_mgr.create_key, None) def test_create_key_pair(self): for length in [2048, 3072, 4096]: name = str(length) + ' key' private_key_uuid, public_key_uuid = self.key_mgr.create_key_pair( self.context, 'RSA', length, name=name) private_key = self.key_mgr.get(self.context, private_key_uuid) self.assertIsNotNone(private_key.id) public_key = self.key_mgr.get(self.context, public_key_uuid) self.assertIsNotNone(public_key.id) crypto_private_key = get_cryptography_private_key(private_key) crypto_public_key = get_cryptography_public_key(public_key) self.assertEqual(name, private_key.name) self.assertEqual(name, public_key.name) self.assertEqual(length, crypto_private_key.key_size) self.assertEqual(length, crypto_public_key.key_size) def test_create_key_pair_encryption(self): private_key_uuid, public_key_uuid = self.key_mgr.create_key_pair( self.context, 'RSA', 2048) private_key = self.key_mgr.get(self.context, private_key_uuid) public_key = self.key_mgr.get(self.context, public_key_uuid) crypto_private_key = get_cryptography_private_key(private_key) crypto_public_key = get_cryptography_public_key(public_key) message = b'secret plaintext' ciphertext = crypto_public_key.encrypt( message, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None)) plaintext = crypto_private_key.decrypt( ciphertext, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None)) self.assertEqual(message, plaintext) def test_create_key_pair_null_context(self): self.assertRaises(exception.Forbidden, self.key_mgr.create_key_pair, None, 'RSA', 2048) def test_create_key_pair_invalid_algorithm(self): self.assertRaises(ValueError, self.key_mgr.create_key_pair, self.context, 'DSA', 2048) def test_create_key_pair_invalid_length(self): self.assertRaises(ValueError, self.key_mgr.create_key_pair, self.context, 'RSA', 10) def test_store_and_get_key(self): secret_key = bytes(b'0' * 64) _key = sym_key.SymmetricKey('AES', 64 * 8, secret_key) key_id = self.key_mgr.store(self.context, _key) actual_key = self.key_mgr.get(self.context, key_id) self.assertEqual(_key, actual_key) self.assertIsNotNone(actual_key.id) def test_store_key_and_get_metadata(self): secret_key = bytes(b'0' * 64) _key = sym_key.SymmetricKey('AES', 64 * 8, secret_key) key_id = self.key_mgr.store(self.context, _key) actual_key = self.key_mgr.get(self.context, key_id, metadata_only=True) self.assertIsNone(actual_key.get_encoded()) self.assertTrue(actual_key.is_metadata_only()) self.assertIsNotNone(actual_key.id) def test_store_key_and_get_metadata_and_get_key(self): secret_key = bytes(b'0' * 64) _key = sym_key.SymmetricKey('AES', 64 * 8, secret_key) key_id = self.key_mgr.store(self.context, _key) actual_key = self.key_mgr.get(self.context, key_id, metadata_only=True) self.assertIsNone(actual_key.get_encoded()) self.assertTrue(actual_key.is_metadata_only()) actual_key = self.key_mgr.get(self.context, key_id, metadata_only=False) self.assertIsNotNone(actual_key.get_encoded()) self.assertFalse(actual_key.is_metadata_only()) self.assertIsNotNone(actual_key.id) def test_store_null_context(self): self.assertRaises(exception.Forbidden, self.key_mgr.store, None, None) def test_get_null_context(self): self.assertRaises(exception.Forbidden, self.key_mgr.get, None, None) def test_get_unknown_key(self): self.assertRaises(KeyError, self.key_mgr.get, self.context, None) def test_delete_key(self): key_id = self.key_mgr.create_key(self.context) self.key_mgr.delete(self.context, key_id) self.assertRaises(KeyError, self.key_mgr.get, self.context, key_id) def test_delete_null_context(self): self.assertRaises(exception.Forbidden, self.key_mgr.delete, None, None) def test_delete_unknown_key(self): self.assertRaises(KeyError, self.key_mgr.delete, self.context, None) def test_list_null_context(self): self.assertRaises(exception.Forbidden, self.key_mgr.list, None) def test_list_keys(self): key1 = sym_key.SymmetricKey('AES', 64 * 8, bytes(b'0' * 64)) self.key_mgr.store(self.context, key1) key2 = sym_key.SymmetricKey('AES', 32 * 8, bytes(b'0' * 32)) self.key_mgr.store(self.context, key2) keys = self.key_mgr.list(self.context) self.assertEqual(2, len(keys)) self.assertTrue(key1 in keys) self.assertTrue(key2 in keys) for key in keys: self.assertIsNotNone(key.id) def test_list_keys_metadata_only(self): key1 = sym_key.SymmetricKey('AES', 64 * 8, bytes(b'0' * 64)) self.key_mgr.store(self.context, key1) key2 = sym_key.SymmetricKey('AES', 32 * 8, bytes(b'0' * 32)) self.key_mgr.store(self.context, key2) keys = self.key_mgr.list(self.context, metadata_only=True) self.assertEqual(2, len(keys)) bit_length_list = [key1.bit_length, key2.bit_length] for key in keys: self.assertTrue(key.is_metadata_only()) self.assertTrue(key.bit_length in bit_length_list) for key in keys: self.assertIsNotNone(key.id) castellan-3.0.1/castellan/tests/unit/key_manager/test_barbican_key_manager.py0000664000175000017500000004656113645116250027607 0ustar zuulzuul00000000000000# Copyright (c) The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for the barbican key manager. """ import calendar from barbicanclient import exceptions as barbican_exceptions import mock from oslo_utils import timeutils from castellan.common import exception from castellan.common.objects import symmetric_key as sym_key from castellan.key_manager import barbican_key_manager from castellan.tests.unit.key_manager import test_key_manager class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase): def _create_key_manager(self): return barbican_key_manager.BarbicanKeyManager(self.conf) def setUp(self): super(BarbicanKeyManagerTestCase, self).setUp() # Create fake auth_token self.ctxt = mock.Mock() self.ctxt.auth_token = "fake_token" # Create mock barbican client self._build_mock_barbican() # Create a key_id, secret_ref, pre_hex, and hex to use self.key_id = "d152fa13-2b41-42ca-a934-6c21566c0f40" self.secret_ref = ("http://host:9311/v1/secrets/" + self.key_id) self.pre_hex = "AIDxQp2++uAbKaTVDMXFYIu8PIugJGqkK0JLqkU0rhY=" self.hex = ("0080f1429dbefae01b29a4d50cc5c5608bbc3c8ba0246aa42b424baa4" "534ae16") self.key_mgr._base_url = "http://host:9311/v1/" self.key_mgr.conf.barbican.number_of_retries = 3 self.key_mgr.conf.barbican.retry_delay = 1 self.addCleanup(self._restore) def _restore(self): try: getattr(self, 'original_key') sym_key.SymmetricKey = self.original_key except AttributeError: return None def _build_mock_barbican(self): self.mock_barbican = mock.MagicMock(name='mock_barbican') # Set commonly used methods self.get = self.mock_barbican.secrets.get self.delete = self.mock_barbican.secrets.delete self.store = self.mock_barbican.secrets.store self.create = self.mock_barbican.secrets.create self.list = self.mock_barbican.secrets.list self.key_mgr._barbican_client = self.mock_barbican self.key_mgr._current_context = self.ctxt def test_base_url_old_version(self): version = "v1" self.key_mgr.conf.barbican.barbican_api_version = version endpoint = "http://localhost:9311" base_url = self.key_mgr._create_base_url(mock.Mock(), mock.Mock(), endpoint) self.assertEqual(endpoint + "/" + version, base_url) def test_base_url_new_version(self): version = "v1" self.key_mgr.conf.barbican.barbican_api_version = version endpoint = "http://localhost/key_manager" base_url = self.key_mgr._create_base_url(mock.Mock(), mock.Mock(), endpoint) self.assertEqual(endpoint + "/" + version, base_url) def test_base_url_service_catalog(self): endpoint_data = mock.Mock() endpoint_data.api_version = 'v321' auth = mock.Mock(spec=['service_catalog']) auth.service_catalog.endpoint_data_for.return_value = endpoint_data endpoint = "http://localhost/key_manager" base_url = self.key_mgr._create_base_url(auth, mock.Mock(), endpoint) self.assertEqual(endpoint + "/" + endpoint_data.api_version, base_url) auth.service_catalog.endpoint_data_for.assert_called_once_with( service_type='key-manager') def test_base_url_raise_exception(self): auth = mock.Mock(spec=['get_discovery']) sess = mock.Mock() discovery = mock.Mock() discovery.raw_version_data = mock.Mock(return_value=[]) auth.get_discovery = mock.Mock(return_value=discovery) endpoint = "http://localhost/key_manager" self.assertRaises(exception.KeyManagerError, self.key_mgr._create_base_url, auth, sess, endpoint) auth.get_discovery.asser_called_once_with(sess, url=endpoint) self.assertEqual(1, discovery.raw_version_data.call_count) def test_base_url_get_discovery(self): version = 'v100500' auth = mock.Mock(spec=['get_discovery']) sess = mock.Mock() discovery = mock.Mock() auth.get_discovery = mock.Mock(return_value=discovery) discovery.raw_version_data = mock.Mock(return_value=[{'id': version}]) endpoint = "http://localhost/key_manager" base_url = self.key_mgr._create_base_url(auth, mock.Mock(), endpoint) self.assertEqual(endpoint + "/" + version, base_url) auth.get_discovery.asser_called_once_with(sess, url=endpoint) self.assertEqual(1, discovery.raw_version_data.call_count) def test_create_key(self): # Create order_ref_url and assign return value order_ref_url = ("http://localhost:9311/v1/orders/" "4fe939b7-72bc-49aa-bd1e-e979589858af") key_order = mock.Mock() self.mock_barbican.orders.create_key.return_value = key_order key_order.submit.return_value = order_ref_url # Create order and assign return value order = mock.Mock() order.secret_ref = self.secret_ref order.status = u'ACTIVE' self.mock_barbican.orders.get.return_value = order # Create the key, get the UUID returned_uuid = self.key_mgr.create_key(self.ctxt, algorithm='AES', length=256) self.mock_barbican.orders.get.assert_called_once_with(order_ref_url) self.assertEqual(self.key_id, returned_uuid) def test_create_key_null_context(self): self.key_mgr._barbican_client = None self.assertRaises(exception.Forbidden, self.key_mgr.create_key, None, 'AES', 256) def test_create_key_with_error(self): key_order = mock.Mock() self.mock_barbican.orders.create_key.return_value = key_order key_order.submit = mock.Mock( side_effect=barbican_exceptions.HTTPClientError('test error')) self.assertRaises(exception.KeyManagerError, self.key_mgr.create_key, self.ctxt, 'AES', 256) def test_create_key_pair(self): # Create order_ref_url and assign return value order_ref_url = ("http://localhost:9311/v1/orders/" "f45bf211-a917-4ead-9aec-1c91e52609df") asym_order = mock.Mock() self.mock_barbican.orders.create_asymmetric.return_value = asym_order asym_order.submit.return_value = order_ref_url # Create order and assign return value order = mock.Mock() container_id = "16caa8f4-dd34-4fb3-bf67-6c20533a30e4" container_ref = ("http://localhost:9311/v1/containers/" + container_id) order.container_ref = container_ref order.status = u'ACTIVE' self.mock_barbican.orders.get.return_value = order # Create container and assign return value container = mock.Mock() public_key_id = "43ed09c3-e551-4c24-b612-e619abe9b534" pub_key_ref = ("http://localhost:9311/v1/secrets/" + public_key_id) private_key_id = "32a0bc60-4e10-4269-9f17-f49767e99586" priv_key_ref = ("http://localhost:9311/v1/secrets/" + private_key_id) container.secret_refs = {'public_key': pub_key_ref, 'private_key': priv_key_ref} self.mock_barbican.containers.get.return_value = container # Create the keys, get the UUIDs returned_private_uuid, returned_public_uuid = ( self.key_mgr.create_key_pair(self.ctxt, algorithm='RSA', length=2048)) self.mock_barbican.orders.get.assert_called_once_with(order_ref_url) self.mock_barbican.containers.get.assert_called_once_with( container_ref) self.mock_barbican.orders.get.assert_called_once_with(order_ref_url) self.assertEqual(private_key_id, returned_private_uuid) self.assertEqual(public_key_id, returned_public_uuid) def test_create_key_pair_null_context(self): self.key_mgr._barbican_client = None self.assertRaises(exception.Forbidden, self.key_mgr.create_key_pair, None, 'RSA', 2048) def test_create_key_pair_with_error(self): asym_order = mock.Mock() self.mock_barbican.orders.create_asymmetric.return_value = asym_order asym_order.submit = mock.Mock( side_effect=barbican_exceptions.HTTPClientError('test error')) self.assertRaises(exception.KeyManagerError, self.key_mgr.create_key_pair, self.ctxt, 'RSA', 2048) def test_delete_null_context(self): self.key_mgr._barbican_client = None self.assertRaises(exception.Forbidden, self.key_mgr.delete, None, self.key_id) def test_delete_key(self): self.key_mgr.delete(self.ctxt, self.key_id) self.delete.assert_called_once_with(self.secret_ref) def test_delete_unknown_key(self): self.assertRaises(exception.KeyManagerError, self.key_mgr.delete, self.ctxt, None) def test_delete_with_error(self): self.mock_barbican.secrets.delete = mock.Mock( side_effect=barbican_exceptions.HTTPClientError('test error')) self.assertRaises(exception.KeyManagerError, self.key_mgr.delete, self.ctxt, self.key_id) def test_get_key(self): original_secret_metadata = mock.Mock() original_secret_metadata.algorithm = mock.sentinel.alg original_secret_metadata.bit_length = mock.sentinel.bit original_secret_metadata.secret_type = 'symmetric' key_id = "43ed09c3-e551-4c24-b612-e619abe9b534" key_ref = ("http://localhost:9311/v1/secrets/" + key_id) original_secret_metadata.secret_ref = key_ref created = timeutils.parse_isotime('2015-10-20 18:51:17+00:00') original_secret_metadata.created = created created_formatted = timeutils.parse_isotime(str(created)) created_posix = calendar.timegm(created_formatted.timetuple()) key_name = 'my key' original_secret_metadata.name = key_name original_secret_data = b'test key' original_secret_metadata.payload = original_secret_data self.mock_barbican.secrets.get.return_value = original_secret_metadata key = self.key_mgr.get(self.ctxt, self.key_id) self.get.assert_called_once_with(self.secret_ref) self.assertEqual(key_id, key.id) self.assertEqual(key_name, key.name) self.assertEqual(original_secret_data, key.get_encoded()) self.assertEqual(created_posix, key.created) def test_get_null_context(self): self.key_mgr._barbican_client = None self.assertRaises(exception.Forbidden, self.key_mgr.get, None, self.key_id) def test_get_unknown_key(self): self.assertRaises(exception.KeyManagerError, self.key_mgr.get, self.ctxt, None) def test_get_with_error(self): self.mock_barbican.secrets.get = mock.Mock( side_effect=barbican_exceptions.HTTPClientError('test error')) self.assertRaises(exception.KeyManagerError, self.key_mgr.get, self.ctxt, self.key_id) def test_store_key(self): # Create Key to store secret_key = bytes(b'\x01\x02\xA0\xB3') key_length = len(secret_key) * 8 _key = sym_key.SymmetricKey('AES', key_length, secret_key) # Define the return values secret = mock.Mock() self.create.return_value = secret secret.store.return_value = self.secret_ref # Store the Key returned_uuid = self.key_mgr.store(self.ctxt, _key) self.create.assert_called_once_with(algorithm='AES', bit_length=key_length, name=None, payload=secret_key, secret_type='symmetric') self.assertEqual(self.key_id, returned_uuid) def test_store_key_with_name(self): # Create Key to store secret_key = bytes(b'\x01\x02\xA0\xB3') key_length = len(secret_key) * 8 secret_name = 'My Secret' _key = sym_key.SymmetricKey('AES', key_length, secret_key, secret_name) # Define the return values secret = mock.Mock() self.create.return_value = secret secret.store.return_value = self.secret_ref # Store the Key returned_uuid = self.key_mgr.store(self.ctxt, _key) self.create.assert_called_once_with(algorithm='AES', bit_length=key_length, payload=secret_key, name=secret_name, secret_type='symmetric') self.assertEqual(self.key_id, returned_uuid) def test_store_null_context(self): self.key_mgr._barbican_client = None self.assertRaises(exception.Forbidden, self.key_mgr.store, None, None) def test_store_with_error(self): self.mock_barbican.secrets.create = mock.Mock( side_effect=barbican_exceptions.HTTPClientError('test error')) secret_key = bytes(b'\x01\x02\xA0\xB3') key_length = len(secret_key) * 8 _key = sym_key.SymmetricKey('AES', key_length, secret_key) self.assertRaises(exception.KeyManagerError, self.key_mgr.store, self.ctxt, _key) def test_get_active_order(self): order_ref_url = ("http://localhost:9311/v1/orders/" "4fe939b7-72bc-49aa-bd1e-e979589858af") pending_order = mock.Mock() pending_order.status = u'PENDING' pending_order.order_ref = order_ref_url active_order = mock.Mock() active_order.secret_ref = self.secret_ref active_order.status = u'ACTIVE' active_order.order_ref = order_ref_url self.mock_barbican.orders.get.side_effect = [pending_order, active_order] self.key_mgr._get_active_order(self.mock_barbican, order_ref_url) self.assertEqual(2, self.mock_barbican.orders.get.call_count) calls = [mock.call(order_ref_url), mock.call(order_ref_url)] self.mock_barbican.orders.get.assert_has_calls(calls) def test_get_active_order_timeout(self): order_ref_url = ("http://localhost:9311/v1/orders/" "4fe939b7-72bc-49aa-bd1e-e979589858af") number_of_retries = self.key_mgr.conf.barbican.number_of_retries pending_order = mock.Mock() pending_order.status = u'PENDING' pending_order.order_ref = order_ref_url self.mock_barbican.orders.get.return_value = pending_order self.assertRaises(exception.KeyManagerError, self.key_mgr._get_active_order, self.mock_barbican, order_ref_url) self.assertEqual(number_of_retries + 1, self.mock_barbican.orders.get.call_count) def test_get_active_order_error(self): order_ref_url = ("http://localhost:9311/v1/orders/" "4fe939b7-72bc-49aa-bd1e-e979589858af") error_order = mock.Mock() error_order.status = u'ERROR' error_order.order_ref = order_ref_url error_order.error_status_code = u"500" error_order.error_reason = u"Test Error" self.mock_barbican.orders.get.return_value = error_order self.assertRaises(exception.KeyManagerError, self.key_mgr._get_active_order, self.mock_barbican, order_ref_url) self.assertEqual(1, self.mock_barbican.orders.get.call_count) def test_list_null_context(self): self.key_mgr._barbican_client = None self.assertRaises(exception.Forbidden, self.key_mgr.list, None) def test_list(self): original_secret_metadata = mock.Mock() original_secret_metadata.algorithm = mock.sentinel.alg original_secret_metadata.bit_length = mock.sentinel.bit original_secret_metadata.secret_type = 'symmetric' key_id = "43ed09c3-e551-4c24-b612-e619abe9b534" key_ref = ("http://localhost:9311/v1/secrets/" + key_id) original_secret_metadata.secret_ref = key_ref created = timeutils.parse_isotime('2015-10-20 18:51:17+00:00') original_secret_metadata.created = created created_formatted = timeutils.parse_isotime(str(created)) created_posix = calendar.timegm(created_formatted.timetuple()) key_name = 'my key' original_secret_metadata.name = key_name original_secret_data = b'test key' original_secret_metadata.payload = original_secret_data self.mock_barbican.secrets.list.return_value = ( [original_secret_metadata]) # check metadata_only = False key_list = self.key_mgr.list(self.ctxt) self.assertEqual(1, len(key_list)) key = key_list[0] self.list.assert_called_once() self.assertEqual(key_id, key.id) self.assertEqual(key_name, key.name) self.assertEqual(original_secret_data, key.get_encoded()) self.assertEqual(created_posix, key.created) self.list.reset_mock() # check metadata_only = True key_list = self.key_mgr.list(self.ctxt, metadata_only=True) self.assertEqual(1, len(key_list)) key = key_list[0] self.list.assert_called_once() self.assertEqual(key_name, key.name) self.assertIsNone(key.get_encoded()) self.assertEqual(created_posix, key.created) def test_list_with_error(self): self.mock_barbican.secrets.list = mock.Mock( side_effect=barbican_exceptions.HTTPClientError('test error')) self.assertRaises(exception.KeyManagerError, self.key_mgr.list, self.ctxt) def test_list_with_invalid_object_type(self): self.assertRaises(exception.KeyManagerError, self.key_mgr.list, self.ctxt, "invalid_type") castellan-3.0.1/castellan/tests/unit/key_manager/__init__.py0000664000175000017500000000000013645116250024156 0ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/unit/__init__.py0000664000175000017500000000000013645116250021674 0ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/__init__.py0000664000175000017500000000000013645116250020715 0ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/contrib/0000775000175000017500000000000013645116331020256 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/contrib/gate_hook.sh0000775000175000017500000000010213645116250022546 0ustar zuulzuul00000000000000#!/bin/bash set -ex $BASE/new/devstack-gate/devstack-vm-gate.sh castellan-3.0.1/castellan/tests/contrib/post_test_hook.sh0000775000175000017500000000207113645116250023661 0ustar zuulzuul00000000000000#!/bin/bash set -xe CASTELLAN_DIR="$BASE/new/castellan" function generate_testr_results { if [ -f .testrepository/0 ]; then sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit sudo /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html sudo gzip -9 $BASE/logs/testrepository.subunit sudo gzip -9 $BASE/logs/testr_results.html sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz fi } owner=tempest # Set owner permissions according to job's requirements. cd $CASTELLAN_DIR sudo chown -R $owner:stack $CASTELLAN_DIR testenv=functional # Run tests echo "Running Castellan $testenv test suite" set +e sudo -H -u $owner tox -e $testenv testr_exit_code=$? set -e # Collect and parse results generate_testr_results exit $testr_exit_code castellan-3.0.1/castellan/tests/functional/0000775000175000017500000000000013645116331020760 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/functional/config.py0000664000175000017500000000457213645116250022607 0ustar zuulzuul00000000000000# Copyright (c) The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from oslo_config import cfg TEST_CONF = None identity_group = cfg.OptGroup(name='identity') identity_options = [ cfg.StrOpt('auth_url', default='http://localhost/identity/v3', help='Keystone endpoint'), cfg.StrOpt('username', default='admin', help='Keystone username'), cfg.StrOpt('password', default='secretadmin', help='Password used with Keystone username'), cfg.StrOpt('project_name', default='admin', help='Name of project, used by the given username'), cfg.StrOpt('user_domain_name', default='Default', help='Name of domain, used by the given username'), cfg.StrOpt('project_domain_name', default='Default', help='Name of domain, used by the given project')] def setup_config(config_file=''): global TEST_CONF TEST_CONF = cfg.ConfigOpts() TEST_CONF.register_group(identity_group) TEST_CONF.register_opts(identity_options, group=identity_group) config_to_load = [] local_config = './etc/castellan/castellan-functional.conf' main_config = '/etc/castellan/castellan-functional.conf' if os.path.isfile(config_file): config_to_load.append(config_file) elif os.path.isfile(local_config): config_to_load.append(local_config) elif os.path.isfile(main_config): config_to_load.append(main_config) TEST_CONF( (), # Required to load an anonymous configuration default_config_files=config_to_load ) def get_config(): if not TEST_CONF: setup_config() return TEST_CONF def list_opts(): yield identity_group.name, identity_options castellan-3.0.1/castellan/tests/functional/key_manager/0000775000175000017500000000000013645116331023242 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/functional/key_manager/test_key_manager.py0000664000175000017500000002273713645116250027150 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test cases for a key manager. These test cases should pass against any key manager. """ from castellan.common import exception from castellan.common.objects import opaque_data from castellan.common.objects import passphrase from castellan.common.objects import private_key from castellan.common.objects import public_key from castellan.common.objects import symmetric_key from castellan.common.objects import x_509 from castellan.tests import utils def _get_test_symmetric_key(): key_bytes = bytes(utils.get_symmetric_key()) bit_length = 128 key = symmetric_key.SymmetricKey('AES', bit_length, key_bytes) return key def _get_test_public_key(): key_bytes = bytes(utils.get_public_key_der()) bit_length = 2048 key = public_key.PublicKey('RSA', bit_length, key_bytes) return key def _get_test_private_key(): key_bytes = bytes(utils.get_private_key_der()) bit_length = 2048 key = private_key.PrivateKey('RSA', bit_length, key_bytes) return key def _get_test_certificate(): data = bytes(utils.get_certificate_der()) cert = x_509.X509(data) return cert def _get_test_opaque_data(): data = bytes(b'opaque data') opaque_object = opaque_data.OpaqueData(data) return opaque_object def _get_test_passphrase(): data = bytes(b'passphrase') passphrase_object = passphrase.Passphrase(data) return passphrase_object @utils.parameterized_test_case class KeyManagerTestCase(object): def _create_key_manager(self): raise NotImplementedError() def setUp(self): super(KeyManagerTestCase, self).setUp() self.key_mgr = self._create_key_manager() self.ctxt = None def _get_valid_object_uuid(self, managed_object): object_uuid = self.key_mgr.store(self.ctxt, managed_object) self.assertIsNotNone(object_uuid) return object_uuid def test_create_key(self): key_uuid = self.key_mgr.create_key(self.ctxt, algorithm='AES', length=256) self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) self.assertIsNotNone(key_uuid) def test_create_key_pair(self): private_key_uuid, public_key_uuid = self.key_mgr.create_key_pair( self.ctxt, algorithm='RSA', length=2048) self.addCleanup(self.key_mgr.delete, self.ctxt, private_key_uuid) self.addCleanup(self.key_mgr.delete, self.ctxt, public_key_uuid) self.assertIsNotNone(private_key_uuid) self.assertIsNotNone(public_key_uuid) self.assertNotEqual(private_key_uuid, public_key_uuid) @utils.parameterized_dataset({ 'symmetric_key': [_get_test_symmetric_key()], 'public_key': [_get_test_public_key()], 'private_key': [_get_test_private_key()], 'certificate': [_get_test_certificate()], 'passphrase': [_get_test_passphrase()], 'opaque_data': [_get_test_opaque_data()], }) def test_delete(self, managed_object): object_uuid = self._get_valid_object_uuid(managed_object) self.key_mgr.delete(self.ctxt, object_uuid) try: self.key_mgr.get(self.ctxt, object_uuid) except exception.ManagedObjectNotFoundError: pass else: self.fail('No exception when deleting non-existent key') @utils.parameterized_dataset({ 'symmetric_key': [_get_test_symmetric_key()], 'public_key': [_get_test_public_key()], 'private_key': [_get_test_private_key()], 'certificate': [_get_test_certificate()], 'passphrase': [_get_test_passphrase()], 'opaque_data': [_get_test_opaque_data()], }) def test_get(self, managed_object): uuid = self._get_valid_object_uuid(managed_object) self.addCleanup(self.key_mgr.delete, self.ctxt, uuid) retrieved_object = self.key_mgr.get(self.ctxt, uuid) self.assertEqual(managed_object.get_encoded(), retrieved_object.get_encoded()) self.assertFalse(managed_object.is_metadata_only()) self.assertFalse(retrieved_object.is_metadata_only()) self.assertIsNotNone(retrieved_object.id) @utils.parameterized_dataset({ 'symmetric_key': [_get_test_symmetric_key()], 'public_key': [_get_test_public_key()], 'private_key': [_get_test_private_key()], 'certificate': [_get_test_certificate()], 'passphrase': [_get_test_passphrase()], 'opaque_data': [_get_test_opaque_data()], }) def test_get_metadata(self, managed_object): uuid = self._get_valid_object_uuid(managed_object) self.addCleanup(self.key_mgr.delete, self.ctxt, uuid) retrieved_object = self.key_mgr.get(self.ctxt, uuid, metadata_only=True) self.assertFalse(managed_object.is_metadata_only()) self.assertTrue(retrieved_object.is_metadata_only()) self.assertIsNotNone(retrieved_object.id) @utils.parameterized_dataset({ 'symmetric_key': [_get_test_symmetric_key()], 'public_key': [_get_test_public_key()], 'private_key': [_get_test_private_key()], 'certificate': [_get_test_certificate()], 'passphrase': [_get_test_passphrase()], 'opaque_data': [_get_test_opaque_data()], }) def test_store(self, managed_object): uuid = self.key_mgr.store(self.ctxt, managed_object) self.addCleanup(self.key_mgr.delete, self.ctxt, uuid) retrieved_object = self.key_mgr.get(self.ctxt, uuid) self.assertEqual(managed_object.get_encoded(), retrieved_object.get_encoded()) self.assertIsNotNone(retrieved_object.id) @utils.parameterized_dataset({ 'symmetric_key': [_get_test_symmetric_key()], 'public_key': [_get_test_public_key()], 'private_key': [_get_test_private_key()], 'certificate': [_get_test_certificate()], 'passphrase': [_get_test_passphrase()], 'opaque_data': [_get_test_opaque_data()], }) def test_list(self, managed_object): uuid = self.key_mgr.store(self.ctxt, managed_object) self.addCleanup(self.key_mgr.delete, self.ctxt, uuid) # the list command may return more objects than the one we just # created if older objects were not cleaned up, so we will simply # check if the object we created is in the list retrieved_objects = self.key_mgr.list(self.ctxt) self.assertTrue(managed_object in retrieved_objects) for retrieved_object in retrieved_objects: self.assertFalse(retrieved_object.is_metadata_only()) self.assertIsNotNone(retrieved_object.id) @utils.parameterized_dataset({ 'symmetric_key': [_get_test_symmetric_key()], 'public_key': [_get_test_public_key()], 'private_key': [_get_test_private_key()], 'certificate': [_get_test_certificate()], 'passphrase': [_get_test_passphrase()], 'opaque_data': [_get_test_opaque_data()], }) def test_list_metadata_only(self, managed_object): uuid = self.key_mgr.store(self.ctxt, managed_object) self.addCleanup(self.key_mgr.delete, self.ctxt, uuid) expected_obj = self.key_mgr.get(self.ctxt, uuid, metadata_only=True) # the list command may return more objects than the one we just # created if older objects were not cleaned up, so we will simply # check if the object we created is in the list retrieved_objects = self.key_mgr.list(self.ctxt, metadata_only=True) self.assertTrue(expected_obj in retrieved_objects) for retrieved_object in retrieved_objects: self.assertTrue(retrieved_object.is_metadata_only()) self.assertIsNotNone(retrieved_object.id) @utils.parameterized_dataset({ 'query_by_object_type': { 'object_1': _get_test_symmetric_key(), 'object_2': _get_test_public_key(), 'query_dict': dict(object_type=symmetric_key.SymmetricKey) }, }) def test_list_with_filter(self, object_1, object_2, query_dict): uuid1 = self.key_mgr.store(self.ctxt, object_1) uuid2 = self.key_mgr.store(self.ctxt, object_2) self.addCleanup(self.key_mgr.delete, self.ctxt, uuid1) self.addCleanup(self.key_mgr.delete, self.ctxt, uuid2) # the list command may return more objects than the one we just # created if older objects were not cleaned up, so we will simply # check that the returned objects have the expected type retrieved_objects = self.key_mgr.list(self.ctxt, **query_dict) for retrieved_object in retrieved_objects: self.assertEqual(type(object_1), type(retrieved_object)) self.assertIsNotNone(retrieved_object.id) self.assertTrue(object_1 in retrieved_objects) castellan-3.0.1/castellan/tests/functional/key_manager/test_barbican_key_manager.py0000664000175000017500000001533513645116250030765 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Functional test cases for the Barbican key manager. Note: This requires local running instances of Barbican and Keystone. """ import abc from keystoneauth1 import identity from keystoneauth1 import session from oslo_config import cfg from oslo_context import context from oslo_utils import uuidutils from oslotest import base from testtools import testcase from castellan.common.credentials import keystone_password from castellan.common.credentials import keystone_token from castellan.common import exception from castellan.key_manager import barbican_key_manager from castellan.tests.functional import config from castellan.tests.functional.key_manager import test_key_manager CONF = config.get_config() class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase): def _create_key_manager(self): return barbican_key_manager.BarbicanKeyManager(cfg.CONF) @abc.abstractmethod def get_context(self): """Retrieves Context for Authentication""" return def setUp(self): super(BarbicanKeyManagerTestCase, self).setUp() try: self.ctxt = self.get_context() self.key_mgr._get_barbican_client(self.ctxt) except Exception as e: # When we run functional-vault target, This test class needs # to be skipped as barbican is not running raise testcase.TestSkipped(str(e)) def tearDown(self): super(BarbicanKeyManagerTestCase, self).tearDown() def test_create_null_context(self): self.assertRaises(exception.Forbidden, self.key_mgr.create_key, None, 'AES', 256) def test_create_key_pair_null_context(self): self.assertRaises(exception.Forbidden, self.key_mgr.create_key_pair, None, 'RSA', 2048) def test_delete_null_context(self): key_uuid = self._get_valid_object_uuid( test_key_manager._get_test_symmetric_key()) self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) self.assertRaises(exception.Forbidden, self.key_mgr.delete, None, key_uuid) def test_delete_null_object(self): self.assertRaises(exception.KeyManagerError, self.key_mgr.delete, self.ctxt, None) def test_delete_unknown_object(self): unknown_uuid = uuidutils.generate_uuid() self.assertRaises(exception.ManagedObjectNotFoundError, self.key_mgr.delete, self.ctxt, unknown_uuid) def test_get_null_context(self): key_uuid = self._get_valid_object_uuid( test_key_manager._get_test_symmetric_key()) self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) self.assertRaises(exception.Forbidden, self.key_mgr.get, None, key_uuid) def test_get_null_object(self): self.assertRaises(exception.KeyManagerError, self.key_mgr.get, self.ctxt, None) def test_get_unknown_key(self): bad_key_uuid = uuidutils.generate_uuid() self.assertRaises(exception.ManagedObjectNotFoundError, self.key_mgr.get, self.ctxt, bad_key_uuid) def test_store_null_context(self): key = test_key_manager._get_test_symmetric_key() self.assertRaises(exception.Forbidden, self.key_mgr.store, None, key) class BarbicanKeyManagerOSLOContextTestCase(BarbicanKeyManagerTestCase, base.BaseTestCase): def get_context(self): username = CONF.identity.username password = CONF.identity.password project_name = CONF.identity.project_name auth_url = CONF.identity.auth_url user_domain_name = CONF.identity.user_domain_name project_domain_name = CONF.identity.project_domain_name auth = identity.V3Password(auth_url=auth_url, username=username, password=password, project_name=project_name, user_domain_name=user_domain_name, project_domain_name=project_domain_name) sess = session.Session(auth=auth) return context.RequestContext(auth_token=auth.get_token(sess), tenant=auth.get_project_id(sess)) class BarbicanKeyManagerKSPasswordTestCase(BarbicanKeyManagerTestCase, base.BaseTestCase): def get_context(self): auth_url = CONF.identity.auth_url username = CONF.identity.username password = CONF.identity.password project_name = CONF.identity.project_name user_domain_name = CONF.identity.user_domain_name project_domain_name = CONF.identity.project_domain_name ctxt = keystone_password.KeystonePassword( auth_url=auth_url, username=username, password=password, project_name=project_name, user_domain_name=user_domain_name, project_domain_name=project_domain_name) return ctxt class BarbicanKeyManagerKSTokenTestCase(BarbicanKeyManagerTestCase, base.BaseTestCase): def get_context(self): username = CONF.identity.username password = CONF.identity.password project_name = CONF.identity.project_name auth_url = CONF.identity.auth_url user_domain_name = CONF.identity.user_domain_name project_domain_name = CONF.identity.project_domain_name auth = identity.V3Password(auth_url=auth_url, username=username, password=password, project_name=project_name, user_domain_name=user_domain_name, project_domain_name=project_domain_name) sess = session.Session() return keystone_token.KeystoneToken( token=auth.get_token(sess), auth_url=auth_url, project_id=auth.get_project_id(sess)) castellan-3.0.1/castellan/tests/functional/key_manager/test_vault_key_manager.py0000664000175000017500000001407113645116250030353 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Functional test cases for the Vault key manager. Note: This requires local running instance of Vault. """ import os import uuid from oslo_config import cfg from oslo_utils import uuidutils from oslotest import base import requests from testtools import testcase from castellan.common import exception from castellan.key_manager import vault_key_manager from castellan.tests.functional import config from castellan.tests.functional.key_manager import test_key_manager CONF = config.get_config() class VaultKeyManagerTestCase(test_key_manager.KeyManagerTestCase, base.BaseTestCase): def _create_key_manager(self): key_mgr = vault_key_manager.VaultKeyManager(cfg.CONF) if ('VAULT_TEST_URL' not in os.environ or 'VAULT_TEST_ROOT_TOKEN' not in os.environ): raise testcase.TestSkipped('Missing Vault setup information') key_mgr._root_token_id = os.environ['VAULT_TEST_ROOT_TOKEN'] key_mgr._vault_url = os.environ['VAULT_TEST_URL'] return key_mgr def test_create_key_pair_bad_algorithm(self): self.assertRaises( NotImplementedError, self.key_mgr.create_key_pair, self.ctxt, 'DSA', 2048 ) def test_delete_null_object(self): self.assertRaises(exception.KeyManagerError, self.key_mgr.delete, self.ctxt, None) def test_get_null_object(self): self.assertRaises(exception.KeyManagerError, self.key_mgr.get, self.ctxt, None) def test_get_unknown_key(self): bad_key_uuid = uuidutils.generate_uuid() self.assertRaises(exception.ManagedObjectNotFoundError, self.key_mgr.get, self.ctxt, bad_key_uuid) TEST_POLICY = ''' path "{backend}/*" {{ capabilities = ["create", "read", "update", "delete", "list"] }} path "sys/internal/ui/mounts/{backend}" {{ capabilities = ["read"] }} ''' AUTH_ENDPOINT = 'v1/sys/auth/{auth_type}' POLICY_ENDPOINT = 'v1/sys/policy/{policy_name}' APPROLE_ENDPOINT = 'v1/auth/approle/role/{role_name}' class VaultKeyManagerAppRoleTestCase(VaultKeyManagerTestCase): mountpoint = 'secret' def _create_key_manager(self): key_mgr = vault_key_manager.VaultKeyManager(cfg.CONF) if ('VAULT_TEST_URL' not in os.environ or 'VAULT_TEST_ROOT_TOKEN' not in os.environ): raise testcase.TestSkipped('Missing Vault setup information') self.root_token_id = os.environ['VAULT_TEST_ROOT_TOKEN'] self.vault_url = os.environ['VAULT_TEST_URL'] test_uuid = str(uuid.uuid4()) vault_policy = 'policy-{}'.format(test_uuid) vault_approle = 'approle-{}'.format(test_uuid) self.session = requests.Session() self.session.headers.update({'X-Vault-Token': self.root_token_id}) self._mount_kv(self.mountpoint) self._enable_approle() self._create_policy(vault_policy) self._create_approle(vault_approle, vault_policy) key_mgr._approle_role_id, key_mgr._approle_secret_id = ( self._retrieve_approle(vault_approle) ) key_mgr._kv_mountpoint = self.mountpoint key_mgr._vault_url = self.vault_url return key_mgr def _mount_kv(self, vault_mountpoint): backends = self.session.get( '{}/v1/sys/mounts'.format(self.vault_url)).json() if vault_mountpoint not in backends: params = { 'type': 'kv', 'options': { 'version': 2, } } self.session.post( '{}/v1/sys/mounts/{}'.format(self.vault_url, vault_mountpoint), json=params) def _enable_approle(self): params = { 'type': 'approle' } self.session.post( '{}/{}'.format( self.vault_url, AUTH_ENDPOINT.format(auth_type='approle') ), json=params, ) def _create_policy(self, vault_policy): params = { 'rules': TEST_POLICY.format(backend=self.mountpoint), } self.session.put( '{}/{}'.format( self.vault_url, POLICY_ENDPOINT.format(policy_name=vault_policy) ), json=params, ) def _create_approle(self, vault_approle, vault_policy): params = { 'token_ttl': '60s', 'token_max_ttl': '60s', 'policies': [vault_policy], 'bind_secret_id': 'true', 'bound_cidr_list': '127.0.0.1/32' } self.session.post( '{}/{}'.format( self.vault_url, APPROLE_ENDPOINT.format(role_name=vault_approle) ), json=params, ) def _retrieve_approle(self, vault_approle): approle_role_id = ( self.session.get( '{}/v1/auth/approle/role/{}/role-id'.format( self.vault_url, vault_approle )).json()['data']['role_id'] ) approle_secret_id = ( self.session.post( '{}/v1/auth/approle/role/{}/secret-id'.format( self.vault_url, vault_approle )).json()['data']['secret_id'] ) return (approle_role_id, approle_secret_id) class VaultKeyManagerAltMountpointTestCase(VaultKeyManagerAppRoleTestCase): mountpoint = 'different-secrets' castellan-3.0.1/castellan/tests/functional/key_manager/__init__.py0000664000175000017500000000000013645116250025341 0ustar zuulzuul00000000000000castellan-3.0.1/castellan/tests/functional/__init__.py0000664000175000017500000000000013645116250023057 0ustar zuulzuul00000000000000castellan-3.0.1/castellan/common/0000775000175000017500000000000013645116331016744 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/common/credentials/0000775000175000017500000000000013645116331021241 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/common/credentials/password.py0000664000175000017500000000323013645116250023453 0ustar zuulzuul00000000000000# Copyright (c) 2015 IBM # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base Password Credential This module defines the Password credential. """ from castellan.common.credentials import credential class Password(credential.Credential): """This class represents a password credential.""" def __init__(self, username, password): """Create a new Password credential. :param string password: Password for authentication. :param string username: Username for authentication. """ self._username = username self._password = password @property def username(self): """This method returns a username.""" return self._username @property def password(self): """This method returns a password.""" return self._password def __eq__(self, other): if isinstance(other, Password): return (self._username == other._username and self._password == other._password) else: return False def __ne__(self, other): result = self.__eq__(other) return not result castellan-3.0.1/castellan/common/credentials/credential.py0000664000175000017500000000170413645116250023727 0ustar zuulzuul00000000000000# Copyright (c) IBM # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base Credential Object Class This module defines the Credential class. The Credential class is the base class to represent all credentials which a KeyMaster can use for authenticating. """ import abc class Credential(object, metaclass=abc.ABCMeta): """Base class to represent all credentials.""" def __init__(self): pass castellan-3.0.1/castellan/common/credentials/keystone_password.py0000664000175000017500000001255313645116250025404 0ustar zuulzuul00000000000000# Copyright (c) 2015 IBM # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Keystone Password Credential This module defines the Keystone Password credential. """ from castellan.common.credentials import password class KeystonePassword(password.Password): """This class represents a keystone password credential.""" def __init__(self, password, auth_url=None, username=None, user_id=None, user_domain_id=None, user_domain_name=None, trust_id=None, domain_id=None, domain_name=None, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, reauthenticate=True): """Create a new Keystone Password Credential. :param string auth_url: Use this endpoint to connect to Keystone. :param string password: Password for authentication. :param string username: Username for authentication. :param string user_id: User ID for authentication. :param string user_domain_id: User's domain ID for authentication. :param string user_domain_name: User's domain name for authentication. :param string trust_id: Trust ID for trust scoping. :param string domain_id: Domain ID for domain scoping. :param string domain_name: Domain name for domain scoping. :param string project_id: Project ID for project scoping. :param string project_name: Project name for project scoping. :param string project_domain_id: Project's domain ID for project. :param string project_domain_name: Project's domain name for project. :param bool reauthenticate: Allow fetching a new token if the current one is going to expire. (optional) default True """ self._auth_url = auth_url self._user_id = user_id self._user_domain_id = user_domain_id self._user_domain_name = user_domain_name self._trust_id = trust_id self._domain_id = domain_id self._domain_name = domain_name self._project_id = project_id self._project_name = project_name self._project_domain_id = project_domain_id self._project_domain_name = project_domain_name self._reauthenticate = reauthenticate super(KeystonePassword, self).__init__(username, password) @property def auth_url(self): """This method returns an auth_url.""" return self._auth_url @property def user_id(self): """This method returns a user_id.""" return self._user_id @property def user_domain_id(self): """This method returns a user_domain_id.""" return self._user_domain_id @property def user_domain_name(self): """This method returns a user_domain_name.""" return self._user_domain_name @property def trust_id(self): """This method returns a trust_id.""" return self._trust_id @property def domain_id(self): """This method returns a domain_id.""" return self._domain_id @property def domain_name(self): """This method returns a domain_name.""" return self._domain_name @property def project_id(self): """This method returns a project_id.""" return self._project_id @property def project_name(self): """This method returns a project_name.""" return self._project_name @property def project_domain_id(self): """This method returns a project_domain_id.""" return self._project_domain_id @property def project_domain_name(self): """This method returns a project_domain_name.""" return self._project_domain_name @property def reauthenticate(self): """This method returns reauthenticate.""" return self._reauthenticate def __eq__(self, other): if isinstance(other, KeystonePassword): return ( self._password == other._password and self._username == other._username and self._user_id == other._user_id and self._user_domain_id == other._user_domain_id and self._user_domain_name == other._user_domain_name and self._trust_id == other._trust_id and self._domain_id == other._domain_id and self._domain_name == other._domain_name and self._project_id == other._project_id and self._project_name == other._project_name and self._project_domain_id == other._project_domain_id and self._project_domain_name == other._project_domain_name and self._reauthenticate == other._reauthenticate) else: return False def __ne__(self, other): result = self.__eq__(other) return not result castellan-3.0.1/castellan/common/credentials/token.py0000664000175000017500000000252213645116250022734 0ustar zuulzuul00000000000000# Copyright (c) 2015 IBM # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base Token Credential This module defines the Token credential. """ from castellan.common.credentials import credential class Token(credential.Credential): """This class represents a token credential.""" def __init__(self, token): """Create a new Token credential. :param string token: Token for authentication. """ self._token = token @property def token(self): """This method returns a token.""" return self._token def __eq__(self, other): if isinstance(other, Token): return (self._token == other._token) else: return False def __ne__(self, other): result = self.__eq__(other) return not result castellan-3.0.1/castellan/common/credentials/__init__.py0000664000175000017500000000000013645116250023340 0ustar zuulzuul00000000000000castellan-3.0.1/castellan/common/credentials/keystone_token.py0000664000175000017500000001035213645116250024655 0ustar zuulzuul00000000000000# Copyright (c) 2015 IBM # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Keystone Token Credential This module defines the Keystone Token credential. """ from castellan.common.credentials import token class KeystoneToken(token.Token): """This class represents a keystone token credential.""" def __init__(self, token, auth_url=None, trust_id=None, domain_id=None, domain_name=None, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, reauthenticate=True): """Create a new Keystone Token Credential. :param string token: Token for authentication. The type of token formats accepted are UUID, PKI, and Fernet. :param string auth_url: Use this endpoint to connect to Keystone. :param string trust_id: Trust ID for trust scoping. :param string domain_id: Domain ID for domain scoping. :param string domain_name: Domain name for domain scoping. :param string project_id: Project ID for project scoping. :param string project_name: Project name for project scoping. :param string project_domain_id: Project's domain ID for project. :param string project_domain_name: Project's domain name for project. :param bool reauthenticate: Allow fetching a new token if the current one is going to expire. (optional) default True """ self._auth_url = auth_url self._trust_id = trust_id self._domain_id = domain_id self._domain_name = domain_name self._project_id = project_id self._project_name = project_name self._project_domain_id = project_domain_id self._project_domain_name = project_domain_name self._reauthenticate = reauthenticate super(KeystoneToken, self).__init__(token) @property def auth_url(self): """This method returns an auth_url.""" return self._auth_url @property def trust_id(self): """This method returns a trust_id.""" return self._trust_id @property def domain_id(self): """This method returns a domain_id.""" return self._domain_id @property def domain_name(self): """This method returns a domain_name.""" return self._domain_name @property def project_id(self): """This method returns a project_id.""" return self._project_id @property def project_name(self): """This method returns a project_name.""" return self._project_name @property def project_domain_id(self): """This method returns a project_domain_id.""" return self._project_domain_id @property def project_domain_name(self): """This method returns a project_domain_name.""" return self._project_domain_name @property def reauthenticate(self): """This method returns reauthenticate.""" return self._reauthenticate def __eq__(self, other): if isinstance(other, KeystoneToken): return ( self._token == other._token and self._trust_id == other._trust_id and self._domain_id == other._domain_id and self._domain_name == other._domain_name and self._project_id == other._project_id and self._project_name == other._project_name and self._project_domain_id == other._project_domain_id and self._project_domain_name == other._project_domain_name and self._reauthenticate == other._reauthenticate) else: return False def __ne__(self, other): result = self.__eq__(other) return not result castellan-3.0.1/castellan/common/utils.py0000664000175000017500000001743613645116250020471 0ustar zuulzuul00000000000000# Copyright (c) 2016 IBM # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """ Common utilities for Castellan. """ from castellan.common.credentials import keystone_password from castellan.common.credentials import keystone_token from castellan.common.credentials import password from castellan.common.credentials import token from castellan.common import exception from oslo_config import cfg from oslo_log import log as logging LOG = logging.getLogger(__name__) credential_opts = [ # auth_type opt cfg.StrOpt('auth_type', help="The type of authentication credential to create. " "Possible values are 'token', 'password', 'keystone_token', " "and 'keystone_password'. Required if no context is passed to " "the credential factory."), # token opt cfg.StrOpt('token', secret=True, help="Token for authentication. Required for 'token' and " "'keystone_token' auth_type if no context is passed to the " "credential factory."), # password opts cfg.StrOpt('username', help="Username for authentication. Required for 'password' " "auth_type. Optional for the 'keystone_password' auth_type."), cfg.StrOpt('password', secret=True, help="Password for authentication. Required for 'password' and " "'keystone_password' auth_type."), # keystone credential opts cfg.StrOpt('auth_url', help="Use this endpoint to connect to Keystone."), cfg.StrOpt('user_id', help="User ID for authentication. Optional for " "'keystone_token' and 'keystone_password' auth_type."), cfg.StrOpt('user_domain_id', help="User's domain ID for authentication. Optional for " "'keystone_token' and 'keystone_password' auth_type."), cfg.StrOpt('user_domain_name', help="User's domain name for authentication. Optional for " "'keystone_token' and 'keystone_password' auth_type."), cfg.StrOpt('trust_id', help="Trust ID for trust scoping. Optional for " "'keystone_token' and 'keystone_password' auth_type."), cfg.StrOpt('domain_id', help="Domain ID for domain scoping. Optional for " "'keystone_token' and 'keystone_password' auth_type."), cfg.StrOpt('domain_name', help="Domain name for domain scoping. Optional for " "'keystone_token' and 'keystone_password' auth_type."), cfg.StrOpt('project_id', help="Project ID for project scoping. Optional for " "'keystone_token' and 'keystone_password' auth_type."), cfg.StrOpt('project_name', help="Project name for project scoping. Optional for " "'keystone_token' and 'keystone_password' auth_type."), cfg.StrOpt('project_domain_id', help="Project's domain ID for project. Optional for " "'keystone_token' and 'keystone_password' auth_type."), cfg.StrOpt('project_domain_name', help="Project's domain name for project. Optional for " "'keystone_token' and 'keystone_password' auth_type."), cfg.BoolOpt('reauthenticate', default=True, help="Allow fetching a new token if the current one is " "going to expire. Optional for 'keystone_token' and " "'keystone_password' auth_type.") ] OPT_GROUP = 'key_manager' def credential_factory(conf=None, context=None): """This function provides a factory for credentials. It is used to create an appropriare credential object from a passed configuration. This should be called before making any calls to a key manager. :param conf: Configuration file which this factory method uses to generate a credential object. Note: In the future it will become a required field. :param context: Context used for authentication. It can be used in conjunction with the configuration file. If no conf is passed, then the context object will be converted to a KeystoneToken and returned. If a conf is passed then only the 'token' is grabbed from the context for the authentication types that require a token. :returns: A credential object used for authenticating with the Castellan key manager. Type of credential returned depends on config and/or context passed. """ if conf: conf.register_opts(credential_opts, group=OPT_GROUP) if conf.key_manager.auth_type == 'token': if conf.key_manager.token: auth_token = conf.key_manager.token elif context: auth_token = context.auth_token else: raise exception.InsufficientCredentialDataError() return token.Token(auth_token) elif conf.key_manager.auth_type == 'password': return password.Password( conf.key_manager.username, conf.key_manager.password) elif conf.key_manager.auth_type == 'keystone_password': return keystone_password.KeystonePassword( conf.key_manager.password, auth_url=conf.key_manager.auth_url, username=conf.key_manager.username, user_id=conf.key_manager.user_id, user_domain_id=conf.key_manager.user_domain_id, user_domain_name=conf.key_manager.user_domain_name, trust_id=conf.key_manager.trust_id, domain_id=conf.key_manager.domain_id, domain_name=conf.key_manager.domain_name, project_id=conf.key_manager.project_id, project_name=conf.key_manager.project_name, project_domain_id=conf.key_manager.project_domain_id, project_domain_name=conf.key_manager.project_domain_name, reauthenticate=conf.key_manager.reauthenticate) elif conf.key_manager.auth_type == 'keystone_token': if conf.key_manager.token: auth_token = conf.key_manager.token elif context: auth_token = context.auth_token else: raise exception.InsufficientCredentialDataError() return keystone_token.KeystoneToken( auth_token, auth_url=conf.key_manager.auth_url, trust_id=conf.key_manager.trust_id, domain_id=conf.key_manager.domain_id, domain_name=conf.key_manager.domain_name, project_id=conf.key_manager.project_id, project_name=conf.key_manager.project_name, project_domain_id=conf.key_manager.project_domain_id, project_domain_name=conf.key_manager.project_domain_name, reauthenticate=conf.key_manager.reauthenticate) else: LOG.error("Invalid auth_type specified.") raise exception.AuthTypeInvalidError( type=conf.key_manager.auth_type) # for compatibility between _TokenData and RequestContext if hasattr(context, 'tenant') and context.tenant: project_id = context.tenant elif hasattr(context, 'project_id') and context.project_id: project_id = context.project_id return keystone_token.KeystoneToken( context.auth_token, project_id=project_id) castellan-3.0.1/castellan/common/exception.py0000664000175000017500000000450713645116250021322 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Castellan exception subclasses """ import urllib from castellan.i18n import _ _FATAL_EXCEPTION_FORMAT_ERRORS = False class RedirectException(Exception): def __init__(self, url): self.url = urllib.parse.urlparse(url) class CastellanException(Exception): """Base Castellan Exception To correctly use this class, inherit from it and define a 'message' property. That message will get printf'd with the keyword arguments provided to the constructor. """ message = _("An unknown exception occurred") def __init__(self, message_arg=None, *args, **kwargs): if not message_arg: message_arg = self.message try: self.message = message_arg % kwargs except Exception: if _FATAL_EXCEPTION_FORMAT_ERRORS: raise else: # at least get the core message out if something happened pass super(CastellanException, self).__init__(self.message) class Forbidden(CastellanException): message = _("You are not authorized to complete this action.") class KeyManagerError(CastellanException): message = _("Key manager error: %(reason)s") class ManagedObjectNotFoundError(CastellanException): message = _("Key not found, uuid: %(uuid)s") class AuthTypeInvalidError(CastellanException): message = _("Invalid auth_type was specified, auth_type: %(type)s") class InsufficientCredentialDataError(CastellanException): message = _("Insufficient credential data was provided, either " "\"token\" must be set in the passed conf, or a context " "with an \"auth_token\" property must be passed.") castellan-3.0.1/castellan/common/objects/0000775000175000017500000000000013645116331020375 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/common/objects/symmetric_key.py0000664000175000017500000000414313645116250023635 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base SymmetricKey Class This module defines the SymmetricKey class. """ from castellan.common.objects import key class SymmetricKey(key.Key): """This class represents symmetric keys.""" def __init__(self, algorithm, bit_length, key, name=None, created=None, id=None): """Create a new SymmetricKey object. The arguments specify the algorithm and bit length for the symmetric encryption and the bytes for the key in a bytestring. """ self._alg = algorithm self._bit_length = bit_length self._key = key super(SymmetricKey, self).__init__(name=name, created=created, id=id) @property def algorithm(self): """Returns the algorithm for symmetric encryption.""" return self._alg @property def format(self): """This method returns 'RAW'.""" return "RAW" def get_encoded(self): """Returns the key in its encoded format.""" return self._key @property def bit_length(self): """Returns the key length.""" return self._bit_length def __eq__(self, other): if isinstance(other, SymmetricKey): return (self._alg == other._alg and self._bit_length == other._bit_length and self._key == other._key) else: return False def __ne__(self, other): result = self.__eq__(other) return not result castellan-3.0.1/castellan/common/objects/key.py0000664000175000017500000000305013645116250021535 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base Key Class This module defines the Key class. The Key class is the base class to represent all encryption keys. The basis for this class was copied from Java. """ import abc from castellan.common.objects import managed_object class Key(managed_object.ManagedObject): """Base class to represent all keys.""" @abc.abstractproperty def algorithm(self): """Returns the key's algorithm. Returns the key's algorithm. For example, "DSA" indicates that this key is a DSA key and "AES" indicates that this key is an AES key. """ pass @abc.abstractproperty def bit_length(self): """Returns the key's bit length. Returns the key's bit length. For example, for AES symmetric keys, this refers to the length of the key, and for RSA keys, this refers to the length of the modulus. """ pass castellan-3.0.1/castellan/common/objects/x_509.py0000664000175000017500000000312413645116250021613 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ X509 Class This module defines the X509 class, used to represent X.509 certificates. """ from castellan.common.objects import certificate class X509(certificate.Certificate): """This class represents X.509 certificates.""" def __init__(self, data, name=None, created=None, id=None): """Create a new X509 object. The data should be in a bytestring. """ self._data = data super(X509, self).__init__(name=name, created=created, id=id) @property def format(self): """This method returns 'X.509'.""" return "X.509" def get_encoded(self): """Returns the data in its encoded format.""" return self._data def __eq__(self, other): if isinstance(other, X509): return (self._data == other._data) else: return False def __ne__(self, other): result = self.__eq__(other) return not result castellan-3.0.1/castellan/common/objects/public_key.py0000664000175000017500000000421713645116250023101 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base PublicKey Class This module defines the PublicKey class. """ from castellan.common.objects import key class PublicKey(key.Key): """This class represents public keys.""" def __init__(self, algorithm, bit_length, key, name=None, created=None, id=None): """Create a new PublicKey object. The arguments specify the algorithm and bit length for the asymmetric encryption and the bytes for the key. The bytes should be in a bytestring. """ self._alg = algorithm self._bit_length = bit_length self._key = key super(PublicKey, self).__init__(name=name, created=created, id=id) @property def algorithm(self): """Returns the algorithm for asymmetric encryption.""" return self._alg @property def format(self): """This method returns 'SubjectPublicKeyInfo'.""" return "SubjectPublicKeyInfo" def get_encoded(self): """Returns the key in its encoded format.""" return self._key @property def bit_length(self): """Returns the key length.""" return self._bit_length def __eq__(self, other): if isinstance(other, PublicKey): return (self._alg == other._alg and self._bit_length == other._bit_length and self._key == other._key) else: return False def __ne__(self, other): result = self.__eq__(other) return not result castellan-3.0.1/castellan/common/objects/managed_object.py0000664000175000017500000000547713645116250023706 0ustar zuulzuul00000000000000# Copyright (c) The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base ManagedObject Class This module defines the ManagedObject class. The ManagedObject class is the base class to represent all objects managed by the key manager. """ import abc class ManagedObject(object, metaclass=abc.ABCMeta): """Base class to represent all managed objects.""" def __init__(self, name=None, created=None, id=None): """Managed Object :param name: the name of the managed object. :param created: the time a managed object was created. :param id: the ID of the object, generated after storing the object. """ self._name = name # If None or POSIX times if not created or type(created) == int: self._created = created else: raise ValueError('created must be of long type, actual type %s' % type(created)) self._id = id @property def id(self): """Returns the ID of the managed object. Returns the ID of the managed object or None if this object does not have one. If the ID is None, the object has not been persisted yet. """ return self._id @property def name(self): """Returns the name. Returns the object's name or None if this object does not have one. """ return self._name @property def created(self): """Returns the POSIX time(long) of the object that was created. Returns the POSIX time(long) of the object that was created or None if the object does not have one, meaning it has not been persisted. """ return self._created @abc.abstractproperty def format(self): """Returns the encoding format. Returns the object's encoding format or None if this object is not encoded. """ pass @abc.abstractmethod def get_encoded(self): """Returns the encoded object. Returns a bytestring object in a format represented in the encoding specified. """ pass def is_metadata_only(self): """Returns if the associated object is only metadata or not.""" return self.get_encoded() is None castellan-3.0.1/castellan/common/objects/passphrase.py0000664000175000017500000000320513645116250023120 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base Passphrase Class This module defines the Passphrase class. """ from castellan.common.objects import managed_object class Passphrase(managed_object.ManagedObject): """This class represents a passphrase.""" def __init__(self, passphrase, name=None, created=None, id=None): """Create a new Passphrase object. The expected type for the passphrase is a bytestring. """ self._passphrase = passphrase super(Passphrase, self).__init__(name=name, created=created, id=id) @property def format(self): """This method returns 'RAW'.""" return "RAW" def get_encoded(self): """Returns the data in a bytestring.""" return self._passphrase def __eq__(self, other): if isinstance(other, Passphrase): return (self._passphrase == other._passphrase) else: return False def __ne__(self, other): result = self.__eq__(other) return not result castellan-3.0.1/castellan/common/objects/opaque_data.py0000664000175000017500000000313713645116250023236 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base OpaqueData Class This module defines the OpaqueData class. """ from castellan.common.objects import managed_object class OpaqueData(managed_object.ManagedObject): """This class represents opaque data.""" def __init__(self, data, name=None, created=None, id=None): """Create a new OpaqueData object. Expected type for data is a bytestring. """ self._data = data super(OpaqueData, self).__init__(name=name, created=created, id=id) @property def format(self): """This method returns 'Opaque'.""" return "Opaque" def get_encoded(self): """Returns the data in its original format.""" return self._data def __eq__(self, other): if isinstance(other, OpaqueData): return (self._data == other._data) else: return False def __ne__(self, other): result = self.__eq__(other) return not result castellan-3.0.1/castellan/common/objects/private_key.py0000664000175000017500000000413313645116250023272 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base PrivateKey Class This module defines the PrivateKey class. """ from castellan.common.objects import key class PrivateKey(key.Key): """This class represents private keys.""" def __init__(self, algorithm, bit_length, key, name=None, created=None, id=None): """Create a new PrivateKey object. The arguments specify the algorithm and bit length for the asymmetric encryption and the bytes for the key in a bytestring. """ self._alg = algorithm self._bit_length = bit_length self._key = key super(PrivateKey, self).__init__(name=name, created=created, id=id) @property def algorithm(self): """Returns the algorithm for asymmetric encryption.""" return self._alg @property def format(self): """This method returns 'PKCS8'.""" return "PKCS8" @property def bit_length(self): """Returns the key length.""" return self._bit_length def get_encoded(self): """Returns the key in DER encoded format.""" return self._key def __eq__(self, other): if isinstance(other, PrivateKey): return (self._alg == other._alg and self._bit_length == other._bit_length and self._key == other._key) else: return False def __ne__(self, other): result = self.__eq__(other) return not result castellan-3.0.1/castellan/common/objects/certificate.py0000664000175000017500000000161213645116250023231 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base Certificate Class This module defines the Certificate class. """ from castellan.common.objects import managed_object class Certificate(managed_object.ManagedObject): """Base class to represent all certificates.""" castellan-3.0.1/castellan/common/objects/__init__.py0000664000175000017500000000000013645116250022474 0ustar zuulzuul00000000000000castellan-3.0.1/castellan/common/__init__.py0000664000175000017500000000000013645116250021043 0ustar zuulzuul00000000000000castellan-3.0.1/castellan/options.py0000664000175000017500000001642213645116250017526 0ustar zuulzuul00000000000000# Copyright (c) 2015 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from stevedore import ExtensionManager from oslo_config import cfg from oslo_log import log from castellan import key_manager try: from castellan.key_manager import barbican_key_manager as bkm except ImportError: bkm = None try: from castellan.key_manager import vault_key_manager as vkm except ImportError: vkm = None from castellan.common import utils _DEFAULT_LOG_LEVELS = ['castellan=WARN'] _DEFAULT_LOGGING_CONTEXT_FORMAT = ('%(asctime)s.%(msecs)03d %(process)d ' '%(levelname)s %(name)s [%(request_id)s ' '%(user_identity)s] %(instance)s' '%(message)s') def set_defaults(conf, backend=None, barbican_endpoint=None, barbican_api_version=None, auth_endpoint=None, retry_delay=None, number_of_retries=None, verify_ssl=None, api_class=None, vault_root_token_id=None, vault_approle_role_id=None, vault_approle_secret_id=None, vault_kv_mountpoint=None, vault_url=None, vault_ssl_ca_crt_file=None, vault_use_ssl=None, barbican_endpoint_type=None): """Set defaults for configuration values. Overrides the default options values. :param conf: Config instance in which to set default options. :param api_class: The full class name of the key manager API class. :param barbican_endpoint: Use this endpoint to connect to Barbican. :param barbican_api_version: Version of the Barbican API. :param auth_endpoint: Use this endpoint to connect to Keystone. :param retry_delay: Use this attribute to set retry delay. :param number_of_retries: Use this attribute to set number of retries. :param verify_ssl: Use this to specify if ssl should be verified. :param vault_root_token_id: Use this for the root token id for vault. :param vault_approle_role_id: Use this for the approle role_id for vault. :param vault_approle_secret_id: Use this for the approle secret_id for vault. :param vault_kv_mountpoint: Mountpoint of KV store in vault to use. :param vault_url: Use this for the url for vault. :param vault_use_ssl: Use this to force vault driver to use ssl. :param vault_ssl_ca_crt_file: Use this for the CA file for vault. :param barbican_endpoint_type: Use this to specify the type of URL. : Valid values are: public, internal or admin. """ conf.register_opts(key_manager.key_manager_opts, group='key_manager') ext_mgr = ExtensionManager( "castellan.drivers", invoke_on_load=True, invoke_args=[cfg.CONF]) for km in ext_mgr.names(): for group, opts in ext_mgr[km].obj.list_options_for_discovery(): conf.register_opts(opts, group=group) # Use the new backend option if set or fall back to the older api_class default_backend = backend or api_class if default_backend is not None: conf.set_default('backend', default_backend, group='key_manager') if bkm is not None: if barbican_endpoint is not None: conf.set_default('barbican_endpoint', barbican_endpoint, group=bkm._BARBICAN_OPT_GROUP) if barbican_api_version is not None: conf.set_default('barbican_api_version', barbican_api_version, group=bkm._BARBICAN_OPT_GROUP) if auth_endpoint is not None: conf.set_default('auth_endpoint', auth_endpoint, group=bkm._BARBICAN_OPT_GROUP) if retry_delay is not None: conf.set_default('retry_delay', retry_delay, group=bkm._BARBICAN_OPT_GROUP) if number_of_retries is not None: conf.set_default('number_of_retries', number_of_retries, group=bkm._BARBICAN_OPT_GROUP) if verify_ssl is not None: conf.set_default('verify_ssl', verify_ssl, group=bkm._BARBICAN_OPT_GROUP) if barbican_endpoint_type is not None: conf.set_default('barbican_endpoint_type', barbican_endpoint_type, group=bkm._BARBICAN_OPT_GROUP) if vkm is not None: if vault_root_token_id is not None: conf.set_default('root_token_id', vault_root_token_id, group=vkm._VAULT_OPT_GROUP) if vault_approle_role_id is not None: conf.set_default('approle_role_id', vault_approle_role_id, group=vkm._VAULT_OPT_GROUP) if vault_approle_secret_id is not None: conf.set_default('approle_secret_id', vault_approle_secret_id, group=vkm._VAULT_OPT_GROUP) if vault_kv_mountpoint is not None: conf.set_default('kv_mountpoint', vault_kv_mountpoint, group=vkm._VAULT_OPT_GROUP) if vault_url is not None: conf.set_default('vault_url', vault_url, group=vkm._VAULT_OPT_GROUP) if vault_ssl_ca_crt_file is not None: conf.set_default('ssl_ca_crt_file', vault_ssl_ca_crt_file, group=vkm._VAULT_OPT_GROUP) if vault_use_ssl is not None: conf.set_default('use_ssl', vault_use_ssl, group=vkm._VAULT_OPT_GROUP) def enable_logging(conf=None, app_name='castellan'): conf = conf or cfg.CONF log.register_options(conf) log.set_defaults(_DEFAULT_LOGGING_CONTEXT_FORMAT, _DEFAULT_LOG_LEVELS) log.setup(conf, app_name) def list_opts(): """Returns a list of oslo.config options available in the library. The returned list includes all oslo.config options which may be registered at runtime by the library. Each element of the list is a tuple. The first element is the name of the group under which the list of elements in the second element will be registered. A group name of None corresponds to the [DEFAULT] group in config files. The purpose of this is to allow tools like the Oslo sample config file generator to discover the options exposed to users by this library. :returns: a list of (group_name, opts) tuples """ key_manager_opts = [] key_manager_opts.extend(key_manager.key_manager_opts) key_manager_opts.extend(utils.credential_opts) opts = [('key_manager', key_manager_opts)] ext_mgr = ExtensionManager( "castellan.drivers", invoke_on_load=True, invoke_args=[cfg.CONF]) for driver in ext_mgr.names(): opts.extend(ext_mgr[driver].obj.list_options_for_discovery()) return opts castellan-3.0.1/castellan/_config_driver.py0000664000175000017500000001155313645116250021012 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. r""" Castellan Oslo Config Driver ---------------------------- This driver is an oslo.config backend driver implemented with Castellan. It extends oslo.config's capabilities by enabling it to retrieve configuration values from a secret manager behind Castellan. The setup of a Castellan configuration source is as follow:: [DEFAULT] config_source = castellan_config_group [castellan_config_group] driver = castellan config_file = castellan.conf mapping_file = mapping.conf In the following sessions, you can find more information about this driver's classes and its options. The Driver Class ================ .. autoclass:: CastellanConfigurationSourceDriver The Configuration Source Class ============================== .. autoclass:: CastellanConfigurationSource """ from castellan.common.exception import KeyManagerError from castellan.common.exception import ManagedObjectNotFoundError from castellan import key_manager from oslo_config import cfg from oslo_config import sources from oslo_log import log LOG = log.getLogger(__name__) class CastellanConfigurationSourceDriver(sources.ConfigurationSourceDriver): """A backend driver for configuration values served through castellan. Required options: - config_file: The castellan configuration file. - mapping_file: A configuration/castellan_id mapping file. This file creates connections between configuration options and castellan ids. The group and option name remains the same, while the value gets stored a secret manager behind castellan and is replaced by its castellan id. The ids will be used to fetch the values through castellan. """ _castellan_driver_opts = [ cfg.StrOpt( 'config_file', required=True, sample_default='etc/castellan/castellan.conf', help=('The path to a castellan configuration file.'), ), cfg.StrOpt( 'mapping_file', required=True, sample_default='etc/castellan/secrets_mapping.conf', help=('The path to a configuration/castellan_id mapping file.'), ), ] def list_options_for_discovery(self): return self._castellan_driver_opts def open_source_from_opt_group(self, conf, group_name): conf.register_opts(self._castellan_driver_opts, group_name) return CastellanConfigurationSource( group_name, conf[group_name].config_file, conf[group_name].mapping_file) class CastellanConfigurationSource(sources.ConfigurationSource): """A configuration source for configuration values served through castellan. :param config_file: The path to a castellan configuration file. :param mapping_file: The path to a configuration/castellan_id mapping file. """ def __init__(self, group_name, config_file, mapping_file): conf = cfg.ConfigOpts() conf(args=[], default_config_files=[config_file]) self._name = group_name self._mngr = key_manager.API(conf) self._mapping = {} cfg.ConfigParser(mapping_file, self._mapping).parse() def get(self, group_name, option_name, opt): try: group_name = group_name or "DEFAULT" castellan_id = self._mapping[group_name][option_name][0] return (self._mngr.get("ctx", castellan_id).get_encoded().decode(), cfg.LocationInfo(cfg.Locations.user, castellan_id)) except KeyError: # no mapping 'option = castellan_id' LOG.info("option '[%s] %s' not present in '[%s] mapping_file'", group_name, option_name, self._name) except KeyManagerError: # bad mapping 'option =' without a castellan_id LOG.warning("missing castellan_id for option " "'[%s] %s' in '[%s] mapping_file'", group_name, option_name, self._name) except ManagedObjectNotFoundError: # good mapping, but unknown castellan_id by secret manager LOG.warning("invalid castellan_id for option " "'[%s] %s' in '[%s] mapping_file'", group_name, option_name, self._name) return (sources._NoValue, None) castellan-3.0.1/castellan/key_manager/0000775000175000017500000000000013645116331017736 5ustar zuulzuul00000000000000castellan-3.0.1/castellan/key_manager/vault_key_manager.py0000664000175000017500000003265613645116250024021 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Key manager implementation for Vault """ import binascii from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.serialization import Encoding from cryptography.hazmat.primitives.serialization import NoEncryption from cryptography.hazmat.primitives.serialization import PrivateFormat from cryptography.hazmat.primitives.serialization import PublicFormat import os import time import uuid from keystoneauth1 import loading from oslo_config import cfg from oslo_log import log as logging from oslo_utils import timeutils import requests from castellan.common import exception from castellan.common.objects import private_key as pri_key from castellan.common.objects import public_key as pub_key from castellan.common.objects import symmetric_key as sym_key from castellan.i18n import _ from castellan.key_manager import key_manager _DEFAULT_VAULT_URL = "http://127.0.0.1:8200" _DEFAULT_MOUNTPOINT = "secret" _vault_opts = [ cfg.StrOpt('root_token_id', help='root token for vault'), cfg.StrOpt('approle_role_id', help='AppRole role_id for authentication with vault'), cfg.StrOpt('approle_secret_id', help='AppRole secret_id for authentication with vault'), cfg.StrOpt('kv_mountpoint', default=_DEFAULT_MOUNTPOINT, help='Mountpoint of KV store in Vault to use, for example: ' '{}'.format(_DEFAULT_MOUNTPOINT)), cfg.StrOpt('vault_url', default=_DEFAULT_VAULT_URL, help='Use this endpoint to connect to Vault, for example: ' '"%s"' % _DEFAULT_VAULT_URL), cfg.StrOpt('ssl_ca_crt_file', help='Absolute path to ca cert file'), cfg.BoolOpt('use_ssl', default=False, help=_('SSL Enabled/Disabled')), ] _VAULT_OPT_GROUP = 'vault' _EXCEPTIONS_BY_CODE = [ requests.codes['internal_server_error'], requests.codes['service_unavailable'], requests.codes['request_timeout'], requests.codes['gateway_timeout'], requests.codes['precondition_failed'], ] LOG = logging.getLogger(__name__) class VaultKeyManager(key_manager.KeyManager): """Key Manager Interface that wraps the Vault REST API.""" def __init__(self, configuration): self._conf = configuration self._conf.register_opts(_vault_opts, group=_VAULT_OPT_GROUP) loading.register_session_conf_options(self._conf, _VAULT_OPT_GROUP) self._session = requests.Session() self._root_token_id = self._conf.vault.root_token_id self._approle_role_id = self._conf.vault.approle_role_id self._approle_secret_id = self._conf.vault.approle_secret_id self._cached_approle_token_id = None self._approle_token_ttl = None self._approle_token_issue = None self._kv_mountpoint = self._conf.vault.kv_mountpoint self._vault_url = self._conf.vault.vault_url if self._vault_url.startswith("https://"): self._verify_server = self._conf.vault.ssl_ca_crt_file or True else: self._verify_server = False self._vault_kv_version = None def _get_url(self): if not self._vault_url.endswith('/'): self._vault_url += '/' return self._vault_url def _get_api_version(self): if self._vault_kv_version: return self._vault_kv_version resource_url = '{}v1/sys/internal/ui/mounts/{}'.format( self._get_url(), self._kv_mountpoint ) resp = self._do_http_request(self._session.get, resource_url) if resp.status_code == requests.codes['not_found']: self._vault_kv_version = '1' else: self._vault_kv_version = resp.json()['data']['options']['version'] return self._vault_kv_version def _get_resource_url(self, key_id=None): return '{}v1/{}/{}{}'.format( self._get_url(), self._kv_mountpoint, '' if self._get_api_version() == '1' else 'data/' if key_id else 'metadata/', # no key_id is for listing and 'data/' doesn't works key_id if key_id else '?list=true') @property def _approle_token_id(self): if (all((self._approle_token_issue, self._approle_token_ttl)) and timeutils.is_older_than(self._approle_token_issue, self._approle_token_ttl)): self._cached_approle_token_id = None return self._cached_approle_token_id def _build_auth_headers(self): if self._root_token_id: return {'X-Vault-Token': self._root_token_id} if self._approle_token_id: return {'X-Vault-Token': self._approle_token_id} if self._approle_role_id: params = { 'role_id': self._approle_role_id } if self._approle_secret_id: params['secret_id'] = self._approle_secret_id approle_login_url = '{}v1/auth/approle/login'.format( self._get_url() ) token_issue_utc = timeutils.utcnow() try: resp = self._session.post(url=approle_login_url, json=params, verify=self._verify_server) except requests.exceptions.Timeout as ex: raise exception.KeyManagerError(str(ex)) except requests.exceptions.ConnectionError as ex: raise exception.KeyManagerError(str(ex)) except Exception as ex: raise exception.KeyManagerError(str(ex)) if resp.status_code in _EXCEPTIONS_BY_CODE: raise exception.KeyManagerError(resp.reason) if resp.status_code == requests.codes['forbidden']: raise exception.Forbidden() resp = resp.json() self._cached_approle_token_id = resp['auth']['client_token'] self._approle_token_issue = token_issue_utc self._approle_token_ttl = resp['auth']['lease_duration'] return {'X-Vault-Token': self._approle_token_id} return {} def _do_http_request(self, method, resource, json=None): verify = self._verify_server headers = self._build_auth_headers() try: resp = method(resource, headers=headers, json=json, verify=verify) except requests.exceptions.Timeout as ex: raise exception.KeyManagerError(str(ex)) except requests.exceptions.ConnectionError as ex: raise exception.KeyManagerError(str(ex)) except Exception as ex: raise exception.KeyManagerError(str(ex)) if resp.status_code in _EXCEPTIONS_BY_CODE: raise exception.KeyManagerError(resp.reason) if resp.status_code == requests.codes['forbidden']: raise exception.Forbidden() return resp def create_key_pair(self, context, algorithm, length, expiration=None, name=None): """Creates an asymmetric key pair.""" if algorithm.lower() != 'rsa': raise NotImplementedError( "VaultKeyManager only implements rsa keys" ) priv_key = rsa.generate_private_key( public_exponent=65537, key_size=length, backend=default_backend() ) private_key = pri_key.PrivateKey( 'RSA', length, priv_key.private_bytes( Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() ) ) private_key_id = uuid.uuid4().hex private_id = self._store_key_value( private_key_id, private_key ) # pub_key = priv_key.public_key() public_key = pub_key.PublicKey( 'RSA', length, priv_key.public_key().public_bytes( Encoding.PEM, PublicFormat.SubjectPublicKeyInfo ) ) public_key_id = uuid.uuid4().hex public_id = self._store_key_value( public_key_id, public_key ) return private_id, public_id def _store_key_value(self, key_id, value): type_value = self._secret_type_dict.get(type(value)) if type_value is None: raise exception.KeyManagerError( "Unknown type for value : %r" % value) record = { 'type': type_value, 'value': binascii.hexlify(value.get_encoded()).decode('utf-8'), 'algorithm': (value.algorithm if hasattr(value, 'algorithm') else None), 'bit_length': (value.bit_length if hasattr(value, 'bit_length') else None), 'name': value.name, 'created': value.created } if self._get_api_version() != '1': record = {'data': record} self._do_http_request(self._session.post, self._get_resource_url(key_id), json=record) return key_id def create_key(self, context, algorithm, length, name=None, **kwargs): """Creates a symmetric key.""" if length % 8: msg = _("Length must be multiple of 8.") raise ValueError(msg) key_id = uuid.uuid4().hex key_value = os.urandom((length or 256) // 8) key = sym_key.SymmetricKey(algorithm, length or 256, key_value, key_id, name or int(time.time())) return self._store_key_value(key_id, key) def store(self, context, key_value, **kwargs): """Stores (i.e., registers) a key with the key manager.""" key_id = uuid.uuid4().hex return self._store_key_value(key_id, key_value) def get(self, context, key_id, metadata_only=False): """Retrieves the key identified by the specified id.""" if not key_id: raise exception.KeyManagerError('key identifier not provided') resp = self._do_http_request(self._session.get, self._get_resource_url(key_id)) if resp.status_code == requests.codes['not_found']: raise exception.ManagedObjectNotFoundError(uuid=key_id) record = resp.json()['data'] if self._get_api_version() != '1': record = record['data'] key = None if metadata_only else binascii.unhexlify(record['value']) clazz = None for type_clazz, type_name in self._secret_type_dict.items(): if type_name == record['type']: clazz = type_clazz if clazz is None: raise exception.KeyManagerError( "Unknown type : %r" % record['type']) if hasattr(clazz, 'algorithm') and hasattr(clazz, 'bit_length'): return clazz(record['algorithm'], record['bit_length'], key, record['name'], record['created'], key_id) else: return clazz(key, record['name'], record['created'], key_id) def delete(self, context, key_id): """Represents deleting the key.""" if not key_id: raise exception.KeyManagerError('key identifier not provided') resp = self._do_http_request(self._session.delete, self._get_resource_url(key_id)) if resp.status_code == requests.codes['not_found']: raise exception.ManagedObjectNotFoundError(uuid=key_id) def list(self, context, object_type=None, metadata_only=False): """Lists the managed objects given the criteria.""" if object_type and object_type not in self._secret_type_dict: msg = _("Invalid secret type: %s") % object_type raise exception.KeyManagerError(reason=msg) resp = self._do_http_request(self._session.get, self._get_resource_url()) if resp.status_code == requests.codes['not_found']: keys = [] else: keys = resp.json()['data']['keys'] objects = [] for obj_id in keys: try: obj = self.get(context, obj_id, metadata_only=metadata_only) if object_type is None or isinstance(obj, object_type): objects.append(obj) except exception.ManagedObjectNotFoundError as e: LOG.warning("Error occurred while retrieving object " "metadata, not adding it to the list: %s", e) pass return objects def list_options_for_discovery(self): return [(_VAULT_OPT_GROUP, _vault_opts)] castellan-3.0.1/castellan/key_manager/not_implemented_key_manager.py0000664000175000017500000000345513645116250026044 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Key manager implementation that raises NotImplementedError """ from castellan.key_manager import key_manager class NotImplementedKeyManager(key_manager.KeyManager): """Key Manager Interface that raises NotImplementedError for all operations """ def __init__(self, configuration=None): super(NotImplementedKeyManager, self).__init__(configuration) def create_key(self, context, algorithm='AES', length=256, expiration=None, name=None, **kwargs): raise NotImplementedError() def create_key_pair(self, context, algorithm, length, expiration=None, name=None): raise NotImplementedError() def store(self, context, managed_object, expiration=None, **kwargs): raise NotImplementedError() def copy(self, context, managed_object_id, **kwargs): raise NotImplementedError() def get(self, context, managed_object_id, **kwargs): raise NotImplementedError() def list(self, context, object_type=None): raise NotImplementedError() def delete(self, context, managed_object_id, **kwargs): raise NotImplementedError() castellan-3.0.1/castellan/key_manager/migration.py0000664000175000017500000000551513645116250022307 0ustar zuulzuul00000000000000# Copyright 2017 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import binascii from castellan.common import exception from castellan.common.objects import symmetric_key from oslo_config import cfg from oslo_log import log as logging LOG = logging.getLogger(__name__) def handle_migration(conf, key_mgr): try: conf.register_opt(cfg.StrOpt('fixed_key'), group='key_manager') except cfg.DuplicateOptError: pass if conf.key_manager.fixed_key is not None and \ not conf.key_manager.backend.endswith('ConfKeyManager'): LOG.warning("Using MigrationKeyManager to provide support for legacy" " fixed_key encryption") class MigrationKeyManager(type(key_mgr)): def __init__(self, configuration): self.fixed_key = configuration.key_manager.fixed_key self.fixed_key_id = '00000000-0000-0000-0000-000000000000' super(MigrationKeyManager, self).__init__(configuration) def get(self, context, managed_object_id): if managed_object_id == self.fixed_key_id: LOG.debug("Processing request for secret associated" " with fixed_key key ID") if context is None: raise exception.Forbidden() key_bytes = bytes(binascii.unhexlify(self.fixed_key)) secret = symmetric_key.SymmetricKey('AES', len(key_bytes) * 8, key_bytes) else: secret = super(MigrationKeyManager, self).get( context, managed_object_id) return secret def delete(self, context, managed_object_id): if managed_object_id == self.fixed_key_id: LOG.debug("Not deleting key associated with" " fixed_key key ID") if context is None: raise exception.Forbidden() else: super(MigrationKeyManager, self).delete(context, managed_object_id) key_mgr = MigrationKeyManager(configuration=conf) return key_mgr castellan-3.0.1/castellan/key_manager/barbican_key_manager.py0000664000175000017500000006572513645116250024432 0ustar zuulzuul00000000000000# Copyright (c) The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Key manager implementation for Barbican """ import calendar import time import urllib from cryptography.hazmat import backends from cryptography.hazmat.primitives import serialization from cryptography import x509 as cryptography_x509 from keystoneauth1 import identity from keystoneauth1 import loading from keystoneauth1 import session from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils from castellan.common import exception from castellan.common.objects import key as key_base_class from castellan.common.objects import opaque_data as op_data from castellan.i18n import _ from castellan.key_manager import key_manager from barbicanclient import client as barbican_client_import from barbicanclient import exceptions as barbican_exceptions from oslo_utils import timeutils _barbican_opts = [ cfg.StrOpt('barbican_endpoint', help='Use this endpoint to connect to Barbican, for example: ' '"http://localhost:9311/"'), cfg.StrOpt('barbican_api_version', help='Version of the Barbican API, for example: "v1"'), cfg.StrOpt('auth_endpoint', default='http://localhost/identity/v3', deprecated_name='auth_url', deprecated_group='key_manager', help='Use this endpoint to connect to Keystone'), cfg.IntOpt('retry_delay', default=1, help='Number of seconds to wait before retrying poll for key ' 'creation completion'), cfg.IntOpt('number_of_retries', default=60, help='Number of times to retry poll for key creation ' 'completion'), cfg.BoolOpt('verify_ssl', default=True, help='Specifies if insecure TLS (https) requests. If False, ' 'the server\'s certificate will not be validated'), cfg.StrOpt('barbican_endpoint_type', default='public', choices=['public', 'internal', 'admin'], help='Specifies the type of endpoint. Allowed values are: ' 'public, private, and admin'), ] _BARBICAN_OPT_GROUP = 'barbican' LOG = logging.getLogger(__name__) class BarbicanKeyManager(key_manager.KeyManager): """Key Manager Interface that wraps the Barbican client API.""" def __init__(self, configuration): self._barbican_client = None self._base_url = None self.conf = configuration self.conf.register_opts(_barbican_opts, group=_BARBICAN_OPT_GROUP) loading.register_session_conf_options(self.conf, _BARBICAN_OPT_GROUP) def _get_barbican_client(self, context): """Creates a client to connect to the Barbican service. :param context: the user context for authentication :return: a Barbican Client object :raises Forbidden: if the context is None :raises KeyManagerError: if context is missing tenant or tenant is None or error occurs while creating client """ # Confirm context is provided, if not raise forbidden if not context: msg = _("User is not authorized to use key manager.") LOG.error(msg) raise exception.Forbidden(msg) if self._barbican_client and self._current_context == context: return self._barbican_client try: auth = self._get_keystone_auth(context) sess = session.Session(auth=auth, verify=self.conf.barbican.verify_ssl) self._barbican_endpoint = self._get_barbican_endpoint(auth, sess) self._barbican_client = barbican_client_import.Client( session=sess, endpoint=self._barbican_endpoint) self._current_context = context # TODO(pbourke): more fine grained exception handling - we are eating # tracebacks here except Exception as e: LOG.error("Error creating Barbican client: %s", e) raise exception.KeyManagerError(reason=e) self._base_url = self._create_base_url(auth, sess, self._barbican_endpoint) return self._barbican_client def _get_keystone_auth(self, context): if context.__class__.__name__ == 'KeystonePassword': return identity.Password( auth_url=context.auth_url, username=context.username, password=context.password, user_id=context.user_id, user_domain_id=context.user_domain_id, user_domain_name=context.user_domain_name, trust_id=context.trust_id, domain_id=context.domain_id, domain_name=context.domain_name, project_id=context.project_id, project_name=context.project_name, project_domain_id=context.project_domain_id, project_domain_name=context.project_domain_name, reauthenticate=context.reauthenticate) elif context.__class__.__name__ == 'KeystoneToken': return identity.Token( auth_url=context.auth_url, token=context.token, trust_id=context.trust_id, domain_id=context.domain_id, domain_name=context.domain_name, project_id=context.project_id, project_name=context.project_name, project_domain_id=context.project_domain_id, project_domain_name=context.project_domain_name, reauthenticate=context.reauthenticate) # this will be kept for oslo.context compatibility until # projects begin to use utils.credential_factory elif context.__class__.__name__ == 'RequestContext': if getattr(context, 'get_auth_plugin', None): return context.get_auth_plugin() else: return identity.Token( auth_url=self.conf.barbican.auth_endpoint, token=context.auth_token, project_id=context.project_id, project_name=context.project_name, project_domain_id=context.project_domain_id, project_domain_name=context.project_domain_name) else: msg = _("context must be of type KeystonePassword, " "KeystoneToken, or RequestContext.") LOG.error(msg) raise exception.Forbidden(reason=msg) def _get_barbican_endpoint(self, auth, sess): barbican = self.conf.barbican if barbican.barbican_endpoint: return barbican.barbican_endpoint elif getattr(auth, 'service_catalog', None): endpoint_data = auth.service_catalog.endpoint_data_for( service_type='key-manager') return endpoint_data.url else: service_parameters = {'service_type': 'key-manager', 'service_name': 'barbican', 'interface': barbican.barbican_endpoint_type} return auth.get_endpoint(sess, **service_parameters) def _create_base_url(self, auth, sess, endpoint): api_version = None if self.conf.barbican.barbican_api_version: api_version = self.conf.barbican.barbican_api_version elif getattr(auth, 'service_catalog', None): endpoint_data = auth.service_catalog.endpoint_data_for( service_type='key-manager') api_version = endpoint_data.api_version elif getattr(auth, 'get_discovery', None): discovery = auth.get_discovery(sess, url=endpoint) raw_data = discovery.raw_version_data() if len(raw_data) == 0: msg = _( "Could not find discovery information for %s") % endpoint LOG.error(msg) raise exception.KeyManagerError(reason=msg) latest_version = raw_data[-1] api_version = latest_version.get('id') if endpoint[-1] != '/': endpoint += '/' base_url = urllib.parse.urljoin( endpoint, api_version) return base_url def create_key(self, context, algorithm, length, expiration=None, name=None): """Creates a symmetric key. :param context: contains information of the user and the environment for the request (castellan/context.py) :param algorithm: the algorithm associated with the secret :param length: the bit length of the secret :param name: the name of the key :param expiration: the date the key will expire :return: the UUID of the new key :raises KeyManagerError: if key creation fails """ barbican_client = self._get_barbican_client(context) try: key_order = barbican_client.orders.create_key( name=name, algorithm=algorithm, bit_length=length, expiration=expiration) order_ref = key_order.submit() order = self._get_active_order(barbican_client, order_ref) return self._retrieve_secret_uuid(order.secret_ref) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error("Error creating key: %s", e) raise exception.KeyManagerError(reason=e) def create_key_pair(self, context, algorithm, length, expiration=None, name=None): """Creates an asymmetric key pair. :param context: contains information of the user and the environment for the request (castellan/context.py) :param algorithm: the algorithm associated with the secret :param length: the bit length of the secret :param name: the name of the key :param expiration: the date the key will expire :return: the UUIDs of the new key, in the order (private, public) :raises NotImplementedError: until implemented :raises KeyManagerError: if key pair creation fails """ barbican_client = self._get_barbican_client(context) try: key_pair_order = barbican_client.orders.create_asymmetric( algorithm=algorithm, bit_length=length, name=name, expiration=expiration) order_ref = key_pair_order.submit() order = self._get_active_order(barbican_client, order_ref) container = barbican_client.containers.get(order.container_ref) private_key_uuid = self._retrieve_secret_uuid( container.secret_refs['private_key']) public_key_uuid = self._retrieve_secret_uuid( container.secret_refs['public_key']) return private_key_uuid, public_key_uuid except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error("Error creating key pair: %s", e) raise exception.KeyManagerError(reason=e) def _get_barbican_object(self, barbican_client, managed_object): """Converts the Castellan managed_object to a Barbican secret.""" name = getattr(managed_object, 'name', None) try: algorithm = managed_object.algorithm bit_length = managed_object.bit_length except AttributeError: algorithm = None bit_length = None secret_type = self._secret_type_dict.get(type(managed_object), 'opaque') payload = self._get_normalized_payload(managed_object.get_encoded(), secret_type) secret = barbican_client.secrets.create(payload=payload, algorithm=algorithm, bit_length=bit_length, name=name, secret_type=secret_type) return secret def _get_normalized_payload(self, encoded_bytes, secret_type): """Normalizes the bytes of the object. Barbican expects certificates, public keys, and private keys in PEM format, but Castellan expects these objects to be DER encoded bytes instead. """ if secret_type == 'public': key = serialization.load_der_public_key( encoded_bytes, backend=backends.default_backend()) return key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) elif secret_type == 'private': key = serialization.load_der_private_key( encoded_bytes, backend=backends.default_backend(), password=None) return key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption()) elif secret_type == 'certificate': cert = cryptography_x509.load_der_x509_certificate( encoded_bytes, backend=backends.default_backend()) return cert.public_bytes(encoding=serialization.Encoding.PEM) else: return encoded_bytes def store(self, context, managed_object, expiration=None): """Stores (i.e., registers) an object with the key manager. :param context: contains information of the user and the environment for the request (castellan/context.py) :param managed_object: a secret object with unencrypted payload. Known as "secret" to the barbicanclient api :param expiration: the expiration time of the secret in ISO 8601 format :returns: the UUID of the stored object :raises KeyManagerError: if object store fails """ barbican_client = self._get_barbican_client(context) try: secret = self._get_barbican_object(barbican_client, managed_object) secret.expiration = expiration secret_ref = secret.store() return self._retrieve_secret_uuid(secret_ref) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error("Error storing object: %s", e) raise exception.KeyManagerError(reason=e) def _create_secret_ref(self, object_id): """Creates the URL required for accessing a secret. :param object_id: the UUID of the key to copy :return: the URL of the requested secret """ if not object_id: msg = _("Key ID is None") raise exception.KeyManagerError(reason=msg) base_url = self._base_url if base_url[-1] != '/': base_url += '/' return urllib.parse.urljoin(base_url, "secrets/" + object_id) def _get_active_order(self, barbican_client, order_ref): """Returns the order when it is active. Barbican key creation is done asynchronously, so this loop continues checking until the order is active or a timeout occurs. """ active_status = u'ACTIVE' error_status = u'ERROR' number_of_retries = self.conf.barbican.number_of_retries retry_delay = self.conf.barbican.retry_delay order = barbican_client.orders.get(order_ref) time.sleep(.25) for n in range(number_of_retries): if order.status == error_status: kwargs = {"status": error_status, "code": order.error_status_code, "reason": order.error_reason} msg = _("Order is in %(status)s status - status code: " "%(code)s, status reason: %(reason)s") % kwargs LOG.error(msg) raise exception.KeyManagerError(reason=msg) if order.status != active_status: kwargs = {'attempt': n, 'total': number_of_retries, 'status': order.status, 'active': active_status, 'delay': retry_delay} msg = _("Retry attempt #%(attempt)i out of %(total)i: " "Order status is '%(status)s'. Waiting for " "'%(active)s', will retry in %(delay)s " "seconds") LOG.info(msg, kwargs) time.sleep(retry_delay) order = barbican_client.orders.get(order_ref) else: return order msg = _("Exceeded retries: Failed to find '%(active)s' status " "within %(num_retries)i retries") % { 'active': active_status, 'num_retries': number_of_retries} LOG.error(msg) raise exception.KeyManagerError(reason=msg) def _retrieve_secret_uuid(self, secret_ref): """Retrieves the UUID of the secret from the secret_ref. :param secret_ref: the href of the secret :return: the UUID of the secret """ # The secret_ref is assumed to be of a form similar to # http://host:9311/v1/secrets/d152fa13-2b41-42ca-a934-6c21566c0f40 # with the UUID at the end. This command retrieves everything # after the last '/', which is the UUID. return secret_ref.rpartition('/')[2] def _get_secret_data(self, secret): """Retrieves the secret data. Converts the Barbican secret to bytes suitable for a Castellan object. If the secret is a public key, private key, or certificate, the secret is expected to be in PEM format and will be converted to DER. :param secret: the secret from barbican with the payload of data :returns: the secret data """ if secret.secret_type == 'public': key = serialization.load_pem_public_key( secret.payload, backend=backends.default_backend()) return key.public_bytes( encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo) elif secret.secret_type == 'private': key = serialization.load_pem_private_key( secret.payload, backend=backends.default_backend(), password=None) return key.private_bytes( encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption()) elif secret.secret_type == 'certificate': cert = cryptography_x509.load_pem_x509_certificate( secret.payload, backend=backends.default_backend()) return cert.public_bytes(encoding=serialization.Encoding.DER) else: return secret.payload def _get_castellan_object(self, secret, metadata_only=False): """Creates a Castellan managed object given the Barbican secret. The python barbicanclient lazy-loads the secret data, i.e. the secret data is not requested until secret.payload is called. If the user specifies metadata_only=True, secret.payload is never called, preventing unnecessary loading of secret data. :param secret: the barbican secret object :metadata_only: boolean indicating if the secret bytes should be included in the managed object :returns: the castellan object """ secret_type = op_data.OpaqueData for castellan_type, barbican_type in self._secret_type_dict.items(): if barbican_type == secret.secret_type: secret_type = castellan_type if metadata_only: secret_data = None else: secret_data = self._get_secret_data(secret) if secret.secret_ref: object_id = self._retrieve_secret_uuid(secret.secret_ref) else: object_id = None # convert created ISO8601 in Barbican to POSIX if secret.created: time_stamp = timeutils.parse_isotime( str(secret.created)).timetuple() created = calendar.timegm(time_stamp) if issubclass(secret_type, key_base_class.Key): return secret_type(secret.algorithm, secret.bit_length, secret_data, secret.name, created, object_id) else: return secret_type(secret_data, secret.name, created, object_id) def _get_secret(self, context, object_id): """Returns the metadata of the secret. :param context: contains information of the user and the environment for the request (castellan/context.py) :param object_id: UUID of the secret :return: the secret's metadata :raises HTTPAuthError: if object retrieval fails with 401 :raises HTTPClientError: if object retrieval fails with 4xx :raises HTTPServerError: if object retrieval fails with 5xx """ barbican_client = self._get_barbican_client(context) try: secret_ref = self._create_secret_ref(object_id) return barbican_client.secrets.get(secret_ref) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: with excutils.save_and_reraise_exception(): LOG.error("Error getting secret metadata: %s", e) def _is_secret_not_found_error(self, error): if (isinstance(error, barbican_exceptions.HTTPClientError) and error.status_code == 404): return True else: return False def get(self, context, managed_object_id, metadata_only=False): """Retrieves the specified managed object. :param context: contains information of the user and the environment for the request (castellan/context.py) :param managed_object_id: the UUID of the object to retrieve :param metadata_only: whether secret data should be included :return: ManagedObject representation of the managed object :raises KeyManagerError: if object retrieval fails :raises ManagedObjectNotFoundError: if object not found """ try: secret = self._get_secret(context, managed_object_id) return self._get_castellan_object(secret, metadata_only) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error("Error retrieving object: %s", e) if self._is_secret_not_found_error(e): raise exception.ManagedObjectNotFoundError( uuid=managed_object_id) else: raise exception.KeyManagerError(reason=e) def delete(self, context, managed_object_id): """Deletes the specified managed object. :param context: contains information of the user and the environment for the request (castellan/context.py) :param managed_object_id: the UUID of the object to delete :raises KeyManagerError: if object deletion fails :raises ManagedObjectNotFoundError: if the object could not be found """ barbican_client = self._get_barbican_client(context) try: secret_ref = self._create_secret_ref(managed_object_id) barbican_client.secrets.delete(secret_ref) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error("Error deleting object: %s", e) if self._is_secret_not_found_error(e): raise exception.ManagedObjectNotFoundError( uuid=managed_object_id) else: raise exception.KeyManagerError(reason=e) def list(self, context, object_type=None, metadata_only=False): """Retrieves a list of managed objects that match the criteria. If no search criteria is given, all objects are returned. :param context: contains information of the user and the environment for the request (castellan/context.py) :param object_type: the type of object to retrieve :param metadata_only: whether secret data should be included :raises KeyManagerError: if listing secrets fails """ objects = [] barbican_client = self._get_barbican_client(context) if object_type and object_type not in self._secret_type_dict: msg = _("Invalid secret type: %s") % object_type LOG.error(msg) raise exception.KeyManagerError(reason=msg) secret_type = self._secret_type_dict.get(object_type) try: secrets = barbican_client.secrets.list(secret_type=secret_type) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error("Error listing objects: %s", e) raise exception.KeyManagerError(reason=e) for secret in secrets: try: obj = self._get_castellan_object(secret, metadata_only) objects.append(obj) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.warning("Error occurred while retrieving object " "metadata, not adding it to the list: %s", e) return objects def list_options_for_discovery(self): return [(_BARBICAN_OPT_GROUP, _barbican_opts)] castellan-3.0.1/castellan/key_manager/key_manager.py0000664000175000017500000001353213645116250022576 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Key manager API """ import abc from castellan.common.objects import opaque_data as op_data from castellan.common.objects import passphrase from castellan.common.objects import private_key as pri_key from castellan.common.objects import public_key as pub_key from castellan.common.objects import symmetric_key as sym_key from castellan.common.objects import x_509 class KeyManager(object, metaclass=abc.ABCMeta): """Base Key Manager Interface A Key Manager is responsible for managing encryption keys for volumes. A Key Manager is responsible for creating, reading, and deleting keys. """ _secret_type_dict = { op_data.OpaqueData: "opaque", passphrase.Passphrase: "passphrase", pri_key.PrivateKey: "private", pub_key.PublicKey: "public", sym_key.SymmetricKey: "symmetric", x_509.X509: "certificate"} @abc.abstractmethod def __init__(self, configuration): """Instantiate a KeyManager object. Creates a KeyManager object with implementation specific details obtained from the supplied configuration. """ pass @abc.abstractmethod def create_key(self, context, algorithm, length, expiration=None, name=None): """Creates a symmetric key. This method creates a symmetric key and returns the key's UUID. If the specified context does not permit the creation of keys, then a NotAuthorized exception should be raised. """ pass @abc.abstractmethod def create_key_pair(self, context, algorithm, length, expiration=None, name=None): """Creates an asymmetric key pair. This method creates an asymmetric key pair and returns the pair of key UUIDs. If the specified context does not permit the creation of keys, then a NotAuthorized exception should be raised. The order of the UUIDs will be (private, public). """ pass @abc.abstractmethod def store(self, context, managed_object, expiration=None): """Stores a managed object with the key manager. This method stores the specified managed object and returns its UUID that identifies it within the key manager. If the specified context does not permit the creation of keys, then a NotAuthorized exception should be raised. """ pass @abc.abstractmethod def get(self, context, managed_object_id, metadata_only=False): """Retrieves the specified managed object. Implementations should verify that the caller has permissions to retrieve the managed object by checking the context object passed in as context. If the user lacks permission then a NotAuthorized exception is raised. If the caller requests only metadata, then the object that is returned will contain only the secret metadata and no secret bytes. If the specified object does not exist, then a KeyError should be raised. Implementations should preclude users from discerning the UUIDs of objects that belong to other users by repeatedly calling this method. That is, objects that belong to other users should be considered "non-existent" and completely invisible. """ pass @abc.abstractmethod def delete(self, context, managed_object_id): """Deletes the specified managed object. Implementations should verify that the caller has permission to delete the managed object by checking the context object (context). A NotAuthorized exception should be raised if the caller lacks permission. If the specified object does not exist, then a KeyError should be raised. Implementations should preclude users from discerning the UUIDs of objects that belong to other users by repeatedly calling this method. That is, objects that belong to other users should be considered "non-existent" and completely invisible. """ pass def list(self, context, object_type=None, metadata_only=False): """Lists the managed objects given the criteria. Implementations should verify that the caller has permission to list the managed objects and should only list the objects the caller has access to by checking the context object (context). A NotAuthorized exception should be raised if the caller lacks permission. A list of managed objects or managed object metadata should be returned, depending on the metadata_only flag. If no objects are found, an empty list should be returned instead. """ return [] def list_options_for_discovery(self): """Lists the KeyManager's configure options. KeyManagers should advertise all supported options through this method for the purpose of sample generation by oslo-config-generator. Each item in the advertised list should be tuple composed by the group name and a list of options belonging to that group. None should be used as the group name for the DEFAULT group. :returns: the list of supported options of a KeyManager. """ return [] castellan-3.0.1/castellan/key_manager/__init__.py0000664000175000017500000000410113645116250022043 0ustar zuulzuul00000000000000# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from castellan.key_manager import migration from oslo_config import cfg from oslo_log import log as logging from oslo_utils import importutils from stevedore import driver from stevedore import exception LOG = logging.getLogger(__name__) key_manager_opts = [ cfg.StrOpt('backend', default='barbican', deprecated_name='api_class', deprecated_group='key_manager', help='Specify the key manager implementation. Options are ' '"barbican" and "vault". Default is "barbican". Will ' 'support the values earlier set using ' '[key_manager]/api_class for some time.'), ] def API(configuration=None): conf = configuration or cfg.CONF conf.register_opts(key_manager_opts, group='key_manager') try: mgr = driver.DriverManager("castellan.drivers", conf.key_manager.backend, invoke_on_load=True, invoke_args=[conf]) key_mgr = mgr.driver except exception.NoMatches: LOG.warning("Deprecation Warning : %s is not a stevedore based driver," " trying to load it as a class", conf.key_manager.backend) cls = importutils.import_class(conf.key_manager.backend) key_mgr = cls(configuration=conf) return migration.handle_migration(conf, key_mgr) castellan-3.0.1/castellan/__init__.py0000664000175000017500000000120113645116250017557 0ustar zuulzuul00000000000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version __version__ = pbr.version.VersionInfo( 'castellan').version_string() castellan-3.0.1/README.rst0000664000175000017500000000111013645116250015166 0ustar zuulzuul00000000000000========= Castellan ========= Generic Key Manager interface for OpenStack. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/castellan/latest * Source: https://opendev.org/openstack/castellan * Bugs: https://bugs.launchpad.net/castellan * Release notes: https://docs.openstack.org/releasenotes/castellan * Wiki: https://wiki.openstack.org/wiki/Castellan Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/castellan.svg :target: https://governance.openstack.org/tc/reference/tags/index.html castellan-3.0.1/doc/0000775000175000017500000000000013645116331014253 5ustar zuulzuul00000000000000castellan-3.0.1/doc/source/0000775000175000017500000000000013645116331015553 5ustar zuulzuul00000000000000castellan-3.0.1/doc/source/index.rst0000664000175000017500000000073313645116250017417 0ustar zuulzuul00000000000000.. castellan documentation master file, created by sphinx-quickstart on Tue Jul 9 22:26:36 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. include:: ../../README.rst Contents ======== .. toctree:: :maxdepth: 2 install/index user/index contributor/index .. only:: html Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` castellan-3.0.1/doc/source/install/0000775000175000017500000000000013645116331017221 5ustar zuulzuul00000000000000castellan-3.0.1/doc/source/install/index.rst0000664000175000017500000000030413645116250021057 0ustar zuulzuul00000000000000============ Installation ============ At the command line:: $ pip install castellan Or, if you have virtualenvwrapper installed:: $ mkvirtualenv castellan $ pip install castellan castellan-3.0.1/doc/source/_extra/0000775000175000017500000000000013645116331017035 5ustar zuulzuul00000000000000castellan-3.0.1/doc/source/_extra/.htaccess0000664000175000017500000000034113645116250020631 0ustar zuulzuul00000000000000redirectmatch 301 ^/castellan/([^/]+)/usage.html$ ^/user/index.html redirectmatch 301 ^/castellan/([^/]+)/testing.html$ ^/contributor/testing.html redirectmatch 301 ^/castellan/([^/]+)/installation.html$ ^/install/index.html castellan-3.0.1/doc/source/user/0000775000175000017500000000000013645116331016531 5ustar zuulzuul00000000000000castellan-3.0.1/doc/source/user/index.rst0000664000175000017500000002521413645116250020376 0ustar zuulzuul00000000000000===== Usage ===== This document describes some of the common usage patterns for Castellan. When incorporating this package into your applications, care should be taken to consider the key manager behavior you wish to encapsulate and the OpenStack deployments on which your application will run. Authentication ~~~~~~~~~~~~~~ A fundamental concept to using Castellan is the credential context object. Castellan supports the following credentials for authentication: * Token * Password * Keystone Token * Keystone Password In order to use these credentials, valid configuration parameters must be provided. .. code:: ini # token credential # token variable not required, token can be obtained from context [key_manager] auth_type = 'token' token = '5b4de0bb77064f289f7cc58e33bea8c7' # password credential [key_manager] auth_type = 'password' username = 'admin' password = 'passw0rd1' # keystone token credential [key_manager] auth_url = 'http://192.169.5.254:5000' auth_type = 'keystone_token' token = '5b4de0bb77064f289f7cc58e33bea8c7' project_id = 'a1e19934af81420d980a5d02b4afe9fb' # keystone password credential [key_manager] auth_url = 'http://192.169.5.254:5000' auth_type = 'keystone_password' username = 'admin' password = 'passw0rd1' project_id = '1099302ec608486f9879ba2466c60720' user_domain_name = 'default' .. note:: Keystone Token and Password authentication is achieved using keystoneauth1.identity Token and Password auth plugins. There are a variety of different variables which can be set for the keystone credential options. The configuration must be passed to a credential factory which will generate the appropriate context. .. code:: python from castellan.common import utils CONF = cfg.CONF CONF(default_config_files=['~/castellan.conf']) context = utils.credential_factory(conf=CONF) Now you can go ahead and pass the context and use it for authentication. .. note:: There is a special case for a token. Since a user may not want to store a token in the configuration, the user can pass a context object containing an 'auth_token' as well as a configuration file with 'token' as the auth type. An oslo context object can also be used for authentication, it is frequently inherited from ``oslo.context.RequestContext``. This object represents information that is contained in the current request, and is usually populated in the WSGI pipeline. The information contained in this object will be used by Castellan to interact with the specific key manager that is being abstracted. **Example. Creating RequestContext from Keystone Client** .. code:: python from keystoneauth1 import identity from keystoneauth1 import session from oslo_context import context username = 'admin' password = 'openstack' project_name = 'admin' auth_url = 'http://localhost/identity/v3' auth = identity.Password(auth_url=auth_url, username=username, password=password, project_name=project_name, default_domain_id='default') sess = session.Session() ctxt = context.RequestContext(auth_token=auth.get_token(sess), tenant=auth.get_project_id(sess)) ctxt can then be passed into any key_manager api call. Basic usage ~~~~~~~~~~~ Castellan works on the principle of providing an abstracted key manager based on your configuration. In this manner, several different management services can be supported through a single interface. In addition to the key manager, Castellan also provides primitives for various types of secrets (for example, asymmetric keys, simple passphrases, and certificates). These primitives are used in conjunction with the key manager to create, store, retrieve, and destroy managed secrets. **Example. Creating and storing a key.** .. code:: python import myapp from castellan.common.objects import passphrase from castellan import key_manager key = passphrase.Passphrase('super_secret_password') manager = key_manager.API() stored_key_id = manager.store(myapp.context(), key) To begin with, we'd like to create a key to manage. We create a simple passphrase key, then instantiate the key manager, and finally store it to the manager service. We record the key identifier for later usage. **Example. Retrieving a key and checking the contents.** .. code:: python import myapp from castellan import key_manager manager = key_manager.API() key = manager.get(myapp.context(), stored_key_id) if key.get_encoded() == 'super_secret_password': myapp.do_secret_stuff() This example demonstrates retrieving a stored key from the key manager service and checking its contents. First we instantiate the key manager, then retrieve the key using a previously stored identifier, and finally we check the validity of key before performing our restricted actions. **Example. Deleting a key.** .. code:: python import myapp from castellan import key_manager manager = key_manager.API() manager.delete(myapp.context(), stored_key_id) Having finished our work with the key, we can now delete it from the key manager service. We once again instantiate a key manager, then we simply delete the key by using its identifier. Under normal conditions, this call will not return anything but may raise exceptions if there are communication, identification, or authorization issues. Configuring castellan ~~~~~~~~~~~~~~~~~~~~~ Castellan contains several options which control the key management service usage and the configuration of that service. It also contains functions to help configure the defaults and produce listings for use with the ``oslo-config-generator`` application. In general, castellan configuration is handled by passing an ``oslo_config.cfg.ConfigOpts`` object into the ``castellan.key_manager.API`` call when creating your key manager. By default, when no ``ConfigOpts`` object is provided, the key manager will use the global ``oslo_config.cfg.CONF`` object. **Example. Using the global CONF object for configuration.** .. code:: python from castellan import key_manager manager = key_manager.API() **Example. Using a predetermined configuration object.** .. code:: python from oslo_config import cfg from castellan import key_manager conf = cfg.ConfigOpts() manager = key_manager.API(configuration=conf) Controlling default options --------------------------- To change the default behavior of castellan, and the key management service it uses, the ``castellan.options`` module provides the ``set_defaults`` function. This function can be used at run-time to change the behavior of the library or the key management service provider. **Example. Changing the barbican endpoint.** .. code:: python from oslo_config import cfg from castellan import options from castellan import key_manager conf = cfg.ConfigOpts() options.set_defaults(conf, barbican_endpoint='http://192.168.0.1:9311/') manager = key_manager.API(conf) **Example. Changing the key manager provider while using the global configuration.** .. code:: python from oslo_config import cfg from castellan import options from castellan import key_manager options.set_defaults(cfg.CONF, api_class='some.other.KeyManager') manager = key_manager.API() Logging from within Castellan ----------------------------- Castellan uses ``oslo_log`` for logging. Log information will be generated if your application has configured the ``oslo_log`` module. If your application does not use ``oslo_log`` then you can enable default logging using ``enable_logging`` in the ``castellan.options`` module. **Example. Enabling default logging.** .. code:: python from castellan import options from castellan import key_manager options.enable_logging() manager = key_manager.API() Generating sample configuration files ------------------------------------- Castellan includes a tox configuration for creating a sample configuration file. This file will contain only the values that will be used by castellan. To produce this file, run the following command from the root of the castellan project directory: .. code:: console $ tox -e genconfig Parsing the configuration files ------------------------------- Castellan does not parse the configuration files by default. When you create the files and occupy them, you still need to manipulate the ``oslo_config.cfg`` object before passing it to the ``castellan.key_manager.API`` object. You can create a list of locations where the configuration files reside. If multiple configuration files are specified, the variables will be used from the most recently parsed file and overwrite any previous variables. In the example below, the configuration file in the ``/etc/castellan`` directory will overwrite the values found in the file in the user's home directory. If a file is not found in one of the specified locations, then a config file not found error will occur. **Example. Parsing the config files.** .. code:: python from oslo_config import cfg from castellan import key_manager conf=cfg.CONF config_files = ['~/castellan.conf', '/etc/castellan/castellan.conf'] conf(default_config_files=config_files) manager = key_manager.API(configuration=conf) There are two options for parsing the Castellan values from a configuration file: - The values can be placed in a separate file. - You can include the values in a configuration file you already use. In order to see all of the default values used by Castellan, generate a sample configuration by referring to the section directly above. Adding castellan to configuration files --------------------------------------- One common task for OpenStack projects is to create project configuration files. Castellan provides a ``list_opts`` function in the ``castellan.options`` module to aid in generating these files when using the ``oslo-config-generator``. This function can be specified in the :file:`setup.cfg` file of your project to inform oslo of the configuration options. *Note, this will use the default values supplied by the castellan package.* **Example. Adding castellan to the oslo.config entry point.** .. code:: ini [entry_points] oslo.config.opts = castellan.config = castellan.options:list_opts The new namespace also needs to be added to your project's oslo-config-generator conf, e.g. `etc/oslo-config-generator/myproject.conf`: .. code:: ini [DEFAULT] output_file = etc/myproject/myproject.conf namespace = castellan.config For more information on the oslo configuration generator, please see https://docs.openstack.org/oslo.config/latest/cli/generator.html castellan-3.0.1/doc/source/contributor/0000775000175000017500000000000013645116331020125 5ustar zuulzuul00000000000000castellan-3.0.1/doc/source/contributor/index.rst0000664000175000017500000000017113645116250021765 0ustar zuulzuul00000000000000==================== Contributor Document ==================== .. toctree:: :maxdepth: 2 contributing testing castellan-3.0.1/doc/source/contributor/contributing.rst0000664000175000017500000000004713645116250023367 0ustar zuulzuul00000000000000.. include:: ../../../CONTRIBUTING.rst castellan-3.0.1/doc/source/contributor/testing.rst0000664000175000017500000000665413645116250022347 0ustar zuulzuul00000000000000======= Testing ======= Every Castellan code submission is automatically tested against a number of gating jobs to prevent regressions. Castellan developers should have a habit of running tests locally to ensure the code works as intended before submission. For your convenience we provide the ability to run all tests through the ``tox`` utility. If you are unfamiliar with tox please see refer to the `tox documentation`_ for assistance. .. _`tox documentation`: https://tox.readthedocs.org/en/latest/ Unit Tests ---------- Currently, we provide tox environments for a variety of different Python versions. By default all available test environments within the tox configuration will execute when calling ``tox``. If you want to run an independent version, you can do so with the following command: .. code-block:: bash # Executes tests on Python 2.7 $ tox -e py27 .. note:: Other available environments are py35 and pep8. If you do not have the appropriate Python versions available, consider setting up PyEnv to install multiple versions of Python. See the documentation regarding `Setting up a Barbican development environment`_ for more information. Functional Tests ---------------- Unlike running unit tests, the functional tests require Barbican and Keystone services to be running in order to execute. For more information on this please see `Setting up a Barbican development environment`_ and `Using Keystone Middleware with Barbican`_ .. _`Setting up a Barbican development environment`: https://docs.openstack.org/barbican/latest/contributor/dev.html .. _`Using Keystone Middleware with Barbican`: https://docs.openstack.org/barbican/latest/configuration/keystone.html Castellan uses either ``/etc/castellan/castellan-functional.conf`` or ``./etc/castellan/castellan-functional.conf`` in order to run functional tests. A sample file can be generated by running: .. code-block:: bash # Generate a sample configuration file $ tox -e genconfig ``castellan/etc/castellan/castellan-functional.conf.sample`` is generated. It must be renamed to ``castellan-functional.conf`` and placed in ``/etc/castellan`` or ``./etc/castellan``. The file should look something like the following: .. code-block:: bash [DEFAULT] [identity] username = 'admin' password = 'openstack' project_name = 'admin' auth_url = 'http://localhost/identity/v3' Once you have the appropriate services running and configured you can execute the functional tests through tox. .. code-block:: bash # Execute Barbican Functional Tests $ tox -e functional By default, the functional tox job will use ``testr`` to execute the functional tests. Debugging --------- In order to be able to debug code in Castellan, you must use the Python Debugger. This can be done by adding ``import pdb; pdb.set_trace()`` to set the breakpoint. Then run the following command to hit the breakpoint: .. code-block:: bash # hit the pdb breakpoint $ tox -e debug Once in the Python Debugger, you can use the commands as stated in the `Debugger Commands` section here: https://docs.python.org/2/library/pdb.html Pep8 Check ---------- Pep8 is a style guide for Python code. Castellan code should be have proper style before submission. In order to ensure that pep8 tests can be run through tox as follows: .. code-block:: bash # Checks python code style $ tox -e pep8 Any comments on bad coding style will output to the terminal. castellan-3.0.1/doc/source/conf.py0000775000175000017500000000570613645116250017065 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', #'sphinx.ext.intersphinx', 'openstackdocstheme', 'sphinxcontrib.rsvgconverter', ] # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'castellan' copyright = u'2013, OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_static_path = ['static'] html_theme = 'openstackdocs' # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' html_last_updated_fmt = '%Y-%m-%d %H:%M' # Add any paths that contain "extra" files, such as .htaccess or # robots.txt. html_extra_path = ['_extra'] # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', 'doc-castellan.tex', u'%s Documentation' % project, u'OpenStack Foundation', 'manual'), ] latex_elements = { 'extraclassoptions': 'openany,oneside', } latex_use_xindy = False # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'https://docs.python.org/3/': None} # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/castellan' bug_project = 'castellan' bug_tag = '' castellan-3.0.1/doc/requirements.txt0000664000175000017500000000021213645116250017532 0ustar zuulzuul00000000000000sphinx>=1.8.0,!=2.1.0 # BSD sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD reno>=2.5.0 # Apache-2.0 openstackdocstheme>=1.18.1 # Apache-2.0 castellan-3.0.1/requirements.txt0000664000175000017500000000114213645116250016770 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 Babel!=2.4.0,>=2.3.4 # BSD cryptography>=2.1 # BSD/Apache-2.0 python-barbicanclient>=4.5.2 # Apache-2.0 oslo.config>=6.4.0 # Apache-2.0 oslo.context>=2.19.2 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 requests>=2.18.0,!=2.20.0 # Apache-2.0 castellan-3.0.1/etc/0000775000175000017500000000000013645116331014261 5ustar zuulzuul00000000000000castellan-3.0.1/etc/castellan/0000775000175000017500000000000013645116331016227 5ustar zuulzuul00000000000000castellan-3.0.1/etc/castellan/sample-config-generator.conf0000664000175000017500000000015113645116250023603 0ustar zuulzuul00000000000000[DEFAULT] output_file = etc/castellan/castellan.conf.sample wrap_width = 79 namespace = castellan.config castellan-3.0.1/etc/castellan/functional-config-generator.conf0000664000175000017500000000020513645116250024464 0ustar zuulzuul00000000000000[DEFAULT] output_file = etc/castellan/castellan-functional.conf.sample wrap_width = 79 namespace = castellan.tests.functional.config castellan-3.0.1/.zuul.yaml0000664000175000017500000000400013645116250015441 0ustar zuulzuul00000000000000- job: name: castellan-functional-vault parent: openstack-tox-py27 description: | Run tox functional-vault target required-projects: - name: openstack/castellan vars: tox_envlist: functional-vault - job: name: castellan-functional-devstack parent: devstack description: | Run DevStack-based Castellan functional tests pre-run: playbooks/devstack/pre.yaml run: playbooks/devstack/run.yaml post-run: playbooks/devstack/post.yaml required-projects: - name: openstack/castellan - name: openstack/barbican - name: openstack/python-barbicanclient roles: - zuul: openstack-infra/devstack timeout: 9000 vars: devstack_services: # is there a way to disable all services? I only want barbican ceilometer-acentral: false ceilometer-acompute: false ceilometer-alarm-evaluator: false ceilometer-alarm-notifier: false ceilometer-anotification: false ceilometer-api: false ceilometer-collector: false horizon: false s-account: false s-container: false s-object: false s-proxy: false devstack_plugins: barbican: https://opendev.org/openstack/barbican tox_environment: PYTHONUNBUFFERED: 'true' tox_install_siblings: false # I don't know what this means tox_envlist: functional zuul_work_dir: src/opendev.org/openstack/castellan - project: check: jobs: - castellan-functional-vault - castellan-functional-devstack - barbican-simple-crypto-devstack-tempest-castellan-from-git gate: jobs: - castellan-functional-vault - castellan-functional-devstack - barbican-simple-crypto-devstack-tempest-castellan-from-git templates: - check-requirements - openstack-lower-constraints-jobs - openstack-python3-ussuri-jobs - periodic-stable-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 castellan-3.0.1/setup.py0000664000175000017500000000200613645116250015216 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) castellan-3.0.1/HACKING.rst0000664000175000017500000000355513645116250015314 0ustar zuulzuul00000000000000Castellan Style Commandments =============================================== - Step 1: Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ - Step 2: Read on Castellan Specific Commandments ------------------------------- N/A Creating Unit Tests ------------------- For every new feature, unit tests should be created that both test and (implicitly) document the usage of said feature. If submitting a patch for a bug that had no unit test, a new passing unit test should be added. If a submitted bug fix does have a unit test, be sure to add a new one that fails without the patch and passes with the patch. Running Tests ------------- The testing system is based on a combination of tox and testr. The canonical approach to running tests is to simply run the command ``tox``. This will create virtual environments, populate them with dependencies and run all of the tests that OpenStack CI systems run. Behind the scenes, tox is running ``testr run --parallel``, but is set up such that you can supply any additional testr arguments that are needed to tox. For example, you can run: ``tox -- --analyze-isolation`` to cause tox to tell testr to add --analyze-isolation to its argument list. Python packages may also have dependencies that are outside of tox's ability to install. Please refer to doc/source/devref/development.environment.rst for a list of those packages on Ubuntu, Fedora and Mac OS X. It is also possible to run the tests inside of a virtual environment you have created, or it is possible that you have all of the dependencies installed locally already. In this case, you can interact with the testr command directly. Running ``testr run`` will run the entire test suite. ``testr run --parallel`` will run it in parallel (this is the default incantation tox uses.) More information about testr can be found at: https://wiki.openstack.org/testr