hvac-0.5.0/0000755000372000037200000000000013243073561013301 5ustar travistravis00000000000000hvac-0.5.0/hvac/0000755000372000037200000000000013243073561014222 5ustar travistravis00000000000000hvac-0.5.0/hvac/tests/0000755000372000037200000000000013243073561015364 5ustar travistravis00000000000000hvac-0.5.0/hvac/tests/__init__.py0000644000372000037200000000000013243073521017457 0ustar travistravis00000000000000hvac-0.5.0/hvac/tests/test_integration.py0000644000372000037200000010102313243073521021311 0ustar travistravis00000000000000from unittest import TestCase, skipIf import hcl import requests from nose.tools import * from time import sleep from hvac import Client, exceptions from hvac.tests import util def create_client(**kwargs): return Client(url='https://localhost:8200', cert=('test/client-cert.pem', 'test/client-key.pem'), verify='test/server-cert.pem', **kwargs) class IntegrationTest(TestCase): @classmethod def setUpClass(cls): cls.manager = util.ServerManager(config_path='test/vault-tls.hcl', client=create_client()) cls.manager.start() cls.manager.initialize() cls.manager.unseal() @classmethod def tearDownClass(cls): cls.manager.stop() def root_token(self): cls = type(self) return cls.manager.root_token def setUp(self): self.client = create_client(token=self.root_token()) def test_unseal_multi(self): cls = type(self) self.client.seal() keys = cls.manager.keys result = self.client.unseal_multi(keys[0:2]) assert result['sealed'] assert result['progress'] == 2 result = self.client.unseal_reset() assert result['progress'] == 0 result = self.client.unseal_multi(keys[1:3]) assert result['sealed'] assert result['progress'] == 2 result = self.client.unseal_multi(keys[0:1]) result = self.client.unseal_multi(keys[2:3]) assert not result['sealed'] def test_seal_unseal(self): cls = type(self) assert not self.client.is_sealed() self.client.seal() assert self.client.is_sealed() cls.manager.unseal() assert not self.client.is_sealed() def test_ha_status(self): assert 'ha_enabled' in self.client.ha_status def test_generic_secret_backend(self): self.client.write('secret/foo', zap='zip') result = self.client.read('secret/foo') assert result['data']['zap'] == 'zip' self.client.delete('secret/foo') def test_list_directory(self): self.client.write('secret/test-list/bar/foo', value='bar') self.client.write('secret/test-list/foo', value='bar') result = self.client.list('secret/test-list') assert result['data']['keys'] == ['bar/', 'foo'] self.client.delete('secret/test-list/bar/foo') self.client.delete('secret/test-list/foo') def test_write_with_response(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') plaintext = 'test' self.client.write('transit/keys/foo') result = self.client.write('transit/encrypt/foo', plaintext=plaintext) ciphertext = result['data']['ciphertext'] result = self.client.write('transit/decrypt/foo', ciphertext=ciphertext) assert result['data']['plaintext'] == plaintext def test_wrap_write(self): if 'approle/' not in self.client.list_auth_backends(): self.client.enable_auth_backend("approle") self.client.write("auth/approle/role/testrole") result = self.client.write('auth/approle/role/testrole/secret-id', wrap_ttl="10s") assert 'token' in result['wrap_info'] self.client.unwrap(result['wrap_info']['token']) self.client.disable_auth_backend("approle") def test_read_nonexistent_key(self): assert not self.client.read('secret/I/dont/exist') def test_auth_backend_manipulation(self): assert 'github/' not in self.client.list_auth_backends() self.client.enable_auth_backend('github') assert 'github/' in self.client.list_auth_backends() self.client.token = self.root_token() self.client.disable_auth_backend('github') assert 'github/' not in self.client.list_auth_backends() def test_secret_backend_manipulation(self): assert 'test/' not in self.client.list_secret_backends() self.client.enable_secret_backend('generic', mount_point='test') assert 'test/' in self.client.list_secret_backends() self.client.tune_secret_backend('generic', mount_point='test', default_lease_ttl='3600s', max_lease_ttl='8600s') assert 'max_lease_ttl' in self.client.get_secret_backend_tuning('generic', mount_point='test') assert 'default_lease_ttl' in self.client.get_secret_backend_tuning('generic', mount_point='test') self.client.remount_secret_backend('test', 'foobar') assert 'test/' not in self.client.list_secret_backends() assert 'foobar/' in self.client.list_secret_backends() self.client.token = self.root_token() self.client.disable_secret_backend('foobar') assert 'foobar/' not in self.client.list_secret_backends() def test_audit_backend_manipulation(self): assert 'tmpfile/' not in self.client.list_audit_backends() options = { 'path': '/tmp/vault.audit.log' } self.client.enable_audit_backend('file', options=options, name='tmpfile') assert 'tmpfile/' in self.client.list_audit_backends() self.client.token = self.root_token() self.client.disable_audit_backend('tmpfile') assert 'tmpfile/' not in self.client.list_audit_backends() def prep_policy(self, name): text = """ path "sys" { policy = "deny" } path "secret" { policy = "write" } """ obj = { 'path': { 'sys': { 'policy': 'deny'}, 'secret': { 'policy': 'write'} } } self.client.set_policy(name, text) return text, obj def test_policy_manipulation(self): assert 'root' in self.client.list_policies() assert self.client.get_policy('test') is None policy, parsed_policy = self.prep_policy('test') assert 'test' in self.client.list_policies() assert policy == self.client.get_policy('test') assert parsed_policy == self.client.get_policy('test', parse=True) self.client.delete_policy('test') assert 'test' not in self.client.list_policies() def test_json_policy_manipulation(self): assert 'root' in self.client.list_policies() policy = { "path": { "sys": { "policy": "deny" }, "secret": { "policy": "write" } } } self.client.set_policy('test', policy) assert 'test' in self.client.list_policies() self.client.delete_policy('test') assert 'test' not in self.client.list_policies() def test_auth_token_manipulation(self): result = self.client.create_token(lease='1h', renewable=True) assert result['auth']['client_token'] lookup = self.client.lookup_token(result['auth']['client_token']) assert result['auth']['client_token'] == lookup['data']['id'] renew = self.client.renew_token(lookup['data']['id']) assert result['auth']['client_token'] == renew['auth']['client_token'] self.client.revoke_token(lookup['data']['id']) try: lookup = self.client.lookup_token(result['auth']['client_token']) assert False except exceptions.Forbidden: assert True except exceptions.InvalidPath: assert True except exceptions.InvalidRequest: assert True def test_userpass_auth(self): if 'userpass/' in self.client.list_auth_backends(): self.client.disable_auth_backend('userpass') self.client.enable_auth_backend('userpass') self.client.write('auth/userpass/users/testuser', password='testpass', policies='not_root') result = self.client.auth_userpass('testuser', 'testpass') assert self.client.token == result['auth']['client_token'] assert self.client.is_authenticated() self.client.token = self.root_token() self.client.disable_auth_backend('userpass') def test_create_userpass(self): if 'userpass/' not in self.client.list_auth_backends(): self.client.enable_auth_backend('userpass') self.client.create_userpass('testcreateuser', 'testcreateuserpass', policies='not_root') result = self.client.auth_userpass('testcreateuser', 'testcreateuserpass') assert self.client.token == result['auth']['client_token'] assert self.client.is_authenticated() # Test ttl: self.client.token = self.root_token() self.client.create_userpass('testcreateuser', 'testcreateuserpass', policies='not_root', ttl='10s') self.client.token = result['auth']['client_token'] result = self.client.auth_userpass('testcreateuser', 'testcreateuserpass') assert result['auth']['lease_duration'] == 10 self.client.token = self.root_token() self.client.disable_auth_backend('userpass') def test_delete_userpass(self): if 'userpass/' not in self.client.list_auth_backends(): self.client.enable_auth_backend('userpass') self.client.create_userpass('testcreateuser', 'testcreateuserpass', policies='not_root') result = self.client.auth_userpass('testcreateuser', 'testcreateuserpass') assert self.client.token == result['auth']['client_token'] assert self.client.is_authenticated() self.client.token = self.root_token() self.client.delete_userpass('testcreateuser') assert_raises(exceptions.InvalidRequest, self.client.auth_userpass, 'testcreateuser', 'testcreateuserpass') def test_app_id_auth(self): if 'app-id/' in self.client.list_auth_backends(): self.client.disable_auth_backend('app-id') self.client.enable_auth_backend('app-id') self.client.write('auth/app-id/map/app-id/foo', value='not_root') self.client.write('auth/app-id/map/user-id/bar', value='foo') result = self.client.auth_app_id('foo', 'bar') assert self.client.token == result['auth']['client_token'] assert self.client.is_authenticated() self.client.token = self.root_token() self.client.disable_auth_backend('app-id') def test_create_app_id(self): if 'app-id/' not in self.client.list_auth_backends(): self.client.enable_auth_backend('app-id') self.client.create_app_id('testappid', policies='not_root', display_name='displayname') result = self.client.read('auth/app-id/map/app-id/testappid') lib_result = self.client.get_app_id('testappid') del result['request_id'] del lib_result['request_id'] assert result == lib_result assert result['data']['key'] == 'testappid' assert result['data']['display_name'] == 'displayname' assert result['data']['value'] == 'not_root' self.client.delete_app_id('testappid') assert self.client.get_app_id('testappid')['data'] is None self.client.token = self.root_token() self.client.disable_auth_backend('app-id') def test_create_user_id(self): if 'app-id/' not in self.client.list_auth_backends(): self.client.enable_auth_backend('app-id') self.client.create_app_id('testappid', policies='not_root', display_name='displayname') self.client.create_user_id('testuserid', app_id='testappid') result = self.client.read('auth/app-id/map/user-id/testuserid') lib_result = self.client.get_user_id('testuserid') del result['request_id'] del lib_result['request_id'] assert result == lib_result assert result['data']['key'] == 'testuserid' assert result['data']['value'] == 'testappid' result = self.client.auth_app_id('testappid', 'testuserid') assert self.client.token == result['auth']['client_token'] assert self.client.is_authenticated() self.client.token = self.root_token() self.client.delete_user_id('testuserid') assert self.client.get_user_id('testuserid')['data'] is None self.client.token = self.root_token() self.client.disable_auth_backend('app-id') def test_create_role(self): if 'approle/' in self.client.list_auth_backends(): self.client.disable_auth_backend('approle') self.client.enable_auth_backend('approle') self.client.create_role('testrole') result = self.client.read('auth/approle/role/testrole') lib_result = self.client.get_role('testrole') del result['request_id'] del lib_result['request_id'] assert result == lib_result self.client.token = self.root_token() self.client.disable_auth_backend('approle') def test_create_delete_role_secret_id(self): if 'approle/' in self.client.list_auth_backends(): self.client.disable_auth_backend('approle') self.client.enable_auth_backend('approle') self.client.create_role('testrole') create_result = self.client.create_role_secret_id('testrole', {'foo': 'bar'}) secret_id = create_result['data']['secret_id'] result = self.client.get_role_secret_id('testrole', secret_id) assert result['data']['metadata']['foo'] == 'bar' self.client.delete_role_secret_id('testrole', secret_id) try: self.client.get_role_secret_id('testrole', secret_id) assert False except (exceptions.InvalidPath, ValueError): assert True self.client.token = self.root_token() self.client.disable_auth_backend('approle') def test_auth_approle(self): if 'approle/' in self.client.list_auth_backends(): self.client.disable_auth_backend('approle') self.client.enable_auth_backend('approle') self.client.create_role('testrole') create_result = self.client.create_role_secret_id('testrole', {'foo': 'bar'}) secret_id = create_result['data']['secret_id'] role_id = self.client.get_role_id('testrole') result = self.client.auth_approle(role_id, secret_id) assert result['auth']['metadata']['foo'] == 'bar' assert self.client.token == result['auth']['client_token'] assert self.client.is_authenticated() self.client.token = self.root_token() self.client.disable_auth_backend('approle') def test_auth_approle_dont_use_token(self): if 'approle/' in self.client.list_auth_backends(): self.client.disable_auth_backend('approle') self.client.enable_auth_backend('approle') self.client.create_role('testrole') create_result = self.client.create_role_secret_id('testrole', {'foo':'bar'}) secret_id = create_result['data']['secret_id'] role_id = self.client.get_role_id('testrole') result = self.client.auth_approle(role_id, secret_id, use_token=False) assert result['auth']['metadata']['foo'] == 'bar' assert self.client.token != result['auth']['client_token'] self.client.token = self.root_token() self.client.disable_auth_backend('approle') def test_transit_read_write(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') self.client.transit_create_key('foo') result = self.client.transit_read_key('foo') assert not result['data']['exportable'] self.client.transit_create_key('foo_export', exportable=True, key_type="ed25519") result = self.client.transit_read_key('foo_export') assert result['data']['exportable'] assert result['data']['type'] == 'ed25519' self.client.enable_secret_backend('transit', mount_point='bar') self.client.transit_create_key('foo', mount_point='bar') result = self.client.transit_read_key('foo', mount_point='bar') assert not result['data']['exportable'] def test_transit_list_keys(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') self.client.transit_create_key('foo1') self.client.transit_create_key('foo2') self.client.transit_create_key('foo3') result = self.client.transit_list_keys() assert result['data']['keys'] == ["foo1", "foo2", "foo3"] def test_transit_update_delete_keys(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') self.client.transit_create_key('foo') self.client.transit_update_key('foo', deletion_allowed=True) result = self.client.transit_read_key('foo') assert result['data']['deletion_allowed'] self.client.transit_delete_key('foo') try: self.client.transit_read_key('foo') except exceptions.InvalidPath: assert True else: assert False def test_transit_rotate_key(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') self.client.transit_create_key('foo') self.client.transit_rotate_key('foo') response = self.client.transit_read_key('foo') assert '2' in response['data']['keys'] self.client.transit_rotate_key('foo') response = self.client.transit_read_key('foo') assert '3' in response['data']['keys'] def test_transit_export_key(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') self.client.transit_create_key('foo', exportable=True) response = self.client.transit_export_key('foo', key_type='encryption-key') assert response is not None def test_transit_encrypt_data(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') self.client.transit_create_key('foo') ciphertext_resp = self.client.transit_encrypt_data('foo', 'abbaabba')['data']['ciphertext'] plaintext_resp = self.client.transit_decrypt_data('foo', ciphertext_resp)['data']['plaintext'] assert plaintext_resp == 'abbaabba' def test_transit_rewrap_data(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') self.client.transit_create_key('foo') ciphertext_resp = self.client.transit_encrypt_data('foo', 'abbaabba')['data']['ciphertext'] self.client.transit_rotate_key('foo') response_wrap = self.client.transit_rewrap_data('foo', ciphertext=ciphertext_resp)['data']['ciphertext'] plaintext_resp = self.client.transit_decrypt_data('foo', response_wrap)['data']['plaintext'] assert plaintext_resp == 'abbaabba' def test_transit_generate_data_key(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') self.client.transit_create_key('foo') response_plaintext = self.client.transit_generate_data_key('foo', key_type='plaintext')['data']['plaintext'] assert response_plaintext response_ciphertext = self.client.transit_generate_data_key('foo', key_type='wrapped')['data'] assert 'ciphertext' in response_ciphertext assert 'plaintext' not in response_ciphertext def test_transit_generate_rand_bytes(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') response_data = self.client.transit_generate_rand_bytes(data_bytes=4)['data']['random_bytes'] assert response_data def test_transit_hash_data(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') response_hash = self.client.transit_hash_data('abbaabba')['data']['sum'] assert len(response_hash) == 64 response_hash = self.client.transit_hash_data('abbaabba', algorithm="sha2-512")['data']['sum'] assert len(response_hash) == 128 def test_transit_generate_verify_hmac(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') self.client.transit_create_key('foo') response_hmac = self.client.transit_generate_hmac('foo', 'abbaabba')['data']['hmac'] assert response_hmac verify_resp = self.client.transit_verify_signed_data('foo', 'abbaabba', hmac=response_hmac)['data']['valid'] assert verify_resp response_hmac = self.client.transit_generate_hmac('foo', 'abbaabba', algorithm='sha2-512')['data']['hmac'] assert response_hmac verify_resp = self.client.transit_verify_signed_data('foo', 'abbaabba', algorithm='sha2-512', hmac=response_hmac)['data']['valid'] assert verify_resp def test_transit_sign_verify_signature_data(self): if 'transit/' in self.client.list_secret_backends(): self.client.disable_secret_backend('transit') self.client.enable_secret_backend('transit') self.client.transit_create_key('foo', key_type='ed25519') signed_resp = self.client.transit_sign_data('foo', 'abbaabba')['data']['signature'] assert signed_resp verify_resp = self.client.transit_verify_signed_data('foo', 'abbaabba', signature=signed_resp)['data']['valid'] assert verify_resp signed_resp = self.client.transit_sign_data('foo', 'abbaabba', algorithm='sha2-512')['data']['signature'] assert signed_resp verify_resp = self.client.transit_verify_signed_data('foo', 'abbaabba', algorithm='sha2-512', signature=signed_resp)['data']['valid'] assert verify_resp def test_missing_token(self): client = create_client() assert not client.is_authenticated() def test_invalid_token(self): client = create_client(token='not-a-real-token') assert not client.is_authenticated() def test_illegal_token(self): client = create_client(token='token-with-new-line\n') try: client.is_authenticated() except ValueError as e: assert 'Invalid header value' in str(e) def test_broken_token(self): client = create_client(token='\x1b') try: client.is_authenticated() except exceptions.InvalidRequest as e: assert "invalid header value" in str(e) def test_client_authenticated(self): assert self.client.is_authenticated() def test_client_logout(self): self.client.logout() assert not self.client.is_authenticated() def test_revoke_self_token(self): if 'userpass/' in self.client.list_auth_backends(): self.client.disable_auth_backend('userpass') self.client.enable_auth_backend('userpass') self.client.write('auth/userpass/users/testuser', password='testpass', policies='not_root') result = self.client.auth_userpass('testuser', 'testpass') self.client.revoke_self_token() assert not self.client.is_authenticated() def test_rekey_multi(self): cls = type(self) assert not self.client.rekey_status['started'] self.client.start_rekey() assert self.client.rekey_status['started'] self.client.cancel_rekey() assert not self.client.rekey_status['started'] result = self.client.start_rekey() keys = cls.manager.keys result = self.client.rekey_multi(keys, nonce=result['nonce']) assert result['complete'] cls.manager.keys = result['keys'] cls.manager.unseal() def test_rotate(self): status = self.client.key_status self.client.rotate() assert self.client.key_status['term'] > status['term'] def test_tls_auth(self): self.client.enable_auth_backend('cert') with open('test/client-cert.pem') as fp: certificate = fp.read() self.client.write('auth/cert/certs/test', display_name='test', policies='not_root', certificate=certificate) result = self.client.auth_tls() def test_gh51(self): key = 'secret/http://test.com' self.client.write(key, foo='bar') result = self.client.read(key) assert result['data']['foo'] == 'bar' def test_token_accessor(self): # Create token, check accessor is provided result = self.client.create_token(lease='1h') token_accessor = result['auth'].get('accessor', None) assert token_accessor # Look up token by accessor, make sure token is excluded from results lookup = self.client.lookup_token(token_accessor, accessor=True) assert lookup['data']['accessor'] == token_accessor assert not lookup['data']['id'] # Revoke token using the accessor self.client.revoke_token(token_accessor, accessor=True) # Look up by accessor should fail with self.assertRaises(exceptions.InvalidRequest): lookup = self.client.lookup_token(token_accessor, accessor=True) # As should regular lookup with self.assertRaises(exceptions.Forbidden): lookup = self.client.lookup_token(result['auth']['client_token']) def test_wrapped_token_success(self): wrap = self.client.create_token(wrap_ttl='1m') # Unwrap token result = self.client.unwrap(wrap['wrap_info']['token']) assert result['auth']['client_token'] # Validate token lookup = self.client.lookup_token(result['auth']['client_token']) assert result['auth']['client_token'] == lookup['data']['id'] def test_wrapped_token_intercept(self): wrap = self.client.create_token(wrap_ttl='1m') # Intercept wrapped token _ = self.client.unwrap(wrap['wrap_info']['token']) # Attempt to retrieve the token after it's been intercepted with self.assertRaises(exceptions.Forbidden): result = self.client.unwrap(wrap['wrap_info']['token']) def test_wrapped_token_cleanup(self): wrap = self.client.create_token(wrap_ttl='1m') _token = self.client.token _ = self.client.unwrap(wrap['wrap_info']['token']) assert self.client.token == _token def test_wrapped_token_revoke(self): wrap = self.client.create_token(wrap_ttl='1m') # Revoke token before it's unwrapped self.client.revoke_token(wrap['wrap_info']['wrapped_accessor'], accessor=True) # Unwrap token anyway result = self.client.unwrap(wrap['wrap_info']['token']) assert result['auth']['client_token'] # Attempt to validate token with self.assertRaises(exceptions.Forbidden): lookup = self.client.lookup_token(result['auth']['client_token']) def test_create_token_explicit_max_ttl(self): token = self.client.create_token(ttl='30m', explicit_max_ttl='5m') assert token['auth']['client_token'] assert token['auth']['lease_duration'] == 300 # Validate token lookup = self.client.lookup_token(token['auth']['client_token']) assert token['auth']['client_token'] == lookup['data']['id'] def test_create_token_max_ttl(self): token = self.client.create_token(ttl='5m') assert token['auth']['client_token'] assert token['auth']['lease_duration'] == 300 # Validate token lookup = self.client.lookup_token(token['auth']['client_token']) assert token['auth']['client_token'] == lookup['data']['id'] def test_create_token_periodic(self): token = self.client.create_token(period='30m') assert token['auth']['client_token'] assert token['auth']['lease_duration'] == 1800 # Validate token lookup = self.client.lookup_token(token['auth']['client_token']) assert token['auth']['client_token'] == lookup['data']['id'] assert lookup['data']['period'] == 1800 def test_token_roles(self): # No roles, list_token_roles == None before = self.client.list_token_roles() assert not before # Create token role assert self.client.create_token_role('testrole').status_code == 204 # List token roles during = self.client.list_token_roles()['data']['keys'] assert len(during) == 1 assert during[0] == 'testrole' # Delete token role self.client.delete_token_role('testrole') # No roles, list_token_roles == None after = self.client.list_token_roles() assert not after def test_create_token_w_role(self): # Create policy self.prep_policy('testpolicy') # Create token role w/ policy assert self.client.create_token_role('testrole', allowed_policies='testpolicy').status_code == 204 # Create token against role token = self.client.create_token(lease='1h', role='testrole') assert token['auth']['client_token'] assert token['auth']['policies'] == ['default', 'testpolicy'] # Cleanup self.client.delete_token_role('testrole') self.client.delete_policy('testpolicy') def test_ec2_role_crud(self): if 'aws-ec2/' in self.client.list_auth_backends(): self.client.disable_auth_backend('aws-ec2') self.client.enable_auth_backend('aws-ec2') # create a policy to associate with the role self.prep_policy('ec2rolepolicy') # attempt to get a list of roles before any exist no_roles = self.client.list_ec2_roles() # doing so should succeed and return None assert (no_roles is None) # test binding by AMI ID (the old way, to ensure backward compatibility) self.client.create_ec2_role('foo', 'ami-notarealami', policies='ec2rolepolicy') # test binding by Account ID self.client.create_ec2_role('bar', bound_account_id='123456789012', policies='ec2rolepolicy') # test binding by IAM Role ARN self.client.create_ec2_role('baz', bound_iam_role_arn='arn:aws:iam::123456789012:role/mockec2role', policies='ec2rolepolicy') # test binding by instance profile ARN self.client.create_ec2_role('qux', bound_iam_instance_profile_arn='arn:aws:iam::123456789012:instance-profile/mockprofile', policies='ec2rolepolicy') roles = self.client.list_ec2_roles() assert ('foo' in roles['data']['keys']) assert ('bar' in roles['data']['keys']) assert ('baz' in roles['data']['keys']) assert ('qux' in roles['data']['keys']) foo_role = self.client.get_ec2_role('foo') assert (foo_role['data']['bound_ami_id'] == 'ami-notarealami') assert ('ec2rolepolicy' in foo_role['data']['policies']) bar_role = self.client.get_ec2_role('bar') assert (bar_role['data']['bound_account_id'] == '123456789012') assert ('ec2rolepolicy' in bar_role['data']['policies']) baz_role = self.client.get_ec2_role('baz') assert (baz_role['data']['bound_iam_role_arn'] == 'arn:aws:iam::123456789012:role/mockec2role') assert ('ec2rolepolicy' in baz_role['data']['policies']) qux_role = self.client.get_ec2_role('qux') assert ( qux_role['data']['bound_iam_instance_profile_arn'] == 'arn:aws:iam::123456789012:instance-profile/mockprofile') assert ('ec2rolepolicy' in qux_role['data']['policies']) # teardown self.client.delete_ec2_role('foo') self.client.delete_ec2_role('bar') self.client.delete_ec2_role('baz') self.client.delete_ec2_role('qux') self.client.delete_policy('ec2rolepolicy') self.client.disable_auth_backend('aws-ec2') hvac-0.5.0/hvac/tests/util.py0000644000372000037200000000311413243073521016706 0ustar travistravis00000000000000import re import subprocess import time from semantic_version import Spec, Version class ServerManager(object): def __init__(self, config_path, client): self.config_path = config_path self.client = client self.keys = None self.root_token = None self._process = None def start(self): command = ['vault', 'server', '-config=' + self.config_path] self._process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) attempts_left = 20 last_exception = None while attempts_left > 0: try: self.client.is_initialized() return except Exception as ex: print('Waiting for Vault to start') time.sleep(0.5) attempts_left -= 1 last_exception = ex raise Exception('Unable to start Vault in background: {0}'.format(last_exception)) def stop(self): self._process.kill() def initialize(self): assert not self.client.is_initialized() result = self.client.initialize() self.root_token = result['root_token'] self.keys = result['keys'] def unseal(self): self.client.unseal_multi(self.keys) VERSION_REGEX = re.compile('Vault v([\d\.]+)') def match_version(spec): output = subprocess.check_output(['vault', 'version']).decode('ascii') version = Version(VERSION_REGEX.match(output).group(1)) return Spec(spec).match(version) hvac-0.5.0/hvac/v1/0000755000372000037200000000000013243073561014550 5ustar travistravis00000000000000hvac-0.5.0/hvac/v1/__init__.py0000644000372000037200000012135113243073521016660 0ustar travistravis00000000000000from __future__ import unicode_literals import json try: import hcl has_hcl_parser = True except ImportError: has_hcl_parser = False import requests from hvac import exceptions try: from urlparse import urljoin except ImportError: from urllib.parse import urljoin class Client(object): def __init__(self, url='http://localhost:8200', token=None, cert=None, verify=True, timeout=30, proxies=None, allow_redirects=True, session=None): if not session: session = requests.Session() self.allow_redirects = allow_redirects self.session = session self.token = token self._url = url self._kwargs = { 'cert': cert, 'verify': verify, 'timeout': timeout, 'proxies': proxies, } def read(self, path, wrap_ttl=None): """ GET / """ try: return self._get('/v1/{0}'.format(path), wrap_ttl=wrap_ttl).json() except exceptions.InvalidPath: return None def list(self, path): """ GET /?list=true """ try: payload = { 'list': True } return self._get('/v1/{}'.format(path), params=payload).json() except exceptions.InvalidPath: return None def write(self, path, wrap_ttl=None, **kwargs): """ PUT / """ response = self._put('/v1/{0}'.format(path), json=kwargs, wrap_ttl=wrap_ttl) if response.status_code == 200: return response.json() def delete(self, path): """ DELETE / """ self._delete('/v1/{0}'.format(path)) def unwrap(self, token): """ GET /cubbyhole/response X-Vault-Token: """ path = "cubbyhole/response" _token = self.token try: self.token = token return json.loads(self.read(path)['data']['response']) finally: self.token = _token def is_initialized(self): """ GET /sys/init """ return self._get('/v1/sys/init').json()['initialized'] def initialize(self, secret_shares=5, secret_threshold=3, pgp_keys=None): """ PUT /sys/init """ params = { 'secret_shares': secret_shares, 'secret_threshold': secret_threshold, } if pgp_keys: if len(pgp_keys) != secret_shares: raise ValueError('Length of pgp_keys must equal secret shares') params['pgp_keys'] = pgp_keys return self._put('/v1/sys/init', json=params).json() @property def seal_status(self): """ GET /sys/seal-status """ return self._get('/v1/sys/seal-status').json() def is_sealed(self): return self.seal_status['sealed'] def seal(self): """ PUT /sys/seal """ self._put('/v1/sys/seal') def unseal_reset(self): """ PUT /sys/unseal """ params = { 'reset': True, } return self._put('/v1/sys/unseal', json=params).json() def unseal(self, key): """ PUT /sys/unseal """ params = { 'key': key, } return self._put('/v1/sys/unseal', json=params).json() def unseal_multi(self, keys): result = None for key in keys: result = self.unseal(key) if not result['sealed']: break return result @property def key_status(self): """ GET /sys/key-status """ return self._get('/v1/sys/key-status').json() def rotate(self): """ PUT /sys/rotate """ self._put('/v1/sys/rotate') @property def rekey_status(self): """ GET /sys/rekey/init """ return self._get('/v1/sys/rekey/init').json() def start_rekey(self, secret_shares=5, secret_threshold=3, pgp_keys=None, backup=False): """ PUT /sys/rekey/init """ params = { 'secret_shares': secret_shares, 'secret_threshold': secret_threshold, } if pgp_keys: if len(pgp_keys) != secret_shares: raise ValueError('Length of pgp_keys must equal secret shares') params['pgp_keys'] = pgp_keys params['backup'] = backup resp = self._put('/v1/sys/rekey/init', json=params) if resp.text: return resp.json() def cancel_rekey(self): """ DELETE /sys/rekey/init """ self._delete('/v1/sys/rekey/init') def rekey(self, key, nonce=None): """ PUT /sys/rekey/update """ params = { 'key': key, } if nonce: params['nonce'] = nonce return self._put('/v1/sys/rekey/update', json=params).json() def rekey_multi(self, keys, nonce=None): result = None for key in keys: result = self.rekey(key, nonce=nonce) if 'complete' in result and result['complete']: break return result def get_backed_up_keys(self): """ GET /sys/rekey/backup """ return self._get('/v1/sys/rekey/backup').json() @property def ha_status(self): """ GET /sys/leader """ return self._get('/v1/sys/leader').json() def renew_secret(self, lease_id, increment=None): """ PUT /sys/leases/renew """ params = { 'lease_id': lease_id, 'increment': increment, } return self._put('/v1/sys/leases/renew', json=params).json() def revoke_secret(self, lease_id): """ PUT /sys/revoke/ """ self._put('/v1/sys/revoke/{0}'.format(lease_id)) def revoke_secret_prefix(self, path_prefix): """ PUT /sys/revoke-prefix/ """ self._put('/v1/sys/revoke-prefix/{0}'.format(path_prefix)) def revoke_self_token(self): """ PUT /auth/token/revoke-self """ self._put('/v1/auth/token/revoke-self') def list_secret_backends(self): """ GET /sys/mounts """ return self._get('/v1/sys/mounts').json() def enable_secret_backend(self, backend_type, description=None, mount_point=None, config=None): """ POST /sys/auth/ """ if not mount_point: mount_point = backend_type params = { 'type': backend_type, 'description': description, 'config': config, } self._post('/v1/sys/mounts/{0}'.format(mount_point), json=params) def tune_secret_backend(self, backend_type, mount_point=None, default_lease_ttl=None, max_lease_ttl=None): """ POST /sys/mounts//tune """ if not mount_point: mount_point = backend_type params = { 'default_lease_ttl': default_lease_ttl, 'max_lease_ttl': max_lease_ttl } self._post('/v1/sys/mounts/{0}/tune'.format(mount_point), json=params) def get_secret_backend_tuning(self, backend_type, mount_point=None): """ GET /sys/mounts//tune """ if not mount_point: mount_point = backend_type return self._get('/v1/sys/mounts/{0}/tune'.format(mount_point)).json() def disable_secret_backend(self, mount_point): """ DELETE /sys/mounts/ """ self._delete('/v1/sys/mounts/{0}'.format(mount_point)) def remount_secret_backend(self, from_mount_point, to_mount_point): """ POST /sys/remount """ params = { 'from': from_mount_point, 'to': to_mount_point, } self._post('/v1/sys/remount', json=params) def list_policies(self): """ GET /sys/policy """ return self._get('/v1/sys/policy').json()['policies'] def get_policy(self, name, parse=False): """ GET /sys/policy/ """ try: policy = self._get('/v1/sys/policy/{0}'.format(name)).json()['rules'] if parse: if not has_hcl_parser: raise ImportError('pyhcl is required for policy parsing') policy = hcl.loads(policy) return policy except exceptions.InvalidPath: return None def set_policy(self, name, rules): """ PUT /sys/policy/ """ if isinstance(rules, dict): rules = json.dumps(rules) params = { 'rules': rules, } self._put('/v1/sys/policy/{0}'.format(name), json=params) def delete_policy(self, name): """ DELETE /sys/policy/ """ self._delete('/v1/sys/policy/{0}'.format(name)) def list_audit_backends(self): """ GET /sys/audit """ return self._get('/v1/sys/audit').json() def enable_audit_backend(self, backend_type, description=None, options=None, name=None): """ POST /sys/audit/ """ if not name: name = backend_type params = { 'type': backend_type, 'description': description, 'options': options, } self._post('/v1/sys/audit/{0}'.format(name), json=params) def disable_audit_backend(self, name): """ DELETE /sys/audit/ """ self._delete('/v1/sys/audit/{0}'.format(name)) def audit_hash(self, name, input): """ POST /sys/audit-hash """ params = { 'input': input, } return self._post('/v1/sys/audit-hash/{0}'.format(name), json=params).json() def create_token(self, role=None, token_id=None, policies=None, meta=None, no_parent=False, lease=None, display_name=None, num_uses=None, no_default_policy=False, ttl=None, orphan=False, wrap_ttl=None, renewable=None, explicit_max_ttl=None, period=None): """ POST /auth/token/create POST /auth/token/create/ POST /auth/token/create-orphan """ params = { 'id': token_id, 'policies': policies, 'meta': meta, 'no_parent': no_parent, 'display_name': display_name, 'num_uses': num_uses, 'no_default_policy': no_default_policy, 'renewable': renewable } if lease: params['lease'] = lease else: params['ttl'] = ttl params['explicit_max_ttl'] = explicit_max_ttl if explicit_max_ttl: params['explicit_max_ttl'] = explicit_max_ttl if period: params['period'] = period if orphan: return self._post('/v1/auth/token/create-orphan', json=params, wrap_ttl=wrap_ttl).json() elif role: return self._post('/v1/auth/token/create/{0}'.format(role), json=params, wrap_ttl=wrap_ttl).json() else: return self._post('/v1/auth/token/create', json=params, wrap_ttl=wrap_ttl).json() def lookup_token(self, token=None, accessor=False, wrap_ttl=None): """ GET /auth/token/lookup/ GET /auth/token/lookup-accessor/ GET /auth/token/lookup-self """ if token: if accessor: path = '/v1/auth/token/lookup-accessor/{0}'.format(token) return self._post(path, wrap_ttl=wrap_ttl).json() else: return self._get('/v1/auth/token/lookup/{0}'.format(token)).json() else: return self._get('/v1/auth/token/lookup-self', wrap_ttl=wrap_ttl).json() def revoke_token(self, token, orphan=False, accessor=False): """ POST /auth/token/revoke/ POST /auth/token/revoke-orphan/ POST /auth/token/revoke-accessor/ """ if accessor and orphan: msg = "revoke_token does not support 'orphan' and 'accessor' flags together" raise exceptions.InvalidRequest(msg) elif accessor: self._post('/v1/auth/token/revoke-accessor/{0}'.format(token)) elif orphan: self._post('/v1/auth/token/revoke-orphan/{0}'.format(token)) else: self._post('/v1/auth/token/revoke/{0}'.format(token)) def revoke_token_prefix(self, prefix): """ POST /auth/token/revoke-prefix/ """ self._post('/v1/auth/token/revoke-prefix/{0}'.format(prefix)) def renew_token(self, token=None, increment=None, wrap_ttl=None): """ POST /auth/token/renew/ POST /auth/token/renew-self """ params = { 'increment': increment, } if token: path = '/v1/auth/token/renew/{0}'.format(token) return self._post(path, json=params, wrap_ttl=wrap_ttl).json() else: return self._post('/v1/auth/token/renew-self', json=params, wrap_ttl=wrap_ttl).json() def create_token_role(self, role, allowed_policies=None, disallowed_policies=None, orphan=None, period=None, renewable=None, path_suffix=None, explicit_max_ttl=None): """ POST /auth/token/roles/ """ params = { 'allowed_policies': allowed_policies, 'disallowed_policies': disallowed_policies, 'orphan': orphan, 'period': period, 'renewable': renewable, 'path_suffix': path_suffix, 'explicit_max_ttl': explicit_max_ttl } return self._post('/v1/auth/token/roles/{0}'.format(role), json=params) def token_role(self, role): """ Returns the named token role. """ return self.read('auth/token/roles/{0}'.format(role)) def delete_token_role(self, role): """ Deletes the named token role. """ return self.delete('auth/token/roles/{0}'.format(role)) def list_token_roles(self): """ GET /auth/token/roles?list=true """ return self.list('auth/token/roles') def logout(self, revoke_token=False): """ Clears the token used for authentication, optionally revoking it before doing so """ if revoke_token: self.revoke_self_token() self.token = None def is_authenticated(self): """ Helper method which returns the authentication status of the client """ if not self.token: return False try: self.lookup_token() return True except exceptions.Forbidden: return False except exceptions.InvalidPath: return False except exceptions.InvalidRequest: return False def auth_app_id(self, app_id, user_id, mount_point='app-id', use_token=True): """ POST /auth//login """ params = { 'app_id': app_id, 'user_id': user_id, } return self.auth('/v1/auth/{0}/login'.format(mount_point), json=params, use_token=use_token) def auth_tls(self, mount_point='cert', use_token=True): """ POST /auth//login """ return self.auth('/v1/auth/{0}/login'.format(mount_point), use_token=use_token) def auth_userpass(self, username, password, mount_point='userpass', use_token=True, **kwargs): """ POST /auth//login/ """ params = { 'password': password, } params.update(kwargs) return self.auth('/v1/auth/{0}/login/{1}'.format(mount_point, username), json=params, use_token=use_token) def auth_ec2(self, pkcs7, nonce=None, role=None, use_token=True): """ POST /auth/aws-ec2/login """ params = {'pkcs7': pkcs7} if nonce: params['nonce'] = nonce if role: params['role'] = role return self.auth('/v1/auth/aws-ec2/login', json=params, use_token=use_token).json() def create_userpass(self, username, password, policies, mount_point='userpass', **kwargs): """ POST /auth//users/ """ # Users can have more than 1 policy. It is easier for the user to pass in the # policies as a list so if they do, we need to convert to a , delimited string. if isinstance(policies, (list, set, tuple)): policies = ','.join(policies) params = { 'password': password, 'policies': policies } params.update(kwargs) return self._post('/v1/auth/{}/users/{}'.format(mount_point, username), json=params) def delete_userpass(self, username, mount_point='userpass'): """ DELETE /auth//users/ """ return self._delete('/v1/auth/{}/users/{}'.format(mount_point, username)) def create_app_id(self, app_id, policies, display_name=None, mount_point='app-id', **kwargs): """ POST /auth//map/app-id/ """ # app-id can have more than 1 policy. It is easier for the user to pass in the # policies as a list so if they do, we need to convert to a , delimited string. if isinstance(policies, (list, set, tuple)): policies = ','.join(policies) params = { 'value': policies } # Only use the display_name if it has a value. Made it a named param for user # convienence instead of leaving it as part of the kwargs if display_name: params['display_name'] = display_name params.update(kwargs) return self._post('/v1/auth/{}/map/app-id/{}'.format(mount_point, app_id), json=params) def get_app_id(self, app_id, mount_point='app-id', wrap_ttl=None): """ GET /auth//map/app-id/ """ path = '/v1/auth/{0}/map/app-id/{1}'.format(mount_point, app_id) return self._get(path, wrap_ttl=wrap_ttl).json() def delete_app_id(self, app_id, mount_point='app-id'): """ DELETE /auth//map/app-id/ """ return self._delete('/v1/auth/{0}/map/app-id/{1}'.format(mount_point, app_id)) def create_user_id(self, user_id, app_id, cidr_block=None, mount_point='app-id', **kwargs): """ POST /auth//map/user-id/ """ # user-id can be associated to more than 1 app-id (aka policy). It is easier for the user to # pass in the policies as a list so if they do, we need to convert to a , delimited string. if isinstance(app_id, (list, set, tuple)): app_id = ','.join(app_id) params = { 'value': app_id } # Only use the cidr_block if it has a value. Made it a named param for user # convienence instead of leaving it as part of the kwargs if cidr_block: params['cidr_block'] = cidr_block params.update(kwargs) return self._post('/v1/auth/{}/map/user-id/{}'.format(mount_point, user_id), json=params) def get_user_id(self, user_id, mount_point='app-id', wrap_ttl=None): """ GET /auth//map/user-id/ """ path = '/v1/auth/{0}/map/user-id/{1}'.format(mount_point, user_id) return self._get(path, wrap_ttl=wrap_ttl).json() def delete_user_id(self, user_id, mount_point='app-id'): """ DELETE /auth//map/user-id/ """ return self._delete('/v1/auth/{0}/map/user-id/{1}'.format(mount_point, user_id)) def create_vault_ec2_client_configuration(self, access_key, secret_key, endpoint=None): """ POST /auth/aws-ec2/config/client """ params = { 'access_key': access_key, 'secret_key': secret_key } if endpoint is not None: params['endpoint'] = endpoint return self._post('/v1/auth/aws-ec2/config/client', json=params) def get_vault_ec2_client_configuration(self): """ GET /auth/aws-ec2/config/client """ return self._get('/v1/auth/aws-ec2/config/client').json() def delete_vault_ec2_client_configuration(self): """ DELETE /auth/aws-ec2/config/client """ return self._delete('/v1/auth/aws-ec2/config/client') def create_vault_ec2_certificate_configuration(self, cert_name, aws_public_cert): """ POST /auth/aws-ec2/config/certificate/ """ params = { 'cert_name': cert_name, 'aws_public_cert': aws_public_cert } return self._post('/v1/auth/aws-ec2/config/certificate/{0}'.format(cert_name), json=params) def get_vault_ec2_certificate_configuration(self, cert_name): """ GET /auth/aws-ec2/config/certificate/ """ return self._get('/v1/auth/aws-ec2/config/certificate/{0}'.format(cert_name)).json() def list_vault_ec2_certificate_configurations(self): """ GET /auth/aws-ec2/config/certificates?list=true """ params = {'list': True} return self._get('/v1/auth/aws-ec2/config/certificates', params=params).json() def create_ec2_role(self, role, bound_ami_id=None, bound_account_id=None, bound_iam_role_arn=None, bound_iam_instance_profile_arn=None, role_tag=None, max_ttl=None, policies=None, allow_instance_migration=False, disallow_reauthentication=False, **kwargs): """ POST /auth/aws-ec2/role/ """ params = { 'role': role, 'disallow_reauthentication': disallow_reauthentication, 'allow_instance_migration': allow_instance_migration } if bound_ami_id is not None: params['bound_ami_id'] = bound_ami_id if bound_account_id is not None: params['bound_account_id'] = bound_account_id if bound_iam_role_arn is not None: params['bound_iam_role_arn'] = bound_iam_role_arn if bound_iam_instance_profile_arn is not None: params['bound_iam_instance_profile_arn'] = bound_iam_instance_profile_arn if role_tag is not None: params['role_tag'] = role_tag if max_ttl is not None: params['max_ttl'] = max_ttl if policies is not None: params['policies'] = policies params.update(**kwargs) return self._post('/v1/auth/aws-ec2/role/{0}'.format(role), json=params) def get_ec2_role(self, role): """ GET /auth/aws-ec2/role/ """ return self._get('/v1/auth/aws-ec2/role/{0}'.format(role)).json() def delete_ec2_role(self, role): """ DELETE /auth/aws-ec2/role/ """ return self._delete('/v1/auth/aws-ec2/role/{0}'.format(role)) def list_ec2_roles(self): """ GET /auth/aws-ec2/roles?list=true """ try: return self._get('/v1/auth/aws-ec2/roles', params={'list': True}).json() except exceptions.InvalidPath: return None def create_ec2_role_tag(self, role, policies=None, max_ttl=None, instance_id=None, disallow_reauthentication=False, allow_instance_migration=False): """ POST /auth/aws-ec2/role//tag """ params = { 'role': role, 'disallow_reauthentication': disallow_reauthentication, 'allow_instance_migration': allow_instance_migration } if max_ttl is not None: params['max_ttl'] = max_ttl if policies is not None: params['policies'] = policies if instance_id is not None: params['instance_id'] = instance_id return self._post('/v1/auth/aws-ec2/role/{0}/tag'.format(role), json=params).json() def auth_ldap(self, username, password, mount_point='ldap', use_token=True, **kwargs): """ POST /auth//login/ """ params = { 'password': password, } params.update(kwargs) return self.auth('/v1/auth/{0}/login/{1}'.format(mount_point, username), json=params, use_token=use_token) def auth_github(self, token, mount_point='github', use_token=True): """ POST /auth//login """ params = { 'token': token, } return self.auth('/v1/auth/{0}/login'.format(mount_point), json=params, use_token=use_token) def auth(self, url, use_token=True, **kwargs): response = self._post(url, **kwargs).json() if use_token: self.token = response['auth']['client_token'] return response def list_auth_backends(self): """ GET /sys/auth """ return self._get('/v1/sys/auth').json() def enable_auth_backend(self, backend_type, description=None, mount_point=None): """ POST /sys/auth/ """ if not mount_point: mount_point = backend_type params = { 'type': backend_type, 'description': description, } self._post('/v1/sys/auth/{0}'.format(mount_point), json=params) def disable_auth_backend(self, mount_point): """ DELETE /sys/auth/ """ self._delete('/v1/sys/auth/{0}'.format(mount_point)) def create_role(self, role_name, **kwargs): """ POST /auth/approle/role/ """ self._post('/v1/auth/approle/role/{0}'.format(role_name), json=kwargs) def list_roles(self): """ GET /auth/approle/role """ return self._get('/v1/auth/approle/role?list=true').json() def get_role_id(self, role_name): """ GET /auth/approle/role//role-id """ url = '/v1/auth/approle/role/{0}/role-id'.format(role_name) return self._get(url).json()['data']['role_id'] def set_role_id(self, role_name, role_id): """ POST /auth/approle/role//role-id """ url = '/v1/auth/approle/role/{0}/role-id'.format(role_name) params = { 'role_id': role_id } self._post(url, json=params) def get_role(self, role_name): """ GET /auth/approle/role/ """ return self._get('/v1/auth/approle/role/{0}'.format(role_name)).json() def create_role_secret_id(self, role_name, meta=None, cidr_list=None): """ POST /auth/approle/role//secret-id """ url = '/v1/auth/approle/role/{0}/secret-id'.format(role_name) params = {} if meta is not None: params['metadata'] = json.dumps(meta) if cidr_list is not None: params['cidr_list'] = cidr_list return self._post(url, json=params).json() def get_role_secret_id(self, role_name, secret_id): """ POST /auth/approle/role//secret-id/lookup """ url = '/v1/auth/approle/role/{0}/secret-id/lookup'.format(role_name) params = { 'secret_id': secret_id } return self._post(url, json=params).json() def list_role_secrets(self, role_name): """ GET /auth/approle/role//secret-id?list=true """ url = '/v1/auth/approle/role/{0}/secret-id?list=true'.format(role_name) return self._get(url).json() def get_role_secret_id_accessor(self, role_name, secret_id_accessor): """ GET /auth/approle/role//secret-id-accessor/ """ url = '/v1/auth/approle/role/{0}/secret-id-accessor/{1}'.format(role_name, secret_id_accessor) return self._get(url).json() def delete_role_secret_id(self, role_name, secret_id): """ POST /auth/approle/role//secret-id/destroy """ url = '/v1/auth/approle/role/{0}/secret-id/destroy'.format(role_name) params = { 'secret_id': secret_id } self._post(url, json=params) def delete_role_secret_id_accessor(self, role_name, secret_id_accessor): """ DELETE /auth/approle/role//secret-id/ """ url = '/v1/auth/approle/role/{0}/secret-id-accessor/{1}'.format(role_name, secret_id_accessor) self._delete(url) def create_role_custom_secret_id(self, role_name, secret_id, meta=None): """ POST /auth/approle/role//custom-secret-id """ url = '/v1/auth/approle/role/{0}/custom-secret-id'.format(role_name) params = { 'secret_id': secret_id } if meta is not None: params['meta'] = meta return self._post(url, json=params).json() def auth_approle(self, role_id, secret_id=None, mount_point='approle', use_token=True): """ POST /auth/approle/login """ params = { 'role_id': role_id } if secret_id is not None: params['secret_id'] = secret_id return self.auth('/v1/auth/{0}/login'.format(mount_point), json=params, use_token=use_token) def transit_create_key(self, name, convergent_encryption=None, derived=None, exportable=None, key_type=None, mount_point='transit'): """ POST //keys/ """ url = '/v1/{0}/keys/{1}'.format(mount_point, name) params = {} if convergent_encryption is not None: params['convergent_encryption'] = convergent_encryption if derived is not None: params['derived'] = derived if exportable is not None: params['exportable'] = exportable if key_type is not None: params['type'] = key_type return self._post(url, json=params) def transit_read_key(self, name, mount_point='transit'): """ GET //keys/ """ url = '/v1/{0}/keys/{1}'.format(mount_point, name) return self._get(url).json() def transit_list_keys(self, mount_point='transit'): """ GET //keys?list=true """ url = '/v1/{0}/keys?list=true'.format(mount_point) return self._get(url).json() def transit_delete_key(self, name, mount_point='transit'): """ DELETE //keys/ """ url = '/v1/{0}/keys/{1}'.format(mount_point, name) return self._delete(url) def transit_update_key(self, name, min_decryption_version=None, min_encryption_version=None, deletion_allowed=None, mount_point='transit'): """ POST //keys//config """ url = '/v1/{0}/keys/{1}/config'.format(mount_point, name) params = {} if min_decryption_version is not None: params['min_decryption_version'] = min_decryption_version if min_encryption_version is not None: params['min_encryption_version'] = min_encryption_version if deletion_allowed is not None: params['deletion_allowed'] = deletion_allowed return self._post(url, json=params) def transit_rotate_key(self, name, mount_point='transit'): """ POST //keys//rotate """ url = '/v1/{0}/keys/{1}/rotate'.format(mount_point, name) return self._post(url) def transit_export_key(self, name, key_type, version=None, mount_point='transit'): """ GET //export//(/) """ if version is not None: url = '/v1/{0}/export/{1}/{2}/{3}'.format(mount_point, key_type, name, version) else: url = '/v1/{0}/export/{1}/{2}'.format(mount_point, key_type, name) return self._get(url).json() def transit_encrypt_data(self, name, plaintext, context=None, key_version=None, nonce=None, batch_input=None, key_type=None, convergent_encryption=None, mount_point='transit'): """ POST //encrypt/ """ url = '/v1/{0}/encrypt/{1}'.format(mount_point, name) params = { 'plaintext': plaintext } if context is not None: params['context'] = context if key_version is not None: params['key_version'] = key_version if nonce is not None: params['nonce'] = nonce if batch_input is not None: params['batch_input'] = batch_input if key_type is not None: params['type'] = key_type if convergent_encryption is not None: params['convergent_encryption'] = convergent_encryption return self._post(url, json=params).json() def transit_decrypt_data(self, name, ciphertext, context=None, nonce=None, batch_input=None, mount_point='transit'): """ POST //decrypt/ """ url = '/v1/{0}/decrypt/{1}'.format(mount_point, name) params = { 'ciphertext': ciphertext } if context is not None: params['context'] = context if nonce is not None: params['nonce'] = nonce if batch_input is not None: params['batch_input'] = batch_input return self._post(url, json=params).json() def transit_rewrap_data(self, name, ciphertext, context=None, key_version=None, nonce=None, batch_input=None, mount_point='transit'): """ POST //rewrap/ """ url = '/v1/{0}/rewrap/{1}'.format(mount_point, name) params = { 'ciphertext': ciphertext } if context is not None: params['context'] = context if key_version is not None: params['key_version'] = key_version if nonce is not None: params['nonce'] = nonce if batch_input is not None: params['batch_input'] = batch_input return self._post(url, json=params).json() def transit_generate_data_key(self, name, key_type, context=None, nonce=None, bits=None, mount_point='transit'): """ POST //datakey// """ url = '/v1/{0}/datakey/{1}/{2}'.format(mount_point, key_type, name) params = {} if context is not None: params['context'] = context if nonce is not None: params['nonce'] = nonce if bits is not None: params['bits'] = bits return self._post(url, json=params).json() def transit_generate_rand_bytes(self, data_bytes=None, output_format=None, mount_point='transit'): """ POST //random(/) """ if data_bytes is not None: url = '/v1/{0}/random/{1}'.format(mount_point, data_bytes) else: url = '/v1/{0}/random'.format(mount_point) params = {} if output_format is not None: params["format"] = output_format return self._post(url, json=params).json() def transit_hash_data(self, hash_input, algorithm=None, output_format=None, mount_point='transit'): """ POST //hash(/) """ if algorithm is not None: url = '/v1/{0}/hash/{1}'.format(mount_point, algorithm) else: url = '/v1/{0}/hash'.format(mount_point) params = { 'input': hash_input } if output_format is not None: params['format'] = output_format return self._post(url, json=params).json() def transit_generate_hmac(self, name, hmac_input, key_version=None, algorithm=None, mount_point='transit'): """ POST //hmac/(/) """ if algorithm is not None: url = '/v1/{0}/hmac/{1}/{2}'.format(mount_point, name, algorithm) else: url = '/v1/{0}/hmac/{1}'.format(mount_point, name) params = { 'input': hmac_input } if key_version is not None: params['key_version'] = key_version return self._post(url, json=params).json() def transit_sign_data(self, name, input_data, key_version=None, algorithm=None, context=None, prehashed=None, mount_point='transit'): """ POST //sign/(/) """ if algorithm is not None: url = '/v1/{0}/sign/{1}/{2}'.format(mount_point, name, algorithm) else: url = '/v1/{0}/sign/{1}'.format(mount_point, name) params = { 'input': input_data } if key_version is not None: params['key_version'] = key_version if context is not None: params['context'] = context if prehashed is not None: params['prehashed'] = prehashed return self._post(url, json=params).json() def transit_verify_signed_data(self, name, input_data, algorithm=None, signature=None, hmac=None, context=None, prehashed=None, mount_point='transit'): """ POST //verify/(/) """ if algorithm is not None: url = '/v1/{0}/verify/{1}/{2}'.format(mount_point, name, algorithm) else: url = '/v1/{0}/verify/{1}'.format(mount_point, name) params = { 'input': input_data } if signature is not None: params['signature'] = signature if hmac is not None: params['hmac'] = hmac if context is not None: params['context'] = context if prehashed is not None: params['prehashed'] = prehashed return self._post(url, json=params).json() def close(self): """ Close the underlying Requests session """ self.session.close() def _get(self, url, **kwargs): return self.__request('get', url, **kwargs) def _post(self, url, **kwargs): return self.__request('post', url, **kwargs) def _put(self, url, **kwargs): return self.__request('put', url, **kwargs) def _delete(self, url, **kwargs): return self.__request('delete', url, **kwargs) def __request(self, method, url, headers=None, **kwargs): url = urljoin(self._url, url) if not headers: headers = {} if self.token: headers['X-Vault-Token'] = self.token wrap_ttl = kwargs.pop('wrap_ttl', None) if wrap_ttl: headers['X-Vault-Wrap-TTL'] = str(wrap_ttl) _kwargs = self._kwargs.copy() _kwargs.update(kwargs) response = self.session.request(method, url, headers=headers, allow_redirects=False, **_kwargs) # NOTE(ianunruh): workaround for https://github.com/ianunruh/hvac/issues/51 while response.is_redirect and self.allow_redirects: url = urljoin(self._url, response.headers['Location']) response = self.session.request(method, url, headers=headers, allow_redirects=False, **_kwargs) if response.status_code >= 400 and response.status_code < 600: text = errors = None if response.headers.get('Content-Type') == 'application/json': errors = response.json().get('errors') if errors is None: text = response.text self.__raise_error(response.status_code, text, errors=errors) return response def __raise_error(self, status_code, message=None, errors=None): if status_code == 400: raise exceptions.InvalidRequest(message, errors=errors) elif status_code == 401: raise exceptions.Unauthorized(message, errors=errors) elif status_code == 403: raise exceptions.Forbidden(message, errors=errors) elif status_code == 404: raise exceptions.InvalidPath(message, errors=errors) elif status_code == 429: raise exceptions.RateLimitExceeded(message, errors=errors) elif status_code == 500: raise exceptions.InternalServerError(message, errors=errors) elif status_code == 501: raise exceptions.VaultNotInitialized(message, errors=errors) elif status_code == 503: raise exceptions.VaultDown(message, errors=errors) else: raise exceptions.UnexpectedError(message) hvac-0.5.0/hvac/__init__.py0000644000372000037200000000003313243073521016323 0ustar travistravis00000000000000from hvac.v1 import Client hvac-0.5.0/hvac/exceptions.py0000644000372000037200000000114713243073521016754 0ustar travistravis00000000000000class VaultError(Exception): def __init__(self, message=None, errors=None): if errors: message = ', '.join(errors) self.errors = errors super(VaultError, self).__init__(message) class InvalidRequest(VaultError): pass class Unauthorized(VaultError): pass class Forbidden(VaultError): pass class InvalidPath(VaultError): pass class RateLimitExceeded(VaultError): pass class InternalServerError(VaultError): pass class VaultNotInitialized(VaultError): pass class VaultDown(VaultError): pass class UnexpectedError(VaultError): pass hvac-0.5.0/hvac/version0000644000372000037200000000000513243073535015626 0ustar travistravis000000000000000.5.0hvac-0.5.0/hvac.egg-info/0000755000372000037200000000000013243073561015714 5ustar travistravis00000000000000hvac-0.5.0/hvac.egg-info/PKG-INFO0000644000372000037200000000054613243073561017016 0ustar travistravis00000000000000Metadata-Version: 1.1 Name: hvac Version: 0.5.0 Summary: HashiCorp Vault API client Home-page: https://github.com/ianunruh/hvac Author: Ian Unruh Author-email: ianunruh@gmail.com License: UNKNOWN Description-Content-Type: UNKNOWN Description: UNKNOWN Keywords: hashicorp,vault Platform: UNKNOWN Classifier: License :: OSI Approved :: Apache Software License hvac-0.5.0/hvac.egg-info/SOURCES.txt0000644000372000037200000000050113243073561017574 0ustar travistravis00000000000000MANIFEST.in README.md setup.cfg setup.py hvac/__init__.py hvac/exceptions.py hvac/version hvac.egg-info/PKG-INFO hvac.egg-info/SOURCES.txt hvac.egg-info/dependency_links.txt hvac.egg-info/requires.txt hvac.egg-info/top_level.txt hvac/tests/__init__.py hvac/tests/test_integration.py hvac/tests/util.py hvac/v1/__init__.pyhvac-0.5.0/hvac.egg-info/dependency_links.txt0000644000372000037200000000000113243073561021762 0ustar travistravis00000000000000 hvac-0.5.0/hvac.egg-info/requires.txt0000644000372000037200000000005613243073561020315 0ustar travistravis00000000000000requests>=2.7.0 [parser] pyhcl<0.3.0,>=0.2.1 hvac-0.5.0/hvac.egg-info/top_level.txt0000644000372000037200000000000513243073561020441 0ustar travistravis00000000000000hvac hvac-0.5.0/MANIFEST.in0000644000372000037200000000002413243073521015027 0ustar travistravis00000000000000include hvac/versionhvac-0.5.0/README.md0000644000372000037200000001236713243073521014565 0ustar travistravis00000000000000# HVAC [HashiCorp](https://hashicorp.com/) [Vault](https://www.vaultproject.io) API client for Python 2/3 [![Travis CI](https://travis-ci.org/ianunruh/hvac.svg?branch=master)](https://travis-ci.org/ianunruh/hvac) [![Latest Version](https://img.shields.io/pypi/v/hvac.svg)](https://pypi.python.org/pypi/hvac/) Tested against Vault v0.1.2 and HEAD. Requires v0.1.2 or later. ## Getting started ### Installation ```bash pip install hvac ``` or ```bash pip install "hvac[parser]" ``` if you would like to be able to return parsed HCL data as a Python dict for methods that support it. ### Initialize the client ```python import os import hvac # Using plaintext client = hvac.Client() client = hvac.Client(url='http://localhost:8200') client = hvac.Client(url='http://localhost:8200', token=os.environ['VAULT_TOKEN']) # Using TLS client = hvac.Client(url='https://localhost:8200') # Using TLS with client-side certificate authentication client = hvac.Client(url='https://localhost:8200', cert=('path/to/cert.pem', 'path/to/key.pem')) ``` ### Read and write to secret backends ```python client.write('secret/foo', baz='bar', lease='1h') print(client.read('secret/foo')) client.delete('secret/foo') ``` ### Authenticate to different auth backends ```python # Token client.token = 'MY_TOKEN' assert client.is_authenticated() # => True # App ID client.auth_app_id('MY_APP_ID', 'MY_USER_ID') # App Role client.auth_approle('MY_ROLE_ID', 'MY_SECRET_ID') # GitHub client.auth_github('MY_GITHUB_TOKEN') # LDAP, Username & Password client.auth_ldap('MY_USERNAME', 'MY_PASSWORD') client.auth_userpass('MY_USERNAME', 'MY_PASSWORD') # TLS client = Client(cert=('path/to/cert.pem', 'path/to/key.pem')) client.auth_tls() # Non-default mount point (available on all auth types) client.auth_userpass('MY_USERNAME', 'MY_PASSWORD', mount_point='CUSTOM_MOUNT_POINT') # Authenticating without changing to new token (available on all auth types) result = client.auth_github('MY_GITHUB_TOKEN', use_token=False) print(result['auth']['client_token']) # => u'NEW_TOKEN' # Custom or unsupported auth type params = { 'username': 'MY_USERNAME', 'password': 'MY_PASSWORD', 'custom_param': 'MY_CUSTOM_PARAM', } result = client.auth('/v1/auth/CUSTOM_AUTH/login', json=params) # Logout client.logout() ``` ### Manage tokens ```python token = client.create_token(policies=['root'], lease='1h') current_token = client.lookup_token() some_other_token = client.lookup_token('xxx') client.revoke_token('xxx') client.revoke_token('yyy', orphan=True) client.revoke_token_prefix('zzz') client.renew_token('aaa') ``` ### Managing tokens using accessors ```python token = client.create_token(policies=['root'], lease='1h') token_accessor = token['auth']['accessor'] same_token = client.lookup_token(token_accessor, accessor=True) client.revoke_token(token_accessor, accessor=True) ``` ### Wrapping/unwrapping a token ```python wrap = client.create_token(policies=['root'], lease='1h', wrap_ttl='1m') result = self.client.unwrap(wrap['wrap_info']['token']) ``` ### Manipulate auth backends ```python backends = client.list_auth_backends() client.enable_auth_backend('userpass', mount_point='customuserpass') client.disable_auth_backend('github') ``` ### Manipulate secret backends ```python backends = client.list_secret_backends() client.enable_secret_backend('aws', mount_point='aws-us-east-1') client.disable_secret_backend('mysql') client.tune_secret_backend('generic', mount_point='test', default_lease_ttl='3600s', max_lease_ttl='8600s') client.get_secret_backend_tuning('generic', mount_point='test') client.remount_secret_backend('aws-us-east-1', 'aws-east') ``` ### Manipulate policies ```python policies = client.list_policies() # => ['root'] policy = """ path "sys" { policy = "deny" } path "secret" { policy = "write" } path "secret/foo" { policy = "read" } """ client.set_policy('myapp', policy) client.delete_policy('oldthing') policy = client.get_policy('mypolicy') # Requires pyhcl to automatically parse HCL into a Python dictionary policy = client.get_policy('mypolicy', parse=True) ``` ### Manipulate audit backends ```python backends = client.list_audit_backends() options = { 'path': '/tmp/vault.log', 'log_raw': True, } client.enable_audit_backend('file', options=options, name='somefile') client.disable_audit_backend('oldfile') ``` ### Initialize and seal/unseal ```python print(client.is_initialized()) # => False shares = 5 threshold = 3 result = client.initialize(shares, threshold) root_token = result['root_token'] keys = result['keys'] print(client.is_initialized()) # => True print(client.is_sealed()) # => True # unseal with individual keys client.unseal(keys[0]) client.unseal(keys[1]) client.unseal(keys[2]) # unseal with multiple keys until threshold met client.unseal_multi(keys) print(client.is_sealed()) # => False client.seal() print(client.is_sealed()) # => True ``` ## Testing Integration tests will automatically start a Vault server in the background. Just make sure the latest `vault` binary is available in your `PATH`. 1. [Install Vault](https://vaultproject.io/docs/install/index.html) 2. [Install Tox](http://tox.readthedocs.org/en/latest/install.html) ## Contributing Feel free to open pull requests with additional features or improvements! hvac-0.5.0/setup.cfg0000644000372000037200000000014713243073561015124 0ustar travistravis00000000000000[metadata] description = README.md [bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 hvac-0.5.0/setup.py0000644000372000037200000000177613243073521015022 0ustar travistravis00000000000000#!/usr/bin/env python import os import sys from setuptools import setup, find_packages from pkg_resources import resource_filename # depending on your execution context the version file # may be located in a different place! vsn_path = resource_filename(__name__, 'hvac/version') if not os.path.exists(vsn_path): vsn_path = resource_filename(__name__, 'version') if not os.path.exists(vsn_path): print("%s is missing" % vsn_path) sys.exit(1) setup( name='hvac', version=open(vsn_path, 'r').read(), description='HashiCorp Vault API client', author='Ian Unruh', author_email='ianunruh@gmail.com', url='https://github.com/ianunruh/hvac', keywords=['hashicorp', 'vault'], classifiers=['License :: OSI Approved :: Apache Software License'], packages=find_packages(), install_requires=[ 'requests>=2.7.0', ], include_package_data=True, package_data={'hvac':['version']}, extras_require={ 'parser': ['pyhcl>=0.2.1,<0.3.0'] } ) hvac-0.5.0/PKG-INFO0000644000372000037200000000054613243073561014403 0ustar travistravis00000000000000Metadata-Version: 1.1 Name: hvac Version: 0.5.0 Summary: HashiCorp Vault API client Home-page: https://github.com/ianunruh/hvac Author: Ian Unruh Author-email: ianunruh@gmail.com License: UNKNOWN Description-Content-Type: UNKNOWN Description: UNKNOWN Keywords: hashicorp,vault Platform: UNKNOWN Classifier: License :: OSI Approved :: Apache Software License