Add basic network spaces support

New extra-binding - 'access' - is used to configure in frontend
Vault API address.

The 'cluster' relation is used to configure inter-unit cluster
communcation in HA deployments.

Change-Id: I72031fef5c59e9aeb90f0dee1aea65c2ad27aef7
This commit is contained in:
James Page 2018-04-17 15:25:10 +01:00
parent 68068c64d8
commit d895a358d5
5 changed files with 70 additions and 23 deletions

View File

@ -18,3 +18,10 @@ deploying on bionic, you'll need to deploy a 9.x version of PostgreSQL.
After deploying and relating the charm to postgresql, install
the vault snap locally and use "vault init" to create the
master key shards and the root token, and store them safely.
## Network Spaces support
The vault charm directly supports network binding via the 'access'
extra-binding and the 'cluster' peer relation. These allow the Vault
API and inter-unit Cluster addresses to be configured using Juju
network spaces.

View File

@ -16,6 +16,8 @@ series:
- bionic
tags:
- security
extra-bindings:
access:
requires:
db:
interface: pgsql
@ -30,3 +32,6 @@ provides:
nrpe-external-master:
interface: nrpe-external-master
scope: container
peers:
cluster:
interface: vault-ha

View File

@ -1,4 +1,5 @@
import base64
import functools
import hvac
import psycopg2
import requests
@ -23,6 +24,7 @@ from charmhelpers.core.hookenv import (
application_version_set,
atexit,
local_unit,
network_get_primary_address,
)
from charmhelpers.core.host import (
@ -136,9 +138,10 @@ def configure_vault(context):
key=context['etcd_tls_key_file'],
cert=context['etcd_tls_cert_file'],
ca=context['etcd_tls_ca_file'])
context['vault_api_url'] = get_api_url()
log("Etcd detected, setting vault_api_url to {}".format(
context['vault_api_url']))
context['api_addr'] = get_api_url()
context['cluster_addr'] = get_cluster_url()
log("Etcd detected, setting api_addr to {}".format(
context['api_addr']))
else:
log("Etcd not detected", level=DEBUG)
log("Rendering vault.hcl.j2", level=DEBUG)
@ -162,15 +165,27 @@ def configure_vault(context):
open_port(8200)
def get_api_url():
def binding_address(binding):
try:
return network_get_primary_address(binding)
except NotImplementedError:
return unit_private_ip()
def get_vault_url(binding, port):
protocol = 'http'
port = '8200'
ip = unit_private_ip()
ip = binding_address(binding)
if is_state('vault.ssl.available'):
protocol = 'https'
return '{}://{}:{}'.format(protocol, ip, port)
get_api_url = functools.partial(get_vault_url,
binding='access', port=8200)
get_cluster_url = functools.partial(get_vault_url,
binding='cluster', port=8201)
@when('snap.installed.vault')
@when_not('configured')
@when('db.master.available')

View File

@ -1,5 +1,8 @@
{%- if vault_api_url %}
api_addr = "{{ vault_api_url }}"
{%- if api_addr %}
api_addr = "{{ api_addr }}"
{%- endif %}
{%- if cluster_addr %}
cluster_addr = "{{ cluster_addr }}"
{%- endif %}
{%- if disable_mlock %}
disable_mlock = true
@ -19,21 +22,19 @@ storage "mysql" {
{%- if etcd_conn %}
ha_storage "etcd" {
ha_enabled = "true"
address = "{{ etcd_conn }}"
tls_ca_file = "{{ etcd_tls_ca_file }}"
tls_cert_file = "{{ etcd_tls_cert_file }}"
tls_key_file = "{{ etcd_tls_key_file }}"
address = "{{ etcd_conn }}"
tls_ca_file = "{{ etcd_tls_ca_file }}"
tls_cert_file = "{{ etcd_tls_cert_file }}"
tls_key_file = "{{ etcd_tls_key_file }}"
etcd_api = "v3"
}
{%- endif %}
listener "tcp" {
address = "0.0.0.0:8200"
address = "0.0.0.0:8200"
{%- if ssl_available %}
tls_cert_file = "/var/snap/vault/common/vault.crt"
tls_key_file = "/var/snap/vault/common/vault.key"
tls_cert_file = "/var/snap/vault/common/vault.crt"
tls_key_file = "/var/snap/vault/common/vault.key"
{%- else %}
tls_disable = 1
tls_disable = 1
{%- endif %}
}
}

View File

@ -64,6 +64,7 @@ class TestHandlers(unittest.TestCase):
'unit_private_ip',
'application_version_set',
'local_unit',
'network_get_primary_address',
]
self.patch_all()
@ -218,12 +219,15 @@ class TestHandlers(unittest.TestCase):
])
@patch.object(handlers, 'save_etcd_client_credentials')
@patch.object(handlers, 'get_cluster_url')
@patch.object(handlers, 'can_restart')
@patch.object(handlers, 'get_api_url')
def test_configure_vault_etcd(self, get_api_url, can_restart,
get_cluster_url,
save_etcd_client_credentials):
can_restart.return_value = True
get_api_url.return_value = 'http://this-unit'
get_api_url.return_value = 'http://this-unit:8200'
get_cluster_url.return_value = 'http://this-unit:8201'
self.config.return_value = {'disable-mlock': False}
etcd_mock = mock.MagicMock()
etcd_mock.connection_string.return_value = 'http://etcd'
@ -237,7 +241,8 @@ class TestHandlers(unittest.TestCase):
'etcd_tls_ca_file': '/var/snap/vault/common/etcd-ca.pem',
'etcd_tls_cert_file': '/var/snap/vault/common/etcd-cert.pem',
'etcd_tls_key_file': '/var/snap/vault/common/etcd.key',
'vault_api_url': 'http://this-unit'}
'api_addr': 'http://this-unit:8200',
'cluster_addr': 'http://this-unit:8201'}
render_calls = [
mock.call(
'vault.hcl.j2',
@ -294,13 +299,27 @@ class TestHandlers(unittest.TestCase):
def test_get_api_url_ssl(self):
self.is_state.return_value = True
self.unit_private_ip.return_value = '1.2.3.4'
self.network_get_primary_address.return_value = '1.2.3.4'
self.assertEqual(handlers.get_api_url(), 'https://1.2.3.4:8200')
self.network_get_primary_address.assert_called_with('access')
def test_get_api_url_nossl(self):
self.is_state.return_value = False
self.unit_private_ip.return_value = '1.2.3.4'
self.network_get_primary_address.return_value = '1.2.3.4'
self.assertEqual(handlers.get_api_url(), 'http://1.2.3.4:8200')
self.network_get_primary_address.assert_called_with('access')
def test_get_cluster_url_ssl(self):
self.is_state.return_value = True
self.network_get_primary_address.return_value = '1.2.3.4'
self.assertEqual(handlers.get_cluster_url(), 'https://1.2.3.4:8201')
self.network_get_primary_address.assert_called_with('cluster')
def test_get_cluster_url_nossl(self):
self.is_state.return_value = False
self.network_get_primary_address.return_value = '1.2.3.4'
self.assertEqual(handlers.get_cluster_url(), 'http://1.2.3.4:8201')
self.network_get_primary_address.assert_called_with('cluster')
def test_cluster_connected(self):
self.config.return_value = '10.1.1.1'