Add certificate management support

Certificates for payload traffic is provided through Barbican
secrets store and must be uploaded by the end-user.

Certificates for authentication between Octavia controllers
and its Amphorae instances are issued locally on the Octavia
controller.

At the time of this writing this is the only supported
alternative upstream after the retirement of the Anchor
project [0].

Note that the locally generated certificates are not used for
any load balancer payload data.

0: https://review.openstack.org/#/c/597022/

Change-Id: I38c3e39de4d4cb173970a615f270839871d13910
This commit is contained in:
Frode Nordahl 2018-10-24 14:22:50 +02:00
parent 0cc41b5dcd
commit 4d834c4d2d
No known key found for this signature in database
GPG Key ID: 6A5D59A3BA48373F
3 changed files with 233 additions and 3 deletions

68
src/config.yaml Normal file
View File

@ -0,0 +1,68 @@
options:
loadbalancer-topology:
type: string
default: SINGLE
description: |
Load balancer topology configuration.
.
Supported values are 'SINGLE' and 'ACTIVE_STANDBY'.
lb-mgmt-issuing-cacert:
type: string
default:
description: |
Certificate Authority Certificate used to issue new certificates stored
on the ``Amphora`` load balancer instances. The ``Amphorae`` use them to
authenticate themselves to the ``Octavia`` controller services.
.
Note due to security concerns it is important not use the same CA
certificate for both ``lb-mgmt-issuing-cacert`` and
``lb-mgmt-controller-cacert`` configuration options. Failing to keep
them separate may lead to abuse of certificate data to gain access to
other ``Amphora`` instances in the event one of them is compromised.
.
Note that these certificates are not used for any load balancer payload
data.
lb-mgmt-issuing-ca-private-key:
type: string
default:
description: |
Private key for the Certificate Authority set in ``lb-mgmt-issuing-ca``.
.
Note that these certificates are not used for any load balancer payload
data.
lb-mgmt-issuing-ca-key-passphrase:
type: string
default:
description: |
Passphrase for the key set in ``lb-mgmt-ca-private-key``.
.
NOTE: As of this writing Octavia requires the private key to be protected
with a passphrase.
.
Note that these certificates are not used for any load balancer payload
data.
lb-mgmt-controller-cacert:
type: string
default:
description: |
Certificate Authority Certificate installed on ``Amphorae`` with the
purpose of the ``Amphora`` agent using it to authenticate connections
from ``Octavia`` controller services.
.
Note due to security concerns it is important not use the same CA
certificate for both ``lb-mgmt-issuing-cacert`` and
``lb-mgmt-controller-cacert`` configuration options. Failing to keep
them separate may lead to abuse of certificate data to gain access to
other ``Amphora`` instances in the event one of them is compromised.
.
Note that these certificates are not used for any load balancer payload
data.
lb-mgmt-controller-cert:
type: string
default:
description: |
Certificate used by the ``Octavia`` controller to authenticate itself to
its ``Amphorae``.
.
Note that these certificates are not used for any load balancer payload
data.

View File

