Sync charm-helpers for Stein release
As a part of the Stein release, we need to ensure that charmhelpers is up to date. Change-Id: I8c1c11dcc5f6fc9884ba66a439bf5788342299b2
This commit is contained in:
parent
dc63810cb4
commit
b0c3818bb6
@ -30,14 +30,20 @@ from charmhelpers.core.hookenv import (
|
||||
cached,
|
||||
)
|
||||
|
||||
"""
|
||||
The Security Guide suggests a specific list of files inside the
|
||||
config directory for the service having 640 specifically, but
|
||||
by ensuring the containing directory is 750, only the owner can
|
||||
write, and only the group can read files within the directory.
|
||||
|
||||
By restricting access to the containing directory, we can more
|
||||
effectively ensure that there is no accidental leakage if a new
|
||||
file is added to the service without being added to the security
|
||||
guide, and to this check.
|
||||
"""
|
||||
FILE_ASSERTIONS = {
|
||||
'barbican': {
|
||||
# From security guide
|
||||
'/etc/barbican/barbican.conf': {'group': 'barbican', 'mode': '640'},
|
||||
'/etc/barbican/barbican-api-paste.ini':
|
||||
{'group': 'barbican', 'mode': '640'},
|
||||
'/etc/barbican/policy.json': {'group': 'barbican', 'mode': '640'},
|
||||
'/etc/barbican': {'group': 'barbican', 'mode': '750'},
|
||||
},
|
||||
'ceph-mon': {
|
||||
'/var/lib/charm/ceph-mon/ceph.conf':
|
||||
@ -60,82 +66,29 @@ FILE_ASSERTIONS = {
|
||||
{'owner': 'ceph', 'group': 'ceph', 'mode': '755'},
|
||||
},
|
||||
'cinder': {
|
||||
# From security guide
|
||||
'/etc/cinder/cinder.conf': {'group': 'cinder', 'mode': '640'},
|
||||
'/etc/cinder/api-paste.conf': {'group': 'cinder', 'mode': '640'},
|
||||
'/etc/cinder/rootwrap.conf': {'group': 'cinder', 'mode': '640'},
|
||||
'/etc/cinder': {'group': 'cinder', 'mode': '750'},
|
||||
},
|
||||
'glance': {
|
||||
# From security guide
|
||||
'/etc/glance/glance-api-paste.ini': {'group': 'glance', 'mode': '640'},
|
||||
'/etc/glance/glance-api.conf': {'group': 'glance', 'mode': '640'},
|
||||
'/etc/glance/glance-cache.conf': {'group': 'glance', 'mode': '640'},
|
||||
'/etc/glance/glance-manage.conf': {'group': 'glance', 'mode': '640'},
|
||||
'/etc/glance/glance-registry-paste.ini':
|
||||
{'group': 'glance', 'mode': '640'},
|
||||
'/etc/glance/glance-registry.conf': {'group': 'glance', 'mode': '640'},
|
||||
'/etc/glance/glance-scrubber.conf': {'group': 'glance', 'mode': '640'},
|
||||
'/etc/glance/glance-swift-store.conf':
|
||||
{'group': 'glance', 'mode': '640'},
|
||||
'/etc/glance/policy.json': {'group': 'glance', 'mode': '640'},
|
||||
'/etc/glance/schema-image.json': {'group': 'glance', 'mode': '640'},
|
||||
'/etc/glance/schema.json': {'group': 'glance', 'mode': '640'},
|
||||
'/etc/glance': {'group': 'glance', 'mode': '750'},
|
||||
},
|
||||
'keystone': {
|
||||
# From security guide
|
||||
'/etc/keystone/keystone.conf': {'group': 'keystone', 'mode': '640'},
|
||||
'/etc/keystone/keystone-paste.ini':
|
||||
{'group': 'keystone', 'mode': '640'},
|
||||
'/etc/keystone/policy.json': {'group': 'keystone', 'mode': '640'},
|
||||
'/etc/keystone/logging.conf': {'group': 'keystone', 'mode': '640'},
|
||||
'/etc/keystone/ssl/certs/signing_cert.pem':
|
||||
{'group': 'keystone', 'mode': '640'},
|
||||
'/etc/keystone/ssl/private/signing_key.pem':
|
||||
{'group': 'keystone', 'mode': '640'},
|
||||
'/etc/keystone/ssl/certs/ca.pem': {'group': 'keystone', 'mode': '640'},
|
||||
'/etc/keystone':
|
||||
{'owner': 'keystone', 'group': 'keystone', 'mode': '750'},
|
||||
},
|
||||
'manilla': {
|
||||
# From security guide
|
||||
'/etc/manila/manila.conf': {'group': 'manilla', 'mode': '640'},
|
||||
'/etc/manila/api-paste.ini': {'group': 'manilla', 'mode': '640'},
|
||||
'/etc/manila/policy.json': {'group': 'manilla', 'mode': '640'},
|
||||
'/etc/manila/rootwrap.conf': {'group': 'manilla', 'mode': '640'},
|
||||
'/etc/manila': {'group': 'manilla', 'mode': '750'},
|
||||
},
|
||||
'neutron-gateway': {
|
||||
'/etc/neutron/neutron.conf': {'group': 'neutron', 'mode': '640'},
|
||||
'/etc/neutron/rootwrap.conf': {'mode': '640'},
|
||||
'/etc/neutron/rootwrap.d': {'mode': '755'},
|
||||
'/etc/neutron/*': {'group': 'neutron', 'mode': '644'},
|
||||
'/etc/neutron': {'group': 'neutron', 'mode': '750'},
|
||||
},
|
||||
'neutron-api': {
|
||||
# From security guide
|
||||
'/etc/neutron/neutron.conf': {'group': 'neutron', 'mode': '640'},
|
||||
'/etc/nova/api-paste.ini': {'group': 'neutron', 'mode': '640'},
|
||||
'/etc/neutron/rootwrap.conf': {'group': 'neutron', 'mode': '640'},
|
||||
# Additional validations
|
||||
'/etc/neutron/rootwrap.d': {'mode': '755'},
|
||||
'/etc/neutron/neutron_lbaas.conf': {'mode': '644'},
|
||||
'/etc/neutron/neutron_vpnaas.conf': {'mode': '644'},
|
||||
'/etc/neutron/*': {'group': 'neutron', 'mode': '644'},
|
||||
'/etc/neutron/': {'group': 'neutron', 'mode': '750'},
|
||||
},
|
||||
'nova-cloud-controller': {
|
||||
# From security guide
|
||||
'/etc/nova/api-paste.ini': {'group': 'nova', 'mode': '640'},
|
||||
'/etc/nova/nova.conf': {'group': 'nova', 'mode': '750'},
|
||||
'/etc/nova/*': {'group': 'nova', 'mode': '640'},
|
||||
# Additional validations
|
||||
'/etc/nova/logging.conf': {'group': 'nova', 'mode': '640'},
|
||||
'/etc/nova': {'group': 'nova', 'mode': '750'},
|
||||
},
|
||||
'nova-compute': {
|
||||
# From security guide
|
||||
'/etc/nova/nova.conf': {'group': 'nova', 'mode': '640'},
|
||||
'/etc/nova/api-paste.ini': {'group': 'nova', 'mode': '640'},
|
||||
'/etc/nova/rootwrap.conf': {'group': 'nova', 'mode': '640'},
|
||||
# Additional Validations
|
||||
'/etc/nova/nova-compute.conf': {'group': 'nova', 'mode': '640'},
|
||||
'/etc/nova/logging.conf': {'group': 'nova', 'mode': '640'},
|
||||
'/etc/nova/nm.conf': {'mode': '644'},
|
||||
'/etc/nova/*': {'group': 'nova', 'mode': '640'},
|
||||
'/etc/nova/': {'group': 'nova', 'mode': '750'},
|
||||
},
|
||||
'openstack-dashboard': {
|
||||
# From security guide
|
||||
@ -178,7 +131,7 @@ def _config_ini(path):
|
||||
return dict(conf)
|
||||
|
||||
|
||||
def _validate_file_ownership(owner, group, file_name):
|
||||
def _validate_file_ownership(owner, group, file_name, optional=False):
|
||||
"""
|
||||
Validate that a specified file is owned by `owner:group`.
|
||||
|
||||
@ -188,12 +141,16 @@ def _validate_file_ownership(owner, group, file_name):
|
||||
:type group: str
|
||||
:param file_name: Path to the file to verify
|
||||
:type file_name: str
|
||||
:param optional: Is this file optional,
|
||||
ie: Should this test fail when it's missing
|
||||
:type optional: bool
|
||||
"""
|
||||
try:
|
||||
ownership = _stat(file_name)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Error reading file: {}".format(e))
|
||||
assert False, "Specified file does not exist: {}".format(file_name)
|
||||
if not optional:
|
||||
assert False, "Specified file does not exist: {}".format(file_name)
|
||||
assert owner == ownership.owner, \
|
||||
"{} has an incorrect owner: {} should be {}".format(
|
||||
file_name, ownership.owner, owner)
|
||||
@ -203,7 +160,7 @@ def _validate_file_ownership(owner, group, file_name):
|
||||
print("Validate ownership of {}: PASS".format(file_name))
|
||||
|
||||
|
||||
def _validate_file_mode(mode, file_name):
|
||||
def _validate_file_mode(mode, file_name, optional=False):
|
||||
"""
|
||||
Validate that a specified file has the specified permissions.
|
||||
|
||||
@ -211,12 +168,16 @@ def _validate_file_mode(mode, file_name):
|
||||
:type owner: str
|
||||
:param file_name: Path to the file to verify
|
||||
:type file_name: str
|
||||
:param optional: Is this file optional,
|
||||
ie: Should this test fail when it's missing
|
||||
:type optional: bool
|
||||
"""
|
||||
try:
|
||||
ownership = _stat(file_name)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Error reading file: {}".format(e))
|
||||
assert False, "Specified file does not exist: {}".format(file_name)
|
||||
if not optional:
|
||||
assert False, "Specified file does not exist: {}".format(file_name)
|
||||
assert mode == ownership.mode, \
|
||||
"{} has an incorrect mode: {} should be {}".format(
|
||||
file_name, ownership.mode, mode)
|
||||
@ -243,14 +204,15 @@ def validate_file_ownership(config):
|
||||
"Invalid ownership configuration: {}".format(key))
|
||||
owner = options.get('owner', config.get('owner', 'root'))
|
||||
group = options.get('group', config.get('group', 'root'))
|
||||
optional = options.get('optional', config.get('optional', 'False'))
|
||||
if '*' in file_name:
|
||||
for file in glob.glob(file_name):
|
||||
if file not in files.keys():
|
||||
if os.path.isfile(file):
|
||||
_validate_file_ownership(owner, group, file)
|
||||
_validate_file_ownership(owner, group, file, optional)
|
||||
else:
|
||||
if os.path.isfile(file_name):
|
||||
_validate_file_ownership(owner, group, file_name)
|
||||
_validate_file_ownership(owner, group, file_name, optional)
|
||||
|
||||
|
||||
@audit(is_audit_type(AuditType.OpenStackSecurityGuide),
|
||||
@ -264,14 +226,15 @@ def validate_file_permissions(config):
|
||||
raise RuntimeError(
|
||||
"Invalid ownership configuration: {}".format(key))
|
||||
mode = options.get('mode', config.get('permissions', '600'))
|
||||
optional = options.get('optional', config.get('optional', 'False'))
|
||||
if '*' in file_name:
|
||||
for file in glob.glob(file_name):
|
||||
if file not in files.keys():
|
||||
if os.path.isfile(file):
|
||||
_validate_file_mode(mode, file)
|
||||
_validate_file_mode(mode, file, optional)
|
||||
else:
|
||||
if os.path.isfile(file_name):
|
||||
_validate_file_mode(mode, file_name)
|
||||
_validate_file_mode(mode, file_name, optional)
|
||||
|
||||
|
||||
@audit(is_audit_type(AuditType.OpenStackSecurityGuide))
|
||||
|
@ -180,13 +180,17 @@ def create_ip_cert_links(ssl_dir, custom_hostname_link=None):
|
||||
os.symlink(hostname_key, custom_key)
|
||||
|
||||
|
||||
def install_certs(ssl_dir, certs, chain=None):
|
||||
def install_certs(ssl_dir, certs, chain=None, user='root', group='root'):
|
||||
"""Install the certs passed into the ssl dir and append the chain if
|
||||
provided.
|
||||
|
||||
:param ssl_dir: str Directory to create symlinks in
|
||||
:param certs: {} {'cn': {'cert': 'CERT', 'key': 'KEY'}}
|
||||
:param chain: str Chain to be appended to certs
|
||||
:param user: (Optional) Owner of certificate files. Defaults to 'root'
|
||||
:type user: str
|
||||
:param group: (Optional) Group of certificate files. Defaults to 'root'
|
||||
:type group: str
|
||||
"""
|
||||
for cn, bundle in certs.items():
|
||||
cert_filename = 'cert_{}'.format(cn)
|
||||
@ -197,21 +201,25 @@ def install_certs(ssl_dir, certs, chain=None):
|
||||
# trust certs signed by an intermediate in the chain
|
||||
cert_data = cert_data + os.linesep + chain
|
||||
write_file(
|
||||
path=os.path.join(ssl_dir, cert_filename),
|
||||
path=os.path.join(ssl_dir, cert_filename), owner=user, group=group,
|
||||
content=cert_data, perms=0o640)
|
||||
write_file(
|
||||
path=os.path.join(ssl_dir, key_filename),
|
||||
path=os.path.join(ssl_dir, key_filename), owner=user, group=group,
|
||||
content=bundle['key'], perms=0o640)
|
||||
|
||||
|
||||
def process_certificates(service_name, relation_id, unit,
|
||||
custom_hostname_link=None):
|
||||
custom_hostname_link=None, user='root', group='root'):
|
||||
"""Process the certificates supplied down the relation
|
||||
|
||||
:param service_name: str Name of service the certifcates are for.
|
||||
:param relation_id: str Relation id providing the certs
|
||||
:param unit: str Unit providing the certs
|
||||
:param custom_hostname_link: str Name of custom link to create
|
||||
:param user: (Optional) Owner of certificate files. Defaults to 'root'
|
||||
:type user: str
|
||||
:param group: (Optional) Group of certificate files. Defaults to 'root'
|
||||
:type group: str
|
||||
"""
|
||||
data = relation_get(rid=relation_id, unit=unit)
|
||||
ssl_dir = os.path.join('/etc/apache2/ssl/', service_name)
|
||||
@ -223,7 +231,7 @@ def process_certificates(service_name, relation_id, unit,
|
||||
if certs:
|
||||
certs = json.loads(certs)
|
||||
install_ca_cert(ca.encode())
|
||||
install_certs(ssl_dir, certs, chain)
|
||||
install_certs(ssl_dir, certs, chain, user=user, group=group)
|
||||
create_ip_cert_links(
|
||||
ssl_dir,
|
||||
custom_hostname_link=custom_hostname_link)
|
||||
|
@ -792,6 +792,7 @@ class ApacheSSLContext(OSContextGenerator):
|
||||
# and service namespace accordingly.
|
||||
external_ports = []
|
||||
service_namespace = None
|
||||
user = group = 'root'
|
||||
|
||||
def enable_modules(self):
|
||||
cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http', 'headers']
|
||||
@ -810,9 +811,11 @@ class ApacheSSLContext(OSContextGenerator):
|
||||
key_filename = 'key'
|
||||
|
||||
write_file(path=os.path.join(ssl_dir, cert_filename),
|
||||
content=b64decode(cert), perms=0o640)
|
||||
content=b64decode(cert), owner=self.user,
|
||||
group=self.group, perms=0o640)
|
||||
write_file(path=os.path.join(ssl_dir, key_filename),
|
||||
content=b64decode(key), perms=0o640)
|
||||
content=b64decode(key), owner=self.user,
|
||||
group=self.group, perms=0o640)
|
||||
|
||||
def configure_ca(self):
|
||||
ca_cert = get_ca_cert()
|
||||
@ -1932,3 +1935,30 @@ class VersionsContext(OSContextGenerator):
|
||||
return {
|
||||
'openstack_release': ostack,
|
||||
'operating_system_release': osystem}
|
||||
|
||||
|
||||
class LogrotateContext(OSContextGenerator):
|
||||
"""Common context generator for logrotate."""
|
||||
|
||||
def __init__(self, location, interval, count):
|
||||
"""
|
||||
:param location: Absolute path for the logrotate config file
|
||||
:type location: str
|
||||
:param interval: The interval for the rotations. Valid values are
|
||||
'daily', 'weekly', 'monthly', 'yearly'
|
||||
:type interval: str
|
||||
:param count: The logrotate count option configures the 'count' times
|
||||
the log files are being rotated before being
|
||||
:type count: int
|
||||
"""
|
||||
self.location = location
|
||||
self.interval = interval
|
||||
self.count = 'rotate {}'.format(count)
|
||||
|
||||
def __call__(self):
|
||||
ctxt = {
|
||||
'logrotate_logs_location': self.location,
|
||||
'logrotate_interval': self.interval,
|
||||
'logrotate_count': self.count,
|
||||
}
|
||||
return ctxt
|
||||
|
@ -159,7 +159,7 @@ def resolve_address(endpoint_type=PUBLIC, override=True):
|
||||
if is_address_in_network(bound_cidr, vip):
|
||||
resolved_address = vip
|
||||
break
|
||||
except NotImplementedError:
|
||||
except (NotImplementedError, NoNetworkBinding):
|
||||
# If no net-splits configured and no support for extra
|
||||
# bindings/network spaces so we expect a single vip
|
||||
resolved_address = vips[0]
|
||||
|
9
charmhelpers/contrib/openstack/templates/logrotate
Normal file
9
charmhelpers/contrib/openstack/templates/logrotate
Normal file
@ -0,0 +1,9 @@
|
||||
/var/log/{{ logrotate_logs_location }}/*.log {
|
||||
{{ logrotate_interval }}
|
||||
{{ logrotate_count }}
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
copytruncate
|
||||
}
|
@ -17,12 +17,53 @@ import re
|
||||
from stat import S_ISBLK
|
||||
|
||||
from subprocess import (
|
||||
CalledProcessError,
|
||||
check_call,
|
||||
check_output,
|
||||
call
|
||||
)
|
||||
|
||||
|
||||
def _luks_uuid(dev):
|
||||
"""
|
||||
Check to see if dev is a LUKS encrypted volume, returning the UUID
|
||||
of volume if it is.
|
||||
|
||||
:param: dev: path to block device to check.
|
||||
:returns: str. UUID of LUKS device or None if not a LUKS device
|
||||
"""
|
||||
try:
|
||||
cmd = ['cryptsetup', 'luksUUID', dev]
|
||||
return check_output(cmd).decode('UTF-8').strip()
|
||||
except CalledProcessError:
|
||||
return None
|
||||
|
||||
|
||||
def is_luks_device(dev):
|
||||
"""
|
||||
Determine if dev is a LUKS-formatted block device.
|
||||
|
||||
:param: dev: A full path to a block device to check for LUKS header
|
||||
presence
|
||||
:returns: boolean: indicates whether a device is used based on LUKS header.
|
||||
"""
|
||||
return True if _luks_uuid(dev) else False
|
||||
|
||||
|
||||
def is_mapped_luks_device(dev):
|
||||
"""
|
||||
Determine if dev is a mapped LUKS device
|
||||
:param: dev: A full path to a block device to be checked
|
||||
:returns: boolean: indicates whether a device is mapped
|
||||
"""
|
||||
_, dirs, _ = next(os.walk(
|
||||
'/sys/class/block/{}/holders/'
|
||||
.format(os.path.basename(os.path.realpath(dev))))
|
||||
)
|
||||
is_held = len(dirs) > 0
|
||||
return is_held and is_luks_device(dev)
|
||||
|
||||
|
||||
def is_block_device(path):
|
||||
'''
|
||||
Confirm device at path is a valid block device node.
|
||||
|
@ -47,6 +47,7 @@ if __platform__ == "ubuntu":
|
||||
cmp_pkgrevno,
|
||||
CompareHostReleases,
|
||||
get_distrib_codename,
|
||||
arch
|
||||
) # flake8: noqa -- ignore F401 for this import
|
||||
elif __platform__ == "centos":
|
||||
from charmhelpers.core.host_factory.centos import ( # NOQA:F401
|
||||
|
@ -1,5 +1,6 @@
|
||||
import subprocess
|
||||
|
||||
from charmhelpers.core.hookenv import cached
|
||||
from charmhelpers.core.strutils import BasicStringComparator
|
||||
|
||||
|
||||
@ -97,3 +98,16 @@ def cmp_pkgrevno(package, revno, pkgcache=None):
|
||||
pkgcache = apt_cache()
|
||||
pkg = pkgcache[package]
|
||||
return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
|
||||
|
||||
|
||||
@cached
|
||||
def arch():
|
||||
"""Return the package architecture as a string.
|
||||
|
||||
:returns: the architecture
|
||||
:rtype: str
|
||||
:raises: subprocess.CalledProcessError if dpkg command fails
|
||||
"""
|
||||
return subprocess.check_output(
|
||||
['dpkg', '--print-architecture']
|
||||
).rstrip().decode('UTF-8')
|
||||
|
@ -28,7 +28,7 @@ from charmhelpers.core.hookenv import (
|
||||
__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
|
||||
|
||||
|
||||
def create(sysctl_dict, sysctl_file):
|
||||
def create(sysctl_dict, sysctl_file, ignore=False):
|
||||
"""Creates a sysctl.conf file from a YAML associative array
|
||||
|
||||
:param sysctl_dict: a dict or YAML-formatted string of sysctl
|
||||
@ -36,6 +36,8 @@ def create(sysctl_dict, sysctl_file):
|
||||
:type sysctl_dict: str
|
||||
:param sysctl_file: path to the sysctl file to be saved
|
||||
:type sysctl_file: str or unicode
|
||||
:param ignore: If True, ignore "unknown variable" errors.
|
||||
:type ignore: bool
|
||||
:returns: None
|
||||
"""
|
||||
if type(sysctl_dict) is not dict:
|
||||
@ -52,7 +54,12 @@ def create(sysctl_dict, sysctl_file):
|
||||
for key, value in sysctl_dict_parsed.items():
|
||||
fd.write("{}={}\n".format(key, value))
|
||||
|
||||
log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict_parsed),
|
||||
log("Updating sysctl_file: {} values: {}".format(sysctl_file,
|
||||
sysctl_dict_parsed),
|
||||
level=DEBUG)
|
||||
|
||||
check_call(["sysctl", "-p", sysctl_file])
|
||||
call = ["sysctl", "-p", sysctl_file]
|
||||
if ignore:
|
||||
call.append("-e")
|
||||
|
||||
check_call(call)
|
||||
|
Loading…
x
Reference in New Issue
Block a user