Charmed-Kubernetes/nrpe/mod/charmhelpers/tests/contrib/openstack/test_vaultlocker.py

262 lines
10 KiB
Python

# Copyright 2018 Canonical Limited.
#
# 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 json
import mock
import os
import sys
import unittest
import charmhelpers.contrib.openstack.vaultlocker as vaultlocker
from .test_os_contexts import TestDB
INCOMPLETE_RELATION = {
'secrets-storage:1': {
'vault/0': {}
}
}
COMPLETE_RELATION = {
'secrets-storage:1': {
'vault/0': {
'vault_url': json.dumps('http://vault:8200'),
'test-service/0_role_id': json.dumps('test-role-from-vault'),
'test-service/0_token':
json.dumps('00c9a9ab-c523-459d-a250-2ce8f0877c03'),
}
}
}
DIRTY_RELATION = {
'secrets-storage:1': {
'vault/0': {
'vault_url': json.dumps('http://vault:8200'),
'test-service/0_role_id': json.dumps('test-role-from-vault'),
'test-service/0_token':
json.dumps('00c9a9ab-c523-459d-a250-2ce8f0877c03'),
},
'vault/1': {
'vault_url': json.dumps('http://vault:8200'),
'test-service/0_role_id': json.dumps('test-role-from-vault'),
'test-service/0_token':
json.dumps('67b36149-dc86-4b80-96c4-35b91847d16e'),
}
}
}
COMPLETE_WITH_CA_RELATION = {
'secrets-storage:1': {
'vault/0': {
'vault_url': json.dumps('http://vault:8200'),
'test-service/0_role_id': json.dumps('test-role-from-vault'),
'test-service/0_token':
json.dumps('00c9a9ab-c523-459d-a250-2ce8f0877c03'),
'vault_ca': json.dumps('test-ca-data'),
}
}
}
class VaultLockerTestCase(unittest.TestCase):
to_patch = [
'hookenv',
'templating',
'alternatives',
'host',
'unitdata',
]
_target_path = '/var/lib/charm/test-service/vaultlocker.conf'
def setUp(self):
for m in self.to_patch:
setattr(self, m, self._patch(m))
self.hookenv.service_name.return_value = 'test-service'
self.hookenv.local_unit.return_value = 'test-service/0'
self.db = TestDB()
self.unitdata.kv.return_value = self.db
fake_exc = mock.MagicMock()
fake_exc.InvalidRequest = Exception
self.fake_hvac = mock.MagicMock()
self.fake_hvac.exceptions = fake_exc
sys.modules['hvac'] = self.fake_hvac
def fake_retrieve_secret_id(self, url=None, token=None):
if token == self.good_token:
return '31be8e65-20a3-45e0-a4a8-4d5a0554fb60'
else:
raise self.fake_hvac.exceptions.InvalidRequest
def _patch(self, target):
_m = mock.patch.object(vaultlocker, target)
_mock = _m.start()
self.addCleanup(_m.stop)
return _mock
def test_write_vl_config(self):
ctxt = {'test': 'data'}
vaultlocker.write_vaultlocker_conf(context=ctxt)
self.hookenv.service_name.assert_called_once_with()
self.host.mkdir.assert_called_once_with(
os.path.dirname(self._target_path),
perms=0o700
)
self.templating.render.assert_called_once_with(
source='vaultlocker.conf.j2',
target=self._target_path,
context=ctxt,
perms=0o600,
)
self.alternatives.install_alternative.assert_called_once_with(
'vaultlocker.conf',
'/etc/vaultlocker/vaultlocker.conf',
self._target_path,
100
)
def test_write_vl_config_priority(self):
ctxt = {'test': 'data'}
vaultlocker.write_vaultlocker_conf(context=ctxt, priority=200)
self.hookenv.service_name.assert_called_once_with()
self.host.mkdir.assert_called_once_with(
os.path.dirname(self._target_path),
perms=0o700
)
self.templating.render.assert_called_once_with(
source='vaultlocker.conf.j2',
target=self._target_path,
context=ctxt,
perms=0o600,
)
self.alternatives.install_alternative.assert_called_once_with(
'vaultlocker.conf',
'/etc/vaultlocker/vaultlocker.conf',
self._target_path,
200
)
def _setup_relation(self, relation):
self.hookenv.relation_ids.side_effect = (
lambda _: relation.keys()
)
self.hookenv.related_units.side_effect = (
lambda rid: relation[rid].keys()
)
self.hookenv.relation_get.side_effect = (
lambda unit, rid:
relation[rid][unit]
)
def test_context_incomplete(self):
self._setup_relation(INCOMPLETE_RELATION)
context = vaultlocker.VaultKVContext('charm-test')
self.assertEqual(context(), {})
self.hookenv.relation_ids.assert_called_with('secrets-storage')
self.assertFalse(vaultlocker.vault_relation_complete())
@mock.patch.object(vaultlocker, 'retrieve_secret_id')
def test_context_complete(self, retrieve_secret_id):
self._setup_relation(COMPLETE_RELATION)
context = vaultlocker.VaultKVContext('charm-test')
retrieve_secret_id.return_value = 'a3551c8d-0147-4cb6-afc6-efb3db2fccb2'
self.assertEqual(context(),
{'role_id': 'test-role-from-vault',
'secret_backend': 'charm-test',
'secret_id': 'a3551c8d-0147-4cb6-afc6-efb3db2fccb2',
'vault_url': 'http://vault:8200'})
self.hookenv.relation_ids.assert_called_with('secrets-storage')
self.assertTrue(vaultlocker.vault_relation_complete())
calls = [mock.call(url='http://vault:8200',
token='00c9a9ab-c523-459d-a250-2ce8f0877c03')]
retrieve_secret_id.assert_has_calls(calls)
@mock.patch.object(vaultlocker, 'retrieve_secret_id')
def test_context_complete_cached_secret_id(self, retrieve_secret_id):
self._setup_relation(COMPLETE_RELATION)
context = vaultlocker.VaultKVContext('charm-test')
self.db.set('secret-id', '5502fd27-059b-4b0a-91b2-eaff40b6a112')
self.good_token = 'invalid-token' # i.e. cause failure
retrieve_secret_id.side_effect = self.fake_retrieve_secret_id
self.assertEqual(context(),
{'role_id': 'test-role-from-vault',
'secret_backend': 'charm-test',
'secret_id': '5502fd27-059b-4b0a-91b2-eaff40b6a112',
'vault_url': 'http://vault:8200'})
self.hookenv.relation_ids.assert_called_with('secrets-storage')
self.assertTrue(vaultlocker.vault_relation_complete())
calls = [mock.call(url='http://vault:8200',
token='00c9a9ab-c523-459d-a250-2ce8f0877c03')]
retrieve_secret_id.assert_has_calls(calls)
@mock.patch.object(vaultlocker, 'retrieve_secret_id')
def test_purge_old_tokens(self, retrieve_secret_id):
self._setup_relation(DIRTY_RELATION)
context = vaultlocker.VaultKVContext('charm-test')
self.db.set('secret-id', '5502fd27-059b-4b0a-91b2-eaff40b6a112')
self.good_token = '67b36149-dc86-4b80-96c4-35b91847d16e'
retrieve_secret_id.side_effect = self.fake_retrieve_secret_id
self.assertEqual(context(),
{'role_id': 'test-role-from-vault',
'secret_backend': 'charm-test',
'secret_id': '31be8e65-20a3-45e0-a4a8-4d5a0554fb60',
'vault_url': 'http://vault:8200'})
self.hookenv.relation_ids.assert_called_with('secrets-storage')
self.assertTrue(vaultlocker.vault_relation_complete())
self.assertEquals(self.db.get('secret-id'),
'31be8e65-20a3-45e0-a4a8-4d5a0554fb60')
calls = [mock.call(url='http://vault:8200',
token='67b36149-dc86-4b80-96c4-35b91847d16e')]
retrieve_secret_id.assert_has_calls(calls)
@mock.patch.object(vaultlocker, 'retrieve_secret_id')
def test_context_complete_cached_dirty_data(self, retrieve_secret_id):
self._setup_relation(DIRTY_RELATION)
context = vaultlocker.VaultKVContext('charm-test')
self.db.set('secret-id', '5502fd27-059b-4b0a-91b2-eaff40b6a112')
self.good_token = '67b36149-dc86-4b80-96c4-35b91847d16e'
retrieve_secret_id.side_effect = self.fake_retrieve_secret_id
self.assertEqual(context(),
{'role_id': 'test-role-from-vault',
'secret_backend': 'charm-test',
'secret_id': '31be8e65-20a3-45e0-a4a8-4d5a0554fb60',
'vault_url': 'http://vault:8200'})
self.hookenv.relation_ids.assert_called_with('secrets-storage')
self.assertTrue(vaultlocker.vault_relation_complete())
self.assertEquals(self.db.get('secret-id'),
'31be8e65-20a3-45e0-a4a8-4d5a0554fb60')
calls = [mock.call(url='http://vault:8200',
token='67b36149-dc86-4b80-96c4-35b91847d16e')]
retrieve_secret_id.assert_has_calls(calls)
@mock.patch.object(vaultlocker, 'retrieve_secret_id')
def test_context_complete_with_ca(self, retrieve_secret_id):
self._setup_relation(COMPLETE_WITH_CA_RELATION)
retrieve_secret_id.return_value = 'token1234'
context = vaultlocker.VaultKVContext('charm-test')
retrieve_secret_id.return_value = 'a3551c8d-0147-4cb6-afc6-efb3db2fccb2'
self.assertEqual(context(),
{'role_id': 'test-role-from-vault',
'secret_backend': 'charm-test',
'secret_id': 'a3551c8d-0147-4cb6-afc6-efb3db2fccb2',
'vault_url': 'http://vault:8200',
'vault_ca': 'test-ca-data'})
self.hookenv.relation_ids.assert_called_with('secrets-storage')
self.assertTrue(vaultlocker.vault_relation_complete())
calls = [mock.call(url='http://vault:8200',
token='00c9a9ab-c523-459d-a250-2ce8f0877c03')]
retrieve_secret_id.assert_has_calls(calls)