
Add new secrets-storage relation to vault, supporting the use of block device encryption using dm-crypt/vaultlocker. Prepared devices are now recorded in the local unit kv store; this information is used to provide a list of configured block devices to the swift-proxy charm, rather than the previous best guess provided by determine_block_devices. This allows us to use the dm-crypt device name, rather than the underlying block device. Encrypted block devices are unlocked on boot using vaultlocker-decrypt systemd units (enabled by vaultlocker); /etc/fstab entries for such devices make use of a x-systemd.requires option to ensure that the block device is unlocked prior to attempting to mount it. Add new storage binding to allow charm to be used with Juju storage. Add new ephemeral-unmount configuration option to allow cloud ephemeral storage to be used for testing purposes; update functional testing to use this option. The behaviour of 'overwrite' was changed to accomodate the use of encrypted block devices. Change-Id: I9b3f8cd2de412ee96e0139dba4d4abdf998ecaf2
387 lines
11 KiB
Python
Executable File
387 lines
11 KiB
Python
Executable File
#!/usr/bin/python
|
|
#
|
|
# Copyright 2016 Canonical Ltd
|
|
#
|
|
# 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 base64
|
|
import copy
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import socket
|
|
import subprocess
|
|
import tempfile
|
|
|
|
from lib.swift_storage_utils import (
|
|
PACKAGES,
|
|
RESTART_MAP,
|
|
SWIFT_SVCS,
|
|
do_openstack_upgrade,
|
|
ensure_swift_directories,
|
|
fetch_swift_rings,
|
|
register_configs,
|
|
save_script_rc,
|
|
setup_storage,
|
|
assert_charm_supports_ipv6,
|
|
setup_rsync,
|
|
remember_devices,
|
|
REQUIRED_INTERFACES,
|
|
assess_status,
|
|
ensure_devs_tracked,
|
|
VERSION_PACKAGE,
|
|
setup_ufw,
|
|
revoke_access,
|
|
)
|
|
|
|
from lib.misc_utils import pause_aware_restart_on_change
|
|
|
|
from charmhelpers.core.hookenv import (
|
|
Hooks, UnregisteredHookError,
|
|
config,
|
|
log,
|
|
relation_get,
|
|
relation_ids,
|
|
relation_set,
|
|
relations_of_type,
|
|
status_set,
|
|
ingress_address,
|
|
DEBUG,
|
|
)
|
|
|
|
from charmhelpers.fetch import (
|
|
apt_install,
|
|
apt_update,
|
|
add_source,
|
|
filter_installed_packages
|
|
)
|
|
from charmhelpers.core.host import (
|
|
add_to_updatedb_prunepath,
|
|
rsync,
|
|
write_file,
|
|
umount,
|
|
)
|
|
|
|
from charmhelpers.core.sysctl import create as create_sysctl
|
|
|
|
from charmhelpers.payload.execd import execd_preinstall
|
|
|
|
from charmhelpers.contrib.openstack.utils import (
|
|
configure_installation_source,
|
|
openstack_upgrade_available,
|
|
set_os_workload_status,
|
|
os_application_version_set,
|
|
)
|
|
from charmhelpers.contrib.network.ip import (
|
|
get_relation_ip,
|
|
)
|
|
from charmhelpers.contrib.network import ufw
|
|
from charmhelpers.contrib.charmsupport import nrpe
|
|
from charmhelpers.contrib.hardening.harden import harden
|
|
from charmhelpers.core.unitdata import kv
|
|
|
|
from distutils.dir_util import mkpath
|
|
|
|
import charmhelpers.contrib.openstack.vaultlocker as vaultlocker
|
|
|
|
hooks = Hooks()
|
|
CONFIGS = register_configs()
|
|
NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins'
|
|
SUDOERS_D = '/etc/sudoers.d'
|
|
STORAGE_MOUNT_PATH = '/srv/node'
|
|
UFW_DIR = '/etc/ufw'
|
|
|
|
|
|
def add_ufw_gre_rule(ufw_rules_path):
|
|
"""Add allow gre rule to UFW
|
|
|
|
Make a copy of existing UFW before rules, insert our new rule and replace
|
|
existing rules with updated version.
|
|
"""
|
|
rule = '-A ufw-before-input -p 47 -j ACCEPT'
|
|
rule_exists = False
|
|
with tempfile.NamedTemporaryFile() as tmpfile:
|
|
# Close pre-opened file so that we can replace it with a copy of our
|
|
# config file.
|
|
tmpfile.file.close()
|
|
dst = tmpfile.name
|
|
# Copy over ufw rules file
|
|
shutil.copyfile(ufw_rules_path, dst)
|
|
with open(dst, 'r') as fd:
|
|
lines = fd.readlines()
|
|
|
|
# Check whether the line we are adding already exists
|
|
for line in lines:
|
|
if rule in line:
|
|
rule_exists = True
|
|
break
|
|
|
|
added = False
|
|
if not rule_exists:
|
|
# Insert our rule as the first rule.
|
|
with open(dst, 'w') as fd:
|
|
for line in lines:
|
|
if not added and line.startswith('-A'):
|
|
fd.write('# Allow GRE Traffic (added by swift-storage '
|
|
'charm)\n')
|
|
fd.write('{}\n'.format(rule))
|
|
fd.write(line)
|
|
added = True
|
|
else:
|
|
fd.write(line)
|
|
|
|
# Replace existing config with updated one.
|
|
shutil.copyfile(dst, ufw_rules_path)
|
|
|
|
|
|
def initialize_ufw():
|
|
"""Initialize the UFW firewall
|
|
|
|
Ensure critical ports have explicit allows
|
|
|
|
:return: None
|
|
"""
|
|
|
|
if not config('enable-firewall'):
|
|
log("Firewall has been administratively disabled", "DEBUG")
|
|
return
|
|
|
|
# this charm will monitor exclusively the ports used, using 'allow' as
|
|
# default policy enables sharing the machine with other services
|
|
ufw.default_policy('allow', 'incoming')
|
|
ufw.default_policy('allow', 'outgoing')
|
|
ufw.default_policy('allow', 'routed')
|
|
# Rsync manages its own ACLs
|
|
ufw.service('rsync', 'open')
|
|
# Guarantee SSH access
|
|
ufw.service('ssh', 'open')
|
|
# Enable
|
|
ufw.enable(soft_fail=config('allow-ufw-ip6-softfail'))
|
|
|
|
# Allow GRE traffic
|
|
add_ufw_gre_rule(os.path.join(UFW_DIR, 'before.rules'))
|
|
ufw.reload()
|
|
|
|
|
|
@hooks.hook('install.real')
|
|
@harden()
|
|
def install():
|
|
status_set('maintenance', 'Executing pre-install')
|
|
execd_preinstall()
|
|
configure_installation_source(config('openstack-origin'))
|
|
status_set('maintenance', 'Installing apt packages')
|
|
apt_update()
|
|
apt_install(PACKAGES, fatal=True)
|
|
initialize_ufw()
|
|
ensure_swift_directories()
|
|
|
|
|
|
@hooks.hook('config-changed')
|
|
@pause_aware_restart_on_change(RESTART_MAP)
|
|
@harden()
|
|
def config_changed():
|
|
if config('enable-firewall'):
|
|
initialize_ufw()
|
|
else:
|
|
ufw.disable()
|
|
|
|
if config('ephemeral-unmount'):
|
|
umount(config('ephemeral-unmount'), persist=True)
|
|
|
|
if config('prefer-ipv6'):
|
|
status_set('maintenance', 'Configuring ipv6')
|
|
assert_charm_supports_ipv6()
|
|
|
|
ensure_swift_directories()
|
|
setup_rsync()
|
|
|
|
if not config('action-managed-upgrade') and \
|
|
openstack_upgrade_available('swift'):
|
|
status_set('maintenance', 'Running openstack upgrade')
|
|
do_openstack_upgrade(configs=CONFIGS)
|
|
|
|
install_vaultlocker()
|
|
|
|
configure_storage()
|
|
|
|
CONFIGS.write_all()
|
|
|
|
save_script_rc()
|
|
if relations_of_type('nrpe-external-master'):
|
|
update_nrpe_config()
|
|
|
|
sysctl_dict = config('sysctl')
|
|
if sysctl_dict:
|
|
create_sysctl(sysctl_dict, '/etc/sysctl.d/50-swift-storage-charm.conf')
|
|
|
|
add_to_updatedb_prunepath(STORAGE_MOUNT_PATH)
|
|
|
|
|
|
def install_vaultlocker():
|
|
"""Determine whether vaultlocker is required and install"""
|
|
if config('encrypt'):
|
|
pkgs = ['vaultlocker', 'python-hvac']
|
|
installed = len(filter_installed_packages(pkgs)) == 0
|
|
if not installed:
|
|
add_source('ppa:openstack-charmers/vaultlocker')
|
|
apt_update(fatal=True)
|
|
apt_install(pkgs, fatal=True)
|
|
|
|
|
|
@hooks.hook('upgrade-charm')
|
|
@harden()
|
|
def upgrade_charm():
|
|
initialize_ufw()
|
|
apt_install(filter_installed_packages(PACKAGES), fatal=True)
|
|
update_nrpe_config()
|
|
ensure_devs_tracked()
|
|
|
|
|
|
@hooks.hook()
|
|
def swift_storage_relation_joined(rid=None):
|
|
if config('encrypt') and not vaultlocker.vault_relation_complete():
|
|
log('Encryption configured and vault not ready, deferring',
|
|
level=DEBUG)
|
|
return
|
|
rel_settings = {
|
|
'zone': config('zone'),
|
|
'object_port': config('object-server-port'),
|
|
'container_port': config('container-server-port'),
|
|
'account_port': config('account-server-port'),
|
|
}
|
|
|
|
db = kv()
|
|
devs = db.get('prepared-devices', [])
|
|
devs = [os.path.basename(d) for d in devs]
|
|
rel_settings['device'] = ':'.join(devs)
|
|
# Keep a reference of devices we are adding to the ring
|
|
remember_devices(devs)
|
|
|
|
rel_settings['private-address'] = get_relation_ip('swift-storage')
|
|
|
|
relation_set(relation_id=rid, relation_settings=rel_settings)
|
|
|
|
|
|
@hooks.hook('swift-storage-relation-changed')
|
|
@pause_aware_restart_on_change(RESTART_MAP)
|
|
def swift_storage_relation_changed():
|
|
setup_ufw()
|
|
rings_url = relation_get('rings_url')
|
|
swift_hash = relation_get('swift_hash')
|
|
if '' in [rings_url, swift_hash] or None in [rings_url, swift_hash]:
|
|
log('swift_storage_relation_changed: Peer not ready?')
|
|
sys.exit(0)
|
|
|
|
CONFIGS.write('/etc/rsync-juju.d/050-swift-storage.conf')
|
|
CONFIGS.write('/etc/swift/swift.conf')
|
|
|
|
fetch_swift_rings(rings_url)
|
|
|
|
|
|
@hooks.hook('swift-storage-relation-departed')
|
|
def swift_storage_relation_departed():
|
|
ports = [config('object-server-port'),
|
|
config('container-server-port'),
|
|
config('account-server-port')]
|
|
removed_client = ingress_address()
|
|
if removed_client:
|
|
for port in ports:
|
|
revoke_access(removed_client, port)
|
|
|
|
|
|
@hooks.hook('secrets-storage-relation-joined')
|
|
def secrets_storage_joined(relation_id=None):
|
|
relation_set(relation_id=relation_id,
|
|
secret_backend='charm-vaultlocker',
|
|
isolated=True,
|
|
access_address=get_relation_ip('secrets-storage'),
|
|
hostname=socket.gethostname())
|
|
|
|
|
|
@hooks.hook('secrets-storage-relation-changed')
|
|
def secrets_storage_changed():
|
|
vault_ca = relation_get('vault_ca')
|
|
if vault_ca:
|
|
vault_ca = base64.decodestring(json.loads(vault_ca).encode())
|
|
write_file('/usr/local/share/ca-certificates/vault-ca.crt',
|
|
vault_ca, perms=0o644)
|
|
subprocess.check_call(['update-ca-certificates', '--fresh'])
|
|
configure_storage()
|
|
|
|
|
|
@hooks.hook('storage.real')
|
|
def configure_storage():
|
|
setup_storage(config('encrypt'))
|
|
|
|
for rid in relation_ids('swift-storage'):
|
|
swift_storage_relation_joined(rid=rid)
|
|
|
|
|
|
@hooks.hook('nrpe-external-master-relation-joined')
|
|
@hooks.hook('nrpe-external-master-relation-changed')
|
|
def update_nrpe_config():
|
|
# python-dbus is used by check_upstart_job
|
|
apt_install('python-dbus')
|
|
log('Refreshing nrpe checks')
|
|
if not os.path.exists(NAGIOS_PLUGINS):
|
|
mkpath(NAGIOS_PLUGINS)
|
|
rsync(os.path.join(os.getenv('CHARM_DIR'), 'files', 'nrpe-external-master',
|
|
'check_swift_storage.py'),
|
|
os.path.join(NAGIOS_PLUGINS, 'check_swift_storage.py'))
|
|
rsync(os.path.join(os.getenv('CHARM_DIR'), 'files', 'nrpe-external-master',
|
|
'check_swift_service'),
|
|
os.path.join(NAGIOS_PLUGINS, 'check_swift_service'))
|
|
rsync(os.path.join(os.getenv('CHARM_DIR'), 'files', 'sudo',
|
|
'swift-storage'),
|
|
os.path.join(SUDOERS_D, 'swift-storage'))
|
|
|
|
# Find out if nrpe set nagios_hostname
|
|
hostname = nrpe.get_nagios_hostname()
|
|
current_unit = nrpe.get_nagios_unit_name()
|
|
nrpe_setup = nrpe.NRPE(hostname=hostname)
|
|
|
|
# check the rings and replication
|
|
nrpe_setup.add_check(
|
|
shortname='swift_storage',
|
|
description='Check swift storage ring hashes and replication'
|
|
' {%s}' % current_unit,
|
|
check_cmd='check_swift_storage.py {}'.format(
|
|
config('nagios-check-params'))
|
|
)
|
|
nrpe.add_init_service_checks(nrpe_setup, SWIFT_SVCS, current_unit)
|
|
nrpe_setup.write()
|
|
|
|
|
|
@hooks.hook('update-status')
|
|
@harden()
|
|
def update_status():
|
|
log('Updating status.')
|
|
|
|
|
|
def main():
|
|
try:
|
|
hooks.execute(sys.argv)
|
|
except UnregisteredHookError as e:
|
|
log('Unknown hook {} - skipping.'.format(e))
|
|
required_interfaces = copy.deepcopy(REQUIRED_INTERFACES)
|
|
if config('encrypt'):
|
|
required_interfaces['vault'] = ['secrets-storage']
|
|
set_os_workload_status(CONFIGS, required_interfaces,
|
|
charm_func=assess_status)
|
|
os_application_version_set(VERSION_PACKAGE)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|