Add support for multiple client spaces
Some users may not want to expose all vault clients to the same networks. In particular they might want to have some on the default access network and some on an external network. This patch adds support for new 'external' binding which clients can use to talk to the vault api. Change-Id: I0d393c71dcb127b14b8ffcacbd03bbf68f81a53b Closes-Bug: #1826892
This commit is contained in:
parent
f9dbf243c7
commit
c7e2c531ec
|
@ -46,7 +46,10 @@ options:
|
|||
type: string
|
||||
default:
|
||||
description: |
|
||||
Virtual IP to use api traffic
|
||||
Virtual IP to use api traffic. You can provide up to two addresses
|
||||
configured on the access or external bindings. If neither binding
|
||||
is used then you can only provide one address that must be configured
|
||||
on the default space.
|
||||
channel:
|
||||
type: string
|
||||
default: stable
|
||||
|
|
|
@ -19,6 +19,10 @@ import requests
|
|||
import hvac
|
||||
import tenacity
|
||||
|
||||
from charmhelpers.contrib.network.ip import (
|
||||
is_address_in_network,
|
||||
resolve_network_cidr,
|
||||
)
|
||||
import charmhelpers.core.hookenv as hookenv
|
||||
import charmhelpers.core.host as host
|
||||
import charms.reactive
|
||||
|
@ -134,10 +138,32 @@ get_cluster_url = functools.partial(get_vault_url,
|
|||
binding='cluster', port=8201)
|
||||
|
||||
|
||||
def get_vip(binding=None):
|
||||
vip = hookenv.config('vip')
|
||||
if not vip:
|
||||
return None
|
||||
|
||||
vips = vip.split()
|
||||
if len(vips) == 1:
|
||||
return vips[0]
|
||||
|
||||
if not binding:
|
||||
binding = 'access'
|
||||
|
||||
bound_cidr = resolve_network_cidr(
|
||||
binding_address(binding)
|
||||
)
|
||||
for vip in vips:
|
||||
if is_address_in_network(bound_cidr, vip):
|
||||
return vip
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_access_address():
|
||||
protocol = 'http'
|
||||
addr = hookenv.config('dns-ha-access-record')
|
||||
addr = addr or hookenv.config('vip')
|
||||
addr = addr or get_vip('access')
|
||||
addr = addr or binding_address('access')
|
||||
if charms.reactive.is_state('vault.ssl.available'):
|
||||
protocol = 'https'
|
||||
|
|
|
@ -20,6 +20,7 @@ tags:
|
|||
- security
|
||||
extra-bindings:
|
||||
access:
|
||||
external:
|
||||
requires:
|
||||
db:
|
||||
interface: pgsql
|
||||
|
|
|
@ -354,17 +354,23 @@ def nagios_servicegroups_changed():
|
|||
@when('ha.connected')
|
||||
def cluster_connected(hacluster):
|
||||
"""Configure HA resources in corosync"""
|
||||
vip = config('vip')
|
||||
dns_record = config('dns-ha-access-record')
|
||||
if vip and dns_record:
|
||||
vips = config('vip') or None
|
||||
if vips and dns_record:
|
||||
set_flag('config.dns_vip.invalid')
|
||||
log("Unsupported configuration. vip and dns-ha cannot both be set",
|
||||
level=ERROR)
|
||||
return
|
||||
else:
|
||||
clear_flag('config.dns_vip.invalid')
|
||||
if vip:
|
||||
hacluster.add_vip('vault', vip)
|
||||
|
||||
if vips:
|
||||
vips = vips.split()
|
||||
for vip in vips:
|
||||
if vip == vault.get_vip(binding='external'):
|
||||
hacluster.add_vip('vault-ext', vip)
|
||||
else:
|
||||
hacluster.add_vip('vault', vip)
|
||||
elif dns_record:
|
||||
try:
|
||||
ip = network_get_primary_address('access')
|
||||
|
@ -449,7 +455,7 @@ def configure_secrets_backend():
|
|||
|
||||
unit = request['unit']
|
||||
hostname = request['hostname']
|
||||
access_address = request['access_address']
|
||||
access_address = request['ingress_address']
|
||||
isolated = request['isolated']
|
||||
unit_name = unit.unit_name.replace('/', '-')
|
||||
policy_name = approle_name = 'charm-{}'.format(unit_name)
|
||||
|
@ -492,14 +498,28 @@ def configure_secrets_backend():
|
|||
@when('secrets.connected')
|
||||
def send_vault_url_and_ca():
|
||||
secrets = endpoint_from_flag('secrets.connected')
|
||||
vault_url_external = None
|
||||
if is_flag_set('ha.available'):
|
||||
if config('hostname'):
|
||||
vault_url = vault.get_api_url(address=config('hostname'))
|
||||
hostname = config('hostname')
|
||||
if hostname:
|
||||
vault_url = vault.get_api_url(address=hostname)
|
||||
else:
|
||||
vault_url = vault.get_api_url(address=config('vip'))
|
||||
vip = vault.get_vip()
|
||||
vault_url = vault.get_api_url(address=vip)
|
||||
ext_vip = vault.get_vip(binding='external')
|
||||
if ext_vip and ext_vip != vip:
|
||||
vault_url_external = vault.get_api_url(address=ext_vip,
|
||||
binding='external')
|
||||
else:
|
||||
vault_url = vault.get_api_url()
|
||||
secrets.publish_url(vault_url=vault_url)
|
||||
vault_url_external = vault.get_api_url(binding='external')
|
||||
if vault_url_external == vault_url:
|
||||
vault_url_external = None
|
||||
|
||||
secrets.publish_url(vault_url=vault_url, remote_binding='access')
|
||||
if vault_url_external:
|
||||
secrets.publish_url(vault_url=vault_url_external,
|
||||
remote_binding='external')
|
||||
|
||||
if config('ssl-ca'):
|
||||
secrets.publish_ca(vault_ca=config('ssl-ca'))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import mock
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, call
|
||||
|
||||
import charms.reactive
|
||||
|
||||
|
@ -490,7 +490,8 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
|
|||
self.assertFalse(handlers.validate_snap_channel('foobar'))
|
||||
self.assertFalse(handlers.validate_snap_channel('0.10/foobar'))
|
||||
|
||||
def test_cluster_connected_vip(self):
|
||||
@mock.patch.object(handlers.vault, 'get_vip')
|
||||
def test_cluster_connected_vip(self, mock_get_vip):
|
||||
charm_config = {
|
||||
'vip': '10.1.1.1'}
|
||||
self.config.side_effect = lambda x: charm_config.get(x)
|
||||
|
@ -532,6 +533,7 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
|
|||
'hostname': 'juju-123456-0',
|
||||
'isolated': True,
|
||||
'access_address': '10.20.4.5',
|
||||
'ingress_address': '10.20.4.5',
|
||||
'unit': mock.MagicMock()
|
||||
})
|
||||
test_requests[-1]['unit'].unit_name = 'ceph-osd/0'
|
||||
|
@ -541,6 +543,7 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
|
|||
'hostname': 'juju-789012-0',
|
||||
'isolated': True,
|
||||
'access_address': '10.20.4.20',
|
||||
'ingress_address': '10.20.4.20',
|
||||
'unit': mock.MagicMock()
|
||||
})
|
||||
test_requests[-1]['unit'].unit_name = 'omg/0'
|
||||
|
@ -603,68 +606,118 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
|
|||
mock.call('secrets.refresh'),
|
||||
])
|
||||
|
||||
@mock.patch.object(handlers, 'vault')
|
||||
def test_send_vault_url_and_ca(self, _vault):
|
||||
@mock.patch.object(handlers.vault.hookenv, 'network_get_primary_address')
|
||||
def test_send_vault_url_and_ca(self, mock_network_get_primary_address):
|
||||
_test_config = {
|
||||
'vip': '10.5.100.1',
|
||||
'ssl-ca': 'test-ca',
|
||||
}
|
||||
self.config.side_effect = lambda key: _test_config.get(key)
|
||||
mock_secrets = mock.MagicMock()
|
||||
|
||||
def fake_network_get(binding=None):
|
||||
return '10.5.0.23'
|
||||
|
||||
mock_network_get_primary_address.side_effect = fake_network_get
|
||||
self.endpoint_from_flag.return_value = mock_secrets
|
||||
self.is_flag_set.return_value = False
|
||||
_vault.get_api_url.return_value = 'http://10.5.0.23:8200'
|
||||
handlers.send_vault_url_and_ca()
|
||||
self.endpoint_from_flag.assert_called_with('secrets.connected')
|
||||
self.is_flag_set.assert_called_with('ha.available')
|
||||
_vault.get_api_url.assert_called_once_with()
|
||||
mock_secrets.publish_url.assert_called_once_with(
|
||||
vault_url='http://10.5.0.23:8200'
|
||||
vault_url='http://10.5.0.23:8200',
|
||||
remote_binding='access'
|
||||
)
|
||||
mock_secrets.publish_ca.assert_called_once_with(
|
||||
vault_ca='test-ca'
|
||||
)
|
||||
|
||||
@mock.patch.object(handlers, 'vault')
|
||||
def test_send_vault_url_and_ca_ha(self, _vault):
|
||||
@mock.patch.object(handlers.vault.hookenv, 'network_get_primary_address')
|
||||
def test_send_vault_url_and_ca_ext(self, mock_network_get_primary_address):
|
||||
_test_config = {
|
||||
'vip': '10.5.100.1',
|
||||
'ssl-ca': 'test-ca',
|
||||
}
|
||||
self.config.side_effect = lambda key: _test_config.get(key)
|
||||
mock_secrets = mock.MagicMock()
|
||||
|
||||
def fake_network_get(binding=None):
|
||||
if binding == 'external':
|
||||
return '10.6.0.23'
|
||||
|
||||
return '10.5.0.23'
|
||||
|
||||
mock_network_get_primary_address.side_effect = fake_network_get
|
||||
self.endpoint_from_flag.return_value = mock_secrets
|
||||
self.is_flag_set.return_value = True
|
||||
_vault.get_api_url.return_value = 'http://10.5.100.1:8200'
|
||||
self.is_flag_set.return_value = False
|
||||
handlers.send_vault_url_and_ca()
|
||||
self.endpoint_from_flag.assert_called_with('secrets.connected')
|
||||
self.is_flag_set.assert_called_with('ha.available')
|
||||
_vault.get_api_url.assert_called_once_with(address='10.5.100.1')
|
||||
mock_secrets.publish_url.assert_called_once_with(
|
||||
vault_url='http://10.5.100.1:8200'
|
||||
mock_secrets.publish_url.assert_has_calls(
|
||||
[call(vault_url='http://10.5.0.23:8200',
|
||||
remote_binding='access'),
|
||||
call(vault_url='http://10.6.0.23:8200',
|
||||
remote_binding='external')]
|
||||
)
|
||||
mock_secrets.publish_ca.assert_called_once_with(
|
||||
vault_ca='test-ca'
|
||||
)
|
||||
|
||||
@mock.patch.object(handlers, 'vault')
|
||||
def test_send_vault_url_and_ca_hostname(self, _vault):
|
||||
@mock.patch('charmhelpers.contrib.network.ip.get_netmask_for_address')
|
||||
@mock.patch.object(handlers.vault.hookenv, 'config')
|
||||
@mock.patch.object(handlers.vault.hookenv, 'network_get_primary_address')
|
||||
def test_send_vault_url_and_ca_ha(self,
|
||||
mock_network_get_primary_address,
|
||||
mock_config,
|
||||
mock_get_netmask_for_address):
|
||||
_test_config = {
|
||||
'vip': '10.5.100.1 10.6.100.1',
|
||||
'ssl-ca': 'test-ca',
|
||||
'hostname': None
|
||||
}
|
||||
mock_get_netmask_for_address.return_value = 16
|
||||
self.config.side_effect = lambda key: _test_config.get(key)
|
||||
mock_config.side_effect = lambda key: _test_config.get(key)
|
||||
|
||||
mock_secrets = mock.MagicMock()
|
||||
|
||||
def fake_network_get(binding=None):
|
||||
if binding == 'external':
|
||||
return '10.6.0.23'
|
||||
|
||||
return '10.5.0.23'
|
||||
|
||||
mock_network_get_primary_address.side_effect = fake_network_get
|
||||
|
||||
self.endpoint_from_flag.return_value = mock_secrets
|
||||
self.is_flag_set.return_value = True
|
||||
handlers.send_vault_url_and_ca()
|
||||
self.endpoint_from_flag.assert_called_with('secrets.connected')
|
||||
self.is_flag_set.assert_called_with('ha.available')
|
||||
mock_secrets.publish_url.assert_has_calls(
|
||||
[call(vault_url='http://10.5.100.1:8200',
|
||||
remote_binding='access'),
|
||||
call(vault_url='http://10.6.100.1:8200',
|
||||
remote_binding='external')]
|
||||
)
|
||||
mock_secrets.publish_ca.assert_called_once_with(
|
||||
vault_ca='test-ca'
|
||||
)
|
||||
|
||||
def test_send_vault_url_and_ca_hostname(self):
|
||||
_test_config = {
|
||||
'vip': '10.5.100.1',
|
||||
'ssl-ca': 'test-ca',
|
||||
'hostname': 'vault',
|
||||
}
|
||||
self.config.side_effect = lambda key: _test_config.get(key)
|
||||
|
||||
mock_secrets = mock.MagicMock()
|
||||
|
||||
self.endpoint_from_flag.return_value = mock_secrets
|
||||
self.is_flag_set.return_value = True
|
||||
_vault.get_api_url.return_value = 'https://vault:8200'
|
||||
handlers.send_vault_url_and_ca()
|
||||
self.endpoint_from_flag.assert_called_with('secrets.connected')
|
||||
self.is_flag_set.assert_called_with('ha.available')
|
||||
_vault.get_api_url.assert_called_once_with(address='vault')
|
||||
mock_secrets.publish_url.assert_called_once_with(
|
||||
vault_url='https://vault:8200'
|
||||
mock_secrets.publish_url.assert_has_calls(
|
||||
[call(vault_url='http://vault:8200', remote_binding='access')]
|
||||
)
|
||||
mock_secrets.publish_ca.assert_called_once_with(
|
||||
vault_ca='test-ca'
|
||||
|
|
Loading…
Reference in New Issue