@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import collections
import os
import subprocess
@ -22,9 +23,10 @@ import charms_openstack.ip as os_ip
import charms.leadership as leadership
import charmhelpers.core.host as ch_host
import charmhelpers.core as ch_core
OCTAVIA_DIR = '/etc/octavia'
OCTAVIA_CACERT_DIR = os.path.join(OCTAVIA_DIR, 'certs')
OCTAVIA_CONF = os.path.join(OCTAVIA_DIR, 'octavia.conf')
OCTAVIA_WEBSERVER_SITE = 'octavia-api'
OCTAVIA_WSGI_CONF = '/etc/apache2/sites-available/octavia-api.conf'
@ -43,6 +45,91 @@ class OctaviaAdapters(charms_openstack.adapters.OpenStackAPIRelationAdapters):
charm_intance=charm_instance)
@charms_openstack.adapters.config_property
def issuing_cacert(cls):
"""Get path to certificate provided in ``lb-mgmt-issuing-cacert`` option.
Side effect of reading this property is that the on-disk certificate
data is updated if it has changed.
:param cls: charms_openstack.adapters.ConfigurationAdapter derived class
instance. Charm class instance is at cls.charm_instance.
:type: cls: charms_openstack.adapters.ConfiguartionAdapter
"""
config = ch_core.hookenv.config('lb-mgmt-issuing-cacert')
if config:
return cls.charm_instance.decode_and_write_cert(
'issuing_ca.pem',
config)
@charms_openstack.adapters.config_property
def issuing_ca_private_key(cls):
"""Get path to key provided in ``lb-mgmt-issuing-ca-private-key`` option.
Side effect of reading this property is that the on-disk key
data is updated if it has changed.
:param cls: charms_openstack.adapters.ConfigurationAdapter derived class
instance. Charm class instance is at cls.charm_instance.
:type: cls: charms_openstack.adapters.ConfiguartionAdapter
"""
config = ch_core.hookenv.config('lb-mgmt-issuing-ca-private-key')
if config:
return cls.charm_instance.decode_and_write_cert(
'issuing_ca_key.pem',
config)
@charms_openstack.adapters.config_property
def issuing_ca_private_key_passphrase(cls):
"""Get value provided in in ``lb-mgmt-issuing-ca-key-passphrase`` option.
:param cls: charms_openstack.adapters.ConfigurationAdapter derived class
instance. Charm class instance is at cls.charm_instance.
:type: cls: charms_openstack.adapters.ConfiguartionAdapter
"""
config = ch_core.hookenv.config('lb-mgmt-issuing-ca-key-passphrase')
if config:
return config
@charms_openstack.adapters.config_property
def controller_cacert(cls):
"""Get path to certificate provided in ``lb-mgmt-controller-cacert`` opt.
Side effect of reading this property is that the on-disk certificate
data is updated if it has changed.
:param cls: charms_openstack.adapters.ConfigurationAdapter derived class
instance. Charm class instance is at cls.charm_instance.
:type: cls: charms_openstack.adapters.ConfiguartionAdapter
"""
config = ch_core.hookenv.config('lb-mgmt-controller-cacert')
if config:
return cls.charm_instance.decode_and_write_cert(
'controller_ca.pem',
config)
@charms_openstack.adapters.config_property
def controller_cert(cls):
"""Get path to certificate provided in ``lb-mgmt-controller-cert`` option.
Side effect of reading this property is that the on-disk certificate
data is updated if it has changed.
:param cls: charms_openstack.adapters.ConfigurationAdapter derived class
instance. Charm class instance is at cls.charm_instance.
:type: cls: charms_openstack.adapters.ConfiguartionAdapter
"""
config = ch_core.hookenv.config('lb-mgmt-controller-cert')
if config:
return cls.charm_instance.decode_and_write_cert(
'controller_cert.pem',
config)
class OctaviaCharm(charms_openstack.charm.HAOpenStackCharm):
"""Charm class for the Octavia charm."""
# layer-openstack-api uses service_type as service name in endpoint catalog
@ -95,8 +182,27 @@ class OctaviaCharm(charms_openstack.charm.HAOpenStackCharm):
if check_enabled != 0:
subprocess.check_call(['a2ensite',
OCTAVIA_WEBSERVER_SITE])
ch_host.service_reload('apache2',
restart_on_failure=True)
ch_core.host.service_reload('apache2',
restart_on_failure=True)
def decode_and_write_cert(self, filename, encoded_data):
"""Write certificate data to disk.
:param filename: Name of file
:type filename: str
:param group: Group ownership
:type group: str
:param encoded_data: Base64 encoded data
:type encoded_data: str
:returns: Full path to file
:rtype: str
"""
filename = os.path.join(OCTAVIA_CACERT_DIR, filename)
ch_core.host.mkdir(OCTAVIA_CACERT_DIR, group=self.group,
perms=0o750)
ch_core.host.write_file(filename, base64.b64decode(encoded_data),
group=self.group, perms=0o440)
return filename
@charms_openstack.adapters.config_property
def heartbeat_key(self):

View File

@ -7,6 +7,62 @@ heartbeat_key = {{ options.heartbeat_key }}
[database]
{% include "parts/database" %}
[controller_worker]
{% if options.amp_image_owner_id -%}
amp_image_owner_id = {{ options.amp_image_owner_id }}
{% endif -%}
{% if options.amp_secgroup_list -%}
amp_secgroup_list = {{ options.amp_secgroup_list }}
{% endif -%}
{% if options.amp_flavor_id -%}
amp_flavor_id = {{ options.amp_flavor_id }}
{% endif -%}
{% if options.amp_boot_network_list -%}
amp_boot_network_list = {{ options.amp_boot_network_list }}
{% endif -%}
{% if options.amp_ssh_key_name -%}
amp_ssh_key_name = {{ options.amp_ssh_key_name }}
{% endif -%}
{% if options.amp_image_tag -%}
amp_image_tag = {{ options.amp_image_tag }}
{% endif -%}
amp_active_retries = 180
# This certificate is installed on the ``Amphorae`` and used for validating
# the authenticity of the ``Octavia`` controller.
client_ca = {{ options.controller_cacert }}
network_driver = allowed_address_pairs_driver
compute_driver = compute_nova_driver
amphora_driver = amphora_haproxy_rest_driver
loadbalancer_topology = {{ options.loadbalancer_topology }}
[certificates]
# NOTE(fnordahl) certificates for authentication between Octavia controllers
# and its Amphorae instances are issued locally on the Octavia controller.
#
# At the time of this writing this is the only supported alternative upstream
# after the retirement of the Anchor project [0].
#
# Note that these certificates are not used for any load balancer payload data
#
# 0: https://review.openstack.org/#/c/597022/
cert_generator = local_cert_generator
# This certificate is used to issue individual certificates for each
# ``Amphora`` and to validate their authenticity when they connect to the
# ``Octavia`` controller.
ca_certificate = {{ options.issuing_cacert }}
ca_private_key = {{ options.issuing_ca_private_key }}
ca_private_key_passphrase = {{ options.issuing_ca_private_key_passphrase }}
cert_manager = barbican_cert_manager
[haproxy_amphora]
# This certificate is used by the ``Octavia`` controller to validate the
# authenticity of the ``Amphorae`` connecting to it.
server_ca = {{ options.issuing_cacert }}
# This certificate is used by the ``Octavia`` controller when it takes on the
# role as a "client" connecting to the ``Amphorae``.
client_cert = {{ options.controller_cert }}
[service_auth]
auth_type = password
auth_uri = {{ identity_service.service_protocol }}://{{ identity_service.service_host }}:{{ identity_service.service_port }}