├── revision ├── actions ├── start ├── stop ├── add-disk ├── osd-in ├── osd-out ├── zap-disk ├── list-disks ├── blacklist-add-disk ├── remove-disk ├── blacklist-remove-disk ├── security-checklist ├── get-availability-zone ├── update-apparmor-and-restart-osds ├── __init__.py ├── security_checklist.py ├── list_disks.py ├── blacklist.py └── zap_disk.py ├── hooks ├── stop ├── start ├── config-changed ├── install.real ├── storage.real ├── update-status ├── post-series-upgrade ├── pre-series-upgrade ├── upgrade-charm.real ├── mon-relation-changed ├── mon-relation-departed ├── charmhelpers │ ├── core │ │ ├── host_factory │ │ │ ├── __init__.py │ │ │ ├── centos.py │ │ │ └── ubuntu.py │ │ ├── kernel_factory │ │ │ ├── __init__.py │ │ │ ├── ubuntu.py │ │ │ └── centos.py │ │ ├── __init__.py │ │ ├── services │ │ │ └── __init__.py │ │ ├── files.py │ │ ├── kernel.py │ │ ├── sysctl.py │ │ ├── hugepage.py │ │ ├── decorators.py │ │ ├── templating.py │ │ └── strutils.py │ ├── contrib │ │ ├── hardening │ │ │ ├── defaults │ │ │ │ ├── __init__.py │ │ │ │ ├── apache.yaml.schema │ │ │ │ ├── mysql.yaml.schema │ │ │ │ ├── apache.yaml │ │ │ │ ├── ssh.yaml.schema │ │ │ │ ├── os.yaml.schema │ │ │ │ ├── ssh.yaml │ │ │ │ ├── mysql.yaml │ │ │ │ └── os.yaml │ │ │ ├── host │ │ │ │ ├── templates │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── 99-hardening.sh │ │ │ │ │ ├── 99-juju-hardening.conf │ │ │ │ │ ├── 10.hardcore.conf │ │ │ │ │ ├── securetty │ │ │ │ │ ├── passwdqc.conf │ │ │ │ │ ├── pinerolo_profile.sh │ │ │ │ │ ├── tally2 │ │ │ │ │ └── modules │ │ │ │ ├── __init__.py │ │ │ │ └── checks │ │ │ │ │ ├── apt.py │ │ │ │ │ ├── securetty.py │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── minimize_access.py │ │ │ │ │ ├── profile.py │ │ │ │ │ ├── limits.py │ │ │ │ │ ├── login.py │ │ │ │ │ └── pam.py │ │ │ ├── ssh │ │ │ │ ├── templates │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── ssh_config │ │ │ │ ├── __init__.py │ │ │ │ └── checks │ │ │ │ │ └── __init__.py │ │ │ ├── apache │ │ │ │ ├── templates │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── 99-hardening.conf │ │ │ │ │ └── alias.conf │ │ │ │ ├── __init__.py │ │ │ │ └── checks │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── config.py │ │ │ ├── mysql │ │ │ │ ├── templates │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── hardening.cnf │ │ │ │ ├── __init__.py │ │ │ │ └── checks │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── config.py │ │ │ ├── __init__.py │ │ │ ├── README.hardening.md │ │ │ ├── audits │ │ │ │ ├── __init__.py │ │ │ │ ├── apt.py │ │ │ │ └── apache.py │ │ │ ├── templating.py │ │ │ └── harden.py │ │ ├── __init__.py │ │ ├── hardware │ │ │ └── __init__.py │ │ ├── openstack │ │ │ ├── ha │ │ │ │ └── __init__.py │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── __init__.py │ │ │ │ └── check_deferred_restarts.py │ │ │ ├── templates │ │ │ │ ├── __init__.py │ │ │ │ └── section-ceph-bluestore-compression │ │ │ ├── exceptions.py │ │ │ └── alternatives.py │ │ ├── hahelpers │ │ │ ├── __init__.py │ │ │ └── apache.py │ │ ├── network │ │ │ └── __init__.py │ │ ├── storage │ │ │ ├── __init__.py │ │ │ └── linux │ │ │ │ ├── __init__.py │ │ │ │ ├── bcache.py │ │ │ │ └── loopback.py │ │ ├── charmsupport │ │ │ └── __init__.py │ │ └── python.py │ ├── fetch │ │ ├── python │ │ │ ├── __init__.py │ │ │ ├── version.py │ │ │ ├── debug.py │ │ │ └── rpdb.py │ │ ├── giturl.py │ │ └── bzrurl.py │ ├── cli │ │ ├── hookenv.py │ │ ├── host.py │ │ ├── commands.py │ │ ├── benchmark.py │ │ └── unitdata.py │ ├── osplatform.py │ └── __init__.py ├── osd-devices-storage-attached ├── osd-devices-storage-detaching ├── secrets-storage-relation-broken ├── secrets-storage-relation-changed ├── secrets-storage-relation-departed ├── secrets-storage-relation-joined ├── nrpe-external-master-relation-changed ├── nrpe-external-master-relation-joined ├── upgrade-charm ├── install_deps ├── install └── add-storage ├── lib └── charms_ceph │ └── __init__.py ├── .stestr.conf ├── TODO ├── setup.cfg ├── .gitreview ├── .zuul.yaml ├── hardening.yaml ├── templates ├── vaultlocker.conf.j2 ├── hdparm.conf └── ceph.conf ├── .gitignore ├── files ├── systemd │ └── crimson-osd@.service ├── udev │ └── 95-charm-ceph-osd.rules ├── nagios │ ├── collect_ceph_status.sh │ ├── check_ceph_osd_services.py │ ├── check_ceph_status.py │ └── collect_ceph_osd_services.py └── apparmor │ └── usr.bin.ceph-osd ├── osci.yaml ├── rename.sh ├── charmcraft.yaml ├── .project ├── .pydevproject ├── copyright ├── charm-helpers-hooks.yaml ├── Makefile ├── requirements.txt ├── tests └── tests.yaml ├── unit_tests ├── test_actions_list_disks.py ├── __init__.py ├── test_ceph_networking.py └── test_config.py ├── test-requirements.txt ├── metadata.yaml └── tox.ini /revision: -------------------------------------------------------------------------------- 1 | 15 -------------------------------------------------------------------------------- /actions/start: -------------------------------------------------------------------------------- 1 | service.py -------------------------------------------------------------------------------- /actions/stop: -------------------------------------------------------------------------------- 1 | service.py -------------------------------------------------------------------------------- /hooks/stop: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /actions/add-disk: -------------------------------------------------------------------------------- 1 | add_disk.py -------------------------------------------------------------------------------- /actions/osd-in: -------------------------------------------------------------------------------- 1 | osd_in_out.py -------------------------------------------------------------------------------- /actions/osd-out: -------------------------------------------------------------------------------- 1 | osd_in_out.py -------------------------------------------------------------------------------- /actions/zap-disk: -------------------------------------------------------------------------------- 1 | zap_disk.py -------------------------------------------------------------------------------- /hooks/start: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /lib/charms_ceph/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /actions/list-disks: -------------------------------------------------------------------------------- 1 | list_disks.py -------------------------------------------------------------------------------- /hooks/config-changed: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /hooks/install.real: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /hooks/storage.real: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /hooks/update-status: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /actions/blacklist-add-disk: -------------------------------------------------------------------------------- 1 | blacklist.py -------------------------------------------------------------------------------- /actions/remove-disk: -------------------------------------------------------------------------------- 1 | ./remove_disk.py -------------------------------------------------------------------------------- /hooks/post-series-upgrade: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /hooks/pre-series-upgrade: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /hooks/upgrade-charm.real: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /actions/blacklist-remove-disk: -------------------------------------------------------------------------------- 1 | blacklist.py -------------------------------------------------------------------------------- /hooks/mon-relation-changed: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /hooks/mon-relation-departed: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /actions/security-checklist: -------------------------------------------------------------------------------- 1 | security_checklist.py -------------------------------------------------------------------------------- /hooks/charmhelpers/core/host_factory/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hooks/osd-devices-storage-attached: -------------------------------------------------------------------------------- 1 | add-storage -------------------------------------------------------------------------------- /hooks/osd-devices-storage-detaching: -------------------------------------------------------------------------------- 1 | add-storage -------------------------------------------------------------------------------- /actions/get-availability-zone: -------------------------------------------------------------------------------- 1 | get_availability_zone.py -------------------------------------------------------------------------------- /actions/update-apparmor-and-restart-osds: -------------------------------------------------------------------------------- 1 | service.py -------------------------------------------------------------------------------- /hooks/charmhelpers/core/kernel_factory/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hooks/secrets-storage-relation-broken: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /hooks/secrets-storage-relation-changed: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /hooks/secrets-storage-relation-departed: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /hooks/secrets-storage-relation-joined: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/defaults/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hooks/nrpe-external-master-relation-changed: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /hooks/nrpe-external-master-relation-joined: -------------------------------------------------------------------------------- 1 | ceph_hooks.py -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/ssh/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.stestr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_path=./unit_tests 3 | top_dir=./ 4 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/apache/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/mysql/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Ceph OSD Charm 2 | ============== 3 | 4 | * Nothing TODO! 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | verbosity=2 3 | with-coverage=1 4 | cover-erase=1 5 | cover-package=hooks 6 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/charm-ceph-osd.git 5 | -------------------------------------------------------------------------------- /.zuul.yaml: -------------------------------------------------------------------------------- 1 | - project: 2 | templates: 3 | - openstack-python3-charm-jobs 4 | - openstack-cover-jobs 5 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/templates/99-hardening.sh: -------------------------------------------------------------------------------- 1 | TMOUT={{ tmout }} 2 | readonly TMOUT 3 | export TMOUT 4 | 5 | readonly HISTFILE 6 | -------------------------------------------------------------------------------- /hardening.yaml: -------------------------------------------------------------------------------- 1 | # Overrides file for contrib.hardening. See README.hardening in 2 | # contrib.hardening for info on how to use this file. 3 | ssh: 4 | server: 5 | use_pam: 'yes' # juju requires this 6 | -------------------------------------------------------------------------------- /templates/vaultlocker.conf.j2: -------------------------------------------------------------------------------- 1 | # vaultlocker configuration from ceph-osd charm 2 | [vault] 3 | url = {{ vault_url }} 4 | approle = {{ role_id }} 5 | backend = {{ secret_backend }} 6 | secret_id = {{ secret_id }} 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | .project 3 | .tox 4 | .testrepository 5 | .stestr 6 | bin 7 | *.sw[nop] 8 | *.charm 9 | *.pyc 10 | .unit-state.db 11 | .idea 12 | func-results.json 13 | *__pycache__ 14 | .settings 15 | -------------------------------------------------------------------------------- /templates/hdparm.conf: -------------------------------------------------------------------------------- 1 | {% for uuid,settings in drive_settings.items() %} 2 | /dev/disk/by-uuid/{{ uuid }} { 3 | {% for key, value in settings.items() %} 4 | {{ key }} = {{ value }} 5 | {% endfor %} 6 | } 7 | {% endfor %} -------------------------------------------------------------------------------- /files/systemd/crimson-osd@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Ceph object storage daemon crimson-osd.%i 3 | 4 | [Service] 5 | Environment=CLUSTER=ceph 6 | ExecStart=/usr/bin/crimson-osd -i %i 7 | ExecStop=/usr/bin/kill -QUIT $MAINPID 8 | User=ceph 9 | Group=ceph 10 | -------------------------------------------------------------------------------- /osci.yaml: -------------------------------------------------------------------------------- 1 | - project: 2 | templates: 3 | - charm-unit-jobs-py310 4 | - charm-functional-jobs 5 | vars: 6 | needs_charm_build: true 7 | charm_build_name: ceph-osd 8 | build_type: charmcraft 9 | charmcraft_channel: 3.x/beta 10 | -------------------------------------------------------------------------------- /hooks/upgrade-charm: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Wrapper to ensure that old python bytecode isn't hanging around 3 | # after we upgrade the charm with newer libraries 4 | find . -iname '*.pyc' -delete 5 | find . -name '__pycache__' -prune -exec rm -rf "{}" \; 6 | ./hooks/install_deps 7 | exec ./hooks/upgrade-charm.real 8 | -------------------------------------------------------------------------------- /rename.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | charm=$(grep "charm_build_name" osci.yaml | awk '{print $2}') 3 | echo "renaming ${charm}_*.charm to ${charm}.charm" 4 | echo -n "pwd: " 5 | pwd 6 | ls -al 7 | echo "Removing bad downloaded charm maybe?" 8 | if [[ -e "${charm}.charm" ]]; 9 | then 10 | rm "${charm}.charm" 11 | fi 12 | echo "Renaming charm here." 13 | mv ${charm}_*.charm ${charm}.charm 14 | -------------------------------------------------------------------------------- /charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: charm 2 | 3 | parts: 4 | charm: 5 | plugin: dump 6 | source: . 7 | 8 | base: ubuntu@24.04 9 | platforms: 10 | amd64: 11 | build-on: amd64 12 | build-for: amd64 13 | arm64: 14 | build-on: arm64 15 | build-for: arm64 16 | ppc64el: 17 | build-on: ppc64el 18 | build-for: ppc64el 19 | s390x: 20 | build-on: s390x 21 | build-for: s390x 22 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/defaults/apache.yaml.schema: -------------------------------------------------------------------------------- 1 | # NOTE: this schema must contain all valid keys from it's associated defaults 2 | # file. It is used to validate user-provided overrides. 3 | common: 4 | apache_dir: 5 | traceenable: 6 | 7 | hardening: 8 | allowed_http_methods: 9 | modules_to_disable: 10 | servertokens: 11 | honor_cipher_order: 12 | cipher_suite: 13 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # WARNING: This configuration file is maintained by Juju. Local changes may 3 | # be overwritten. 4 | ############################################################################### 5 | {% for key, value in sysctl_settings -%} 6 | {{ key }}={{ value }} 7 | {% endfor -%} 8 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ceph-osd 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /files/udev/95-charm-ceph-osd.rules: -------------------------------------------------------------------------------- 1 | # OSD LV (ceph-osd charm layout) 2 | ACTION=="add", SUBSYSTEM=="block", \ 3 | ENV{DEVTYPE}=="disk", \ 4 | ENV{DM_LV_NAME}=="osd-*", \ 5 | ENV{DM_VG_NAME}=="ceph-*", \ 6 | OWNER:="ceph", GROUP:="ceph", MODE:="660" 7 | ACTION=="change", SUBSYSTEM=="block", \ 8 | ENV{DEVTYPE}=="disk", \ 9 | ENV{DM_LV_NAME}=="osd-*", \ 10 | ENV{DM_VG_NAME}=="ceph-*", \ 11 | OWNER="ceph", GROUP="ceph", MODE="660" 12 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/kernel_factory/ubuntu.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | 4 | def persistent_modprobe(module): 5 | """Load a kernel module and configure for auto-load on reboot.""" 6 | with open('/etc/modules', 'r+') as modules: 7 | if module not in modules.read(): 8 | modules.write(module + "\n") 9 | 10 | 11 | def update_initramfs(version='all'): 12 | """Updates an initramfs image.""" 13 | return subprocess.check_call(["update-initramfs", "-k", version, "-u"]) 14 | -------------------------------------------------------------------------------- /hooks/install_deps: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Wrapper to ensure that python dependencies are installed before we get into 3 | # the python part of the hook execution 4 | 5 | declare -a DEPS=('dnspython' 'pyudev' 'netaddr' 'netifaces') 6 | 7 | check_and_install() { 8 | pkg="${1}-${2}" 9 | if ! dpkg -s ${pkg} 2>&1 > /dev/null; then 10 | apt-get -y install ${pkg} 11 | fi 12 | } 13 | 14 | PYTHON="python3" 15 | 16 | for dep in ${DEPS[@]}; do 17 | check_and_install ${PYTHON} ${dep} 18 | done 19 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/defaults/mysql.yaml.schema: -------------------------------------------------------------------------------- 1 | # NOTE: this schema must contain all valid keys from it's associated defaults 2 | # file. It is used to validate user-provided overrides. 3 | hardening: 4 | mysql-conf: 5 | hardening-conf: 6 | security: 7 | chroot: 8 | safe-user-create: 9 | secure-auth: 10 | skip-symbolic-links: 11 | skip-show-database: 12 | local-infile: 13 | allow-suspicious-udfs: 14 | automatic-sp-privileges: 15 | secure-file-priv: 16 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/templates/10.hardcore.conf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # WARNING: This configuration file is maintained by Juju. Local changes may 3 | # be overwritten. 4 | ############################################################################### 5 | {% if disable_core_dump -%} 6 | # Prevent core dumps for all users. These are usually only needed by developers and may contain sensitive information. 7 | * hard core 0 8 | {% endif %} -------------------------------------------------------------------------------- /files/nagios/collect_ceph_status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (C) 2014 Canonical 3 | # All Rights Reserved 4 | # Author: Jacek Nykis 5 | 6 | LOCK=/var/lock/ceph-status.lock 7 | lockfile-create -r2 --lock-name $LOCK > /dev/null 2>&1 8 | if [ $? -ne 0 ]; then 9 | exit 1 10 | fi 11 | trap "rm -f $LOCK > /dev/null 2>&1" exit 12 | 13 | DATA_DIR="/var/lib/nagios" 14 | if [ ! -d $DATA_DIR ]; then 15 | mkdir -p $DATA_DIR 16 | fi 17 | 18 | ceph status >${DATA_DIR}/cat-ceph-status.txt 19 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/templates/securetty: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # WARNING: This configuration file is maintained by Juju. Local changes may 3 | # be overwritten. 4 | ############################################################################### 5 | # A list of TTYs, from which root can log in 6 | # see `man securetty` for reference 7 | {% if ttys -%} 8 | {% for tty in ttys -%} 9 | {{ tty }} 10 | {% endfor -%} 11 | {% endif -%} 12 | -------------------------------------------------------------------------------- /hooks/install: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Wrapper to deal with newer Ubuntu versions that don't have py2 installed 3 | # by default. 4 | 5 | declare -a DEPS=('apt' 'pip' 'yaml' 'tabulate') 6 | 7 | check_and_install() { 8 | pkg="${1}-${2}" 9 | if ! dpkg -s ${pkg} 2>&1 > /dev/null; then 10 | apt-get -y install ${pkg} 11 | fi 12 | } 13 | 14 | PYTHON="python3" 15 | 16 | for dep in ${DEPS[@]}; do 17 | check_and_install ${PYTHON} ${dep} 18 | done 19 | 20 | ./hooks/install_deps 21 | exec ./hooks/install.real 22 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/templates/passwdqc.conf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # WARNING: This configuration file is maintained by Juju. Local changes may 3 | # be overwritten. 4 | ############################################################################### 5 | Name: passwdqc password strength enforcement 6 | Default: yes 7 | Priority: 1024 8 | Conflicts: cracklib 9 | Password-Type: Primary 10 | Password: 11 | requisite pam_passwdqc.so {{ auth_pam_passwdqc_options }} 12 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/templates/pinerolo_profile.sh: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # WARNING: This configuration file is maintained by Juju. Local changes may 3 | # be overwritten. 4 | ############################################################################### 5 | # Disable core dumps via soft limits for all users. Compliance to this setting 6 | # is voluntary and can be modified by users up to a hard limit. This setting is 7 | # a sane default. 8 | ulimit -S -c 0 > /dev/null 2>&1 9 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/mysql/templates/hardening.cnf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # WARNING: This configuration file is maintained by Juju. Local changes may 3 | # be overwritten. 4 | ############################################################################### 5 | [mysqld] 6 | {% for setting, value in mysql_settings -%} 7 | {% if value == 'True' -%} 8 | {{ setting }} 9 | {% elif value != 'None' and value != None -%} 10 | {{ setting }} = {{ value }} 11 | {% endif -%} 12 | {% endfor -%} 13 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | python 2.7 4 | Default 5 | 6 | /${PROJECT_DIR_NAME}/lib 7 | /${PROJECT_DIR_NAME}/unit_tests 8 | /${PROJECT_DIR_NAME}/tests 9 | /${PROJECT_DIR_NAME}/hooks 10 | /${PROJECT_DIR_NAME}/actions 11 | 12 | 13 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/kernel_factory/centos.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | 4 | 5 | def persistent_modprobe(module): 6 | """Load a kernel module and configure for auto-load on reboot.""" 7 | if not os.path.exists('/etc/rc.modules'): 8 | open('/etc/rc.modules', 'a') 9 | os.chmod('/etc/rc.modules', 111) 10 | with open('/etc/rc.modules', 'r+') as modules: 11 | if module not in modules.read(): 12 | modules.write('modprobe %s\n' % module) 13 | 14 | 15 | def update_initramfs(version='all'): 16 | """Updates an initramfs image.""" 17 | return subprocess.check_call(["dracut", "-f", version]) 18 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardware/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/openstack/ha/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Ltd 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hooks/charmhelpers/fetch/python/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2019 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hahelpers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/templates/tally2: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # WARNING: This configuration file is maintained by Juju. Local changes may 3 | # be overwritten. 4 | ############################################################################### 5 | Name: tally2 lockout after failed attempts enforcement 6 | Default: yes 7 | Priority: 1024 8 | Conflicts: cracklib 9 | Auth-Type: Primary 10 | Auth-Initial: 11 | required pam_tally2.so deny={{ auth_retries }} onerr=fail unlock_time={{ auth_lockout_time }} 12 | Account-Type: Primary 13 | Account-Initial: 14 | required pam_tally2.so 15 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/network/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/openstack/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/storage/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/charmsupport/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/storage/linux/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /actions/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Ltd 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import sys 16 | sys.path.append('hooks') 17 | sys.path.append('lib') 18 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/defaults/apache.yaml: -------------------------------------------------------------------------------- 1 | # NOTE: this file contains the default configuration for the 'apache' hardening 2 | # code. If you want to override any settings you must add them to a file 3 | # called hardening.yaml in the root directory of your charm using the 4 | # name 'apache' as the root key followed by any of the following with new 5 | # values. 6 | 7 | common: 8 | apache_dir: '/etc/apache2' 9 | 10 | hardening: 11 | traceenable: 'off' 12 | allowed_http_methods: "GET POST" 13 | modules_to_disable: [ cgi, cgid ] 14 | servertokens: 'Prod' 15 | honor_cipher_order: 'on' 16 | cipher_suite: 'ALL:+MEDIUM:+HIGH:!LOW:!MD5:!RC4:!eNULL:!aNULL:!3DES' 17 | -------------------------------------------------------------------------------- /copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0 2 | 3 | Files: * 4 | Copyright: 2012, Canonical Ltd. 5 | License: Apache-2.0 6 | Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | not use this file except in compliance with the License. You may obtain 8 | a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | License for the specific language governing permissions and limitations 16 | under the License. 17 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/services/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from .base import * # NOQA 16 | from .helpers import * # NOQA 17 | -------------------------------------------------------------------------------- /charm-helpers-hooks.yaml: -------------------------------------------------------------------------------- 1 | repo: https://github.com/juju/charm-helpers 2 | destination: hooks/charmhelpers 3 | include: 4 | - core 5 | - cli 6 | - osplatform 7 | - fetch 8 | - contrib.hahelpers: 9 | - apache 10 | - cluster 11 | - contrib.python 12 | - contrib.storage.linux 13 | - contrib.openstack 14 | - contrib.network.ip 15 | - contrib.openstack: 16 | - alternatives 17 | - audits 18 | - context 19 | - exceptions 20 | - ip 21 | - neutron 22 | - utils 23 | - contrib.charmsupport 24 | - contrib.hardening|inc=* 25 | - contrib.hardware 26 | - contrib.openstack.policyd 27 | - contrib.openstack.templates|inc=*/section-ceph-bluestore-compression 28 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from os import path 16 | 17 | TEMPLATES_DIR = path.join(path.dirname(__file__), 'templates') 18 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/ssh/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from os import path 16 | 17 | TEMPLATES_DIR = path.join(path.dirname(__file__), 'templates') 18 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/apache/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from os import path 16 | 17 | TEMPLATES_DIR = path.join(path.dirname(__file__), 'templates') 18 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/mysql/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from os import path 16 | 17 | TEMPLATES_DIR = path.join(path.dirname(__file__), 'templates') 18 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/openstack/files/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # dummy __init__.py to fool syncer into thinking this is a syncable python 16 | # module 17 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/openstack/templates/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # dummy __init__.py to fool syncer into thinking this is a syncable python 16 | # module 17 | -------------------------------------------------------------------------------- /hooks/add-storage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shim used to determine that the ceph packages have been installed 3 | # before running hook execution. The add-storage hook fires before 4 | # the install hook in order to provide storage for charms which need 5 | # it at install time, however the storage added for the ceph-osd 6 | # application will be used to create OSDs, which require the ceph 7 | # binaries, bootstrapping the node, etc. 8 | # 9 | # Note: this doesn't wait to ensure that ceph is bootstrapped because 10 | # that logic is already existing in the charm's hook. 11 | 12 | IFS='/' read -r -a array <<< "$JUJU_UNIT_NAME" 13 | LOCAL_UNIT="${array[0]}" 14 | charm_ceph_conf="/var/lib/charm/$LOCAL_UNIT/ceph.conf" 15 | 16 | if ! test -e $charm_ceph_conf; then 17 | juju-log "Ceph not yet installed." 18 | exit 0 19 | fi 20 | 21 | exec ./hooks/storage.real 22 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/python.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2019 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # deprecated aliases for backwards compatibility 16 | from charmhelpers.fetch.python import debug # noqa 17 | from charmhelpers.fetch.python import packages # noqa 18 | from charmhelpers.fetch.python import rpdb # noqa 19 | from charmhelpers.fetch.python import version # noqa 20 | -------------------------------------------------------------------------------- /hooks/charmhelpers/cli/hookenv.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from . import cmdline 16 | from charmhelpers.core import hookenv 17 | 18 | 19 | cmdline.subcommand('relation-id')(hookenv.relation_id._wrapped) 20 | cmdline.subcommand('service-name')(hookenv.service_name) 21 | cmdline.subcommand('remote-service-name')(hookenv.remote_service_name._wrapped) 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make 2 | PYTHON := /usr/bin/env python3 3 | 4 | lint: 5 | @tox -e pep8 6 | 7 | test: 8 | @echo Starting unit tests... 9 | @tox -e py27 10 | 11 | functional_test: 12 | @echo Starting Zaza functional tests... 13 | @tox -e func 14 | 15 | bin/charm_helpers_sync.py: 16 | @mkdir -p bin 17 | @curl -o bin/charm_helpers_sync.py https://raw.githubusercontent.com/juju/charm-helpers/master/tools/charm_helpers_sync/charm_helpers_sync.py 18 | 19 | 20 | bin/git_sync.py: 21 | @mkdir -p bin 22 | @wget -O bin/git_sync.py https://raw.githubusercontent.com/CanonicalLtd/git-sync/master/git_sync.py 23 | 24 | ch-sync: bin/charm_helpers_sync.py 25 | $(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-hooks.yaml 26 | 27 | ceph-sync: bin/git_sync.py 28 | $(PYTHON) bin/git_sync.py -d lib -s https://github.com/openstack/charms.ceph.git 29 | 30 | sync: ch-sync 31 | 32 | publish: lint 33 | bzr push lp:charms/ceph-osd 34 | bzr push lp:charms/trusty/ceph-osd 35 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/openstack/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Ltd 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | class OSContextError(Exception): 17 | """Raised when an error occurs during context generation. 18 | 19 | This exception is principally used in contrib.openstack.context 20 | """ 21 | pass 22 | 23 | 24 | class ServiceActionError(Exception): 25 | """Raised when a service action (stop/start/ etc) failed.""" 26 | pass 27 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is managed centrally by release-tools and should not be modified 2 | # within individual charm repos. See the 'global' dir contents for available 3 | # choices of *requirements.txt files for OpenStack Charms: 4 | # https://github.com/openstack-charmers/release-tools 5 | # 6 | # TODO: Distill the func test requirements from the lint/unit test 7 | # requirements. They are intertwined. Also, Zaza itself should specify 8 | # all of its own requirements and if it doesn't, fix it there. 9 | # 10 | pbr==5.6.0 11 | simplejson>=2.2.0 12 | netifaces>=0.10.4 13 | 14 | # NOTE: newer versions of cryptography require a Rust compiler to build, 15 | # see 16 | # * https://github.com/openstack-charmers/zaza/issues/421 17 | # * https://mail.python.org/pipermail/cryptography-dev/2021-January/001003.html 18 | # 19 | cryptography<3.4 20 | 21 | # Strange import error with newer netaddr: 22 | netaddr>0.7.16,<0.8.0 23 | 24 | Jinja2>=2.6 # BSD License (3 clause) 25 | six>=1.9.0 26 | 27 | dnspython 28 | 29 | psutil>=1.1.1,<2.0.0 30 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/apache/templates/99-hardening.conf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # WARNING: This configuration file is maintained by Juju. Local changes may 3 | # be overwritten. 4 | ############################################################################### 5 | 6 | 7 | 8 | # http://httpd.apache.org/docs/2.4/upgrading.html 9 | {% if apache_version > '2.2' -%} 10 | Require all granted 11 | {% else -%} 12 | Order Allow,Deny 13 | Deny from all 14 | {% endif %} 15 | 16 | 17 | 18 | 19 | Options -Indexes -FollowSymLinks 20 | AllowOverride None 21 | 22 | 23 | 24 | Options -Indexes -FollowSymLinks 25 | AllowOverride None 26 | 27 | 28 | TraceEnable {{ traceenable }} 29 | ServerTokens {{ servertokens }} 30 | 31 | SSLHonorCipherOrder {{ honor_cipher_order }} 32 | SSLCipherSuite {{ cipher_suite }} 33 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/defaults/ssh.yaml.schema: -------------------------------------------------------------------------------- 1 | # NOTE: this schema must contain all valid keys from it's associated defaults 2 | # file. It is used to validate user-provided overrides. 3 | common: 4 | service_name: 5 | network_ipv6_enable: 6 | ports: 7 | remote_hosts: 8 | client: 9 | package: 10 | cbc_required: 11 | weak_hmac: 12 | weak_kex: 13 | roaming: 14 | password_authentication: 15 | server: 16 | host_key_files: 17 | cbc_required: 18 | weak_hmac: 19 | weak_kex: 20 | allow_root_with_key: 21 | allow_tcp_forwarding: 22 | allow_agent_forwarding: 23 | allow_x11_forwarding: 24 | use_privilege_separation: 25 | listen_to: 26 | use_pam: 27 | package: 28 | password_authentication: 29 | alive_interval: 30 | alive_count: 31 | sftp_enable: 32 | sftp_group: 33 | sftp_chroot: 34 | deny_users: 35 | allow_users: 36 | deny_groups: 37 | allow_groups: 38 | print_motd: 39 | print_last_log: 40 | use_dns: 41 | max_auth_tries: 42 | max_sessions: 43 | -------------------------------------------------------------------------------- /tests/tests.yaml: -------------------------------------------------------------------------------- 1 | charm_name: ceph-osd 2 | 3 | gate_bundles: 4 | - noble-caracal 5 | 6 | smoke_bundles: 7 | - noble-caracal 8 | 9 | configure: 10 | - install: 11 | - zaza.openstack.charm_tests.glance.setup.add_lts_image 12 | 13 | tests: 14 | - install: 15 | - zaza.openstack.charm_tests.ceph.tests.CephLowLevelTest 16 | - zaza.openstack.charm_tests.ceph.tests.CephTest 17 | - zaza.openstack.charm_tests.ceph.osd.tests.SecurityTest 18 | - zaza.openstack.charm_tests.ceph.osd.tests.ServiceTest 19 | - zaza.openstack.charm_tests.ceph.tests.CephLowLevelTest 20 | - zaza.openstack.charm_tests.ceph.tests.CephTest 21 | - zaza.openstack.charm_tests.ceph.osd.tests.SecurityTest 22 | - zaza.openstack.charm_tests.ceph.osd.tests.ServiceTest 23 | # Charm upgrade, then re-run tests 24 | - zaza.charm_tests.lifecycle.tests.UpgradeCharmsToPath;ceph-osd 25 | - zaza.openstack.charm_tests.ceph.tests.CephLowLevelTest 26 | - zaza.openstack.charm_tests.ceph.tests.CephTest 27 | - zaza.openstack.charm_tests.ceph.osd.tests.SecurityTest 28 | - zaza.openstack.charm_tests.ceph.osd.tests.ServiceTest 29 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/defaults/os.yaml.schema: -------------------------------------------------------------------------------- 1 | # NOTE: this schema must contain all valid keys from it's associated defaults 2 | # file. It is used to validate user-provided overrides. 3 | general: 4 | desktop_enable: 5 | environment: 6 | extra_user_paths: 7 | umask: 8 | root_path: 9 | auth: 10 | pw_max_age: 11 | pw_min_age: 12 | retries: 13 | lockout_time: 14 | timeout: 15 | allow_homeless: 16 | pam_passwdqc_enable: 17 | pam_passwdqc_options: 18 | root_ttys: 19 | uid_min: 20 | gid_min: 21 | sys_uid_min: 22 | sys_uid_max: 23 | sys_gid_min: 24 | sys_gid_max: 25 | chfn_restrict: 26 | security: 27 | users_allow: 28 | suid_sgid_enforce: 29 | suid_sgid_blacklist: 30 | suid_sgid_whitelist: 31 | suid_sgid_dry_run_on_unknown: 32 | suid_sgid_remove_from_unknown: 33 | packages_clean: 34 | packages_list: 35 | kernel_enable_module_loading: 36 | kernel_enable_core_dump: 37 | ssh_tmout: 38 | sysctl: 39 | kernel_secure_sysrq: 40 | kernel_enable_sysrq: 41 | forwarding: 42 | ipv6_enable: 43 | arp_restricted: 44 | -------------------------------------------------------------------------------- /hooks/charmhelpers/cli/host.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from . import cmdline 16 | from charmhelpers.core import host 17 | 18 | 19 | @cmdline.subcommand() 20 | def mounts(): 21 | "List mounts" 22 | return host.mounts() 23 | 24 | 25 | @cmdline.subcommand_builder('service', description="Control system services") 26 | def service(subparser): 27 | subparser.add_argument("action", help="The action to perform (start, stop, etc...)") 28 | subparser.add_argument("service_name", help="Name of the service to control") 29 | return host.service 30 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/ssh/checks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from charmhelpers.core.hookenv import ( 16 | log, 17 | DEBUG, 18 | ) 19 | from charmhelpers.contrib.hardening.ssh.checks import config 20 | 21 | 22 | def run_ssh_checks(): 23 | log("Starting SSH hardening checks.", level=DEBUG) 24 | checks = config.get_audits() 25 | for check in checks: 26 | log("Running '%s' check" % (check.__class__.__name__), level=DEBUG) 27 | check.ensure_compliance() 28 | 29 | log("SSH hardening checks complete.", level=DEBUG) 30 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/mysql/checks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from charmhelpers.core.hookenv import ( 16 | log, 17 | DEBUG, 18 | ) 19 | from charmhelpers.contrib.hardening.mysql.checks import config 20 | 21 | 22 | def run_mysql_checks(): 23 | log("Starting MySQL hardening checks.", level=DEBUG) 24 | checks = config.get_audits() 25 | for check in checks: 26 | log("Running '%s' check" % (check.__class__.__name__), level=DEBUG) 27 | check.ensure_compliance() 28 | 29 | log("MySQL hardening checks complete.", level=DEBUG) 30 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/apache/checks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from charmhelpers.core.hookenv import ( 16 | log, 17 | DEBUG, 18 | ) 19 | from charmhelpers.contrib.hardening.apache.checks import config 20 | 21 | 22 | def run_apache_checks(): 23 | log("Starting Apache hardening checks.", level=DEBUG) 24 | checks = config.get_audits() 25 | for check in checks: 26 | log("Running '%s' check" % (check.__class__.__name__), level=DEBUG) 27 | check.ensure_compliance() 28 | 29 | log("Apache hardening checks complete.", level=DEBUG) 30 | -------------------------------------------------------------------------------- /unit_tests/test_actions_list_disks.py: -------------------------------------------------------------------------------- 1 | from actions import list_disks 2 | 3 | from test_utils import CharmTestCase 4 | 5 | 6 | class ListDisksActionTests(CharmTestCase): 7 | def setUp(self): 8 | super(ListDisksActionTests, self).setUp( 9 | list_disks, ['hookenv', 10 | 'charms_ceph', 11 | 'utils', 12 | 'os']) 13 | self.charms_ceph.utils.unmounted_disks.return_value = ['/dev/sda', 14 | '/dev/sdm'] 15 | 16 | def test_list_disks_journal_symbol_link(self): 17 | self.utils.get_journal_devices.return_value = {'/dev/disk/ceph/sdm'} 18 | self.os.path.realpath.return_value = '/dev/sdm' 19 | self.charms_ceph.utils.is_active_bluestore_device.return_value = False 20 | self.charms_ceph.utils.is_pristine_disk.return_value = False 21 | self.utils.get_blacklist.return_value = [] 22 | list_disks.list_disk() 23 | self.hookenv.action_set.assert_called_with({ 24 | 'disks': ['/dev/sda'], 25 | 'blacklist': [], 26 | 'non-pristine': ['/dev/sda'] 27 | }) 28 | -------------------------------------------------------------------------------- /hooks/charmhelpers/fetch/python/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # Copyright 2014-2015 Canonical Limited. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import sys 19 | 20 | __author__ = "Jorge Niedbalski " 21 | 22 | 23 | def current_version(): 24 | """Current system python version""" 25 | return sys.version_info 26 | 27 | 28 | def current_version_string(): 29 | """Current system python version as string major.minor.micro""" 30 | return "{0}.{1}.{2}".format(sys.version_info.major, 31 | sys.version_info.minor, 32 | sys.version_info.micro) 33 | -------------------------------------------------------------------------------- /hooks/charmhelpers/cli/commands.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This module loads sub-modules into the python runtime so they can be 17 | discovered via the inspect module. In order to prevent flake8 from (rightfully) 18 | telling us these are unused modules, throw a ' # noqa' at the end of each import 19 | so that the warning is suppressed. 20 | """ 21 | 22 | from . import CommandLine # noqa 23 | 24 | """ 25 | Import the sub-modules which have decorated subcommands to register with chlp. 26 | """ 27 | from . import host # noqa 28 | from . import benchmark # noqa 29 | from . import unitdata # noqa 30 | from . import hookenv # noqa 31 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/README.hardening.md: -------------------------------------------------------------------------------- 1 | # Juju charm-helpers hardening library 2 | 3 | ## Description 4 | 5 | This library provides multiple implementations of system and application 6 | hardening that conform to the standards of http://hardening.io/. 7 | 8 | Current implementations include: 9 | 10 | * OS 11 | * SSH 12 | * MySQL 13 | * Apache 14 | 15 | ## Requirements 16 | 17 | * Juju Charms 18 | 19 | ## Usage 20 | 21 | 1. Synchronise this library into your charm and add the harden() decorator 22 | (from contrib.hardening.harden) to any functions or methods you want to use 23 | to trigger hardening of your application/system. 24 | 25 | 2. Add a config option called 'harden' to your charm config.yaml and set it to 26 | a space-delimited list of hardening modules you want to run e.g. "os ssh" 27 | 28 | 3. Override any config defaults (contrib.hardening.defaults) by adding a file 29 | called hardening.yaml to your charm root containing the name(s) of the 30 | modules whose settings you want override at root level and then any settings 31 | with overrides e.g. 32 | 33 | os: 34 | general: 35 | desktop_enable: True 36 | 37 | 4. Now just run your charm as usual and hardening will be applied each time the 38 | hook runs. 39 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is managed centrally by release-tools and should not be modified 2 | # within individual charm repos. See the 'global' dir contents for available 3 | # choices of *requirements.txt files for OpenStack Charms: 4 | # https://github.com/openstack-charmers/release-tools 5 | # 6 | # TODO: Distill the func test requirements from the lint/unit test 7 | # requirements. They are intertwined. Also, Zaza itself should specify 8 | # all of its own requirements and if it doesn't, fix it there. 9 | # 10 | pyparsing<3.0.0 # aodhclient is pinned in zaza and needs pyparsing < 3.0.0, but cffi also needs it, so pin here. 11 | 12 | requests>=2.18.4 13 | 14 | stestr>=2.2.0 15 | 16 | # Dependency of stestr. Workaround for 17 | # https://github.com/mtreinish/stestr/issues/145 18 | cliff<3.0.0 19 | 20 | coverage>=4.5.2 21 | pyudev # for ceph-* charm unit tests (need to fix the ceph-* charm unit tests/mocking) 22 | git+https://github.com/openstack-charmers/zaza.git#egg=zaza 23 | git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack 24 | 25 | # Needed for charm-glance: 26 | git+https://opendev.org/openstack/tempest.git#egg=tempest 27 | 28 | croniter # needed for charm-rabbitmq-server unit tests 29 | psutil 30 | -------------------------------------------------------------------------------- /metadata.yaml: -------------------------------------------------------------------------------- 1 | name: ceph-osd 2 | summary: Highly scalable distributed storage - Ceph OSD storage 3 | maintainer: OpenStack Charmers 4 | provides: 5 | nrpe-external-master: 6 | interface: nrpe-external-master 7 | scope: container 8 | tags: 9 | - openstack 10 | - storage 11 | - file-servers 12 | - misc 13 | series: 14 | - noble 15 | description: | 16 | Ceph is a distributed storage and network file system designed to provide 17 | excellent performance, reliability, and scalability. 18 | . 19 | This charm provides the Ceph OSD personality for expanding storage capacity 20 | within a ceph deployment. 21 | docs: https://discourse.charmhub.io/t/ceph-osd-docs-index/10545 22 | extra-bindings: 23 | public: 24 | cluster: 25 | requires: 26 | mon: 27 | interface: ceph-osd 28 | secrets-storage: 29 | interface: vault-kv 30 | storage: 31 | osd-devices: 32 | type: block 33 | multiple: 34 | range: 0- 35 | minimum-size: 1G 36 | osd-journals: 37 | type: block 38 | multiple: 39 | range: 0- 40 | bluestore-db: 41 | type: block 42 | multiple: 43 | range: 0- 44 | bluestore-wal: 45 | type: block 46 | multiple: 47 | range: 0- 48 | cache-devices: 49 | type: block 50 | multiple: 51 | range: 0- 52 | minimum-size: 10G 53 | -------------------------------------------------------------------------------- /unit_tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Ltd 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import sys 16 | from unittest.mock import MagicMock 17 | from unittest import mock 18 | 19 | sys.path.append('hooks') 20 | sys.path.append('lib') 21 | sys.path.append('actions') 22 | sys.path.append('unit_tests') 23 | 24 | sys.modules["tabulate"] = MagicMock() 25 | 26 | # Patch out lsb_release() and get_platform() as unit tests should be fully 27 | # insulated from the underlying platform. Unit tests assume that the system is 28 | # ubuntu jammy. 29 | mock.patch( 30 | 'charmhelpers.osplatform.get_platform', return_value='ubuntu' 31 | ).start() 32 | mock.patch( 33 | 'charmhelpers.core.host.lsb_release', 34 | return_value={ 35 | 'DISTRIB_CODENAME': 'jammy' 36 | }).start() 37 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/apache/templates/alias.conf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # WARNING: This configuration file is maintained by Juju. Local changes may 3 | # be overwritten. 4 | ############################################################################### 5 | 6 | # 7 | # Aliases: Add here as many aliases as you need (with no limit). The format is 8 | # Alias fakename realname 9 | # 10 | # Note that if you include a trailing / on fakename then the server will 11 | # require it to be present in the URL. So "/icons" isn't aliased in this 12 | # example, only "/icons/". If the fakename is slash-terminated, then the 13 | # realname must also be slash terminated, and if the fakename omits the 14 | # trailing slash, the realname must also omit it. 15 | # 16 | # We include the /icons/ alias for FancyIndexed directory listings. If 17 | # you do not use FancyIndexing, you may comment this out. 18 | # 19 | Alias /icons/ "{{ apache_icondir }}/" 20 | 21 | 22 | Options -Indexes -MultiViews -FollowSymLinks 23 | AllowOverride None 24 | {% if apache_version == '2.4' -%} 25 | Require all granted 26 | {% else -%} 27 | Order allow,deny 28 | Allow from all 29 | {% endif %} 30 | 31 | 32 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/checks/apt.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from charmhelpers.contrib.hardening.utils import get_settings 16 | from charmhelpers.contrib.hardening.audits.apt import ( 17 | AptConfig, 18 | RestrictedPackages, 19 | ) 20 | 21 | 22 | def get_audits(): 23 | """Get OS hardening apt audits. 24 | 25 | :returns: dictionary of audits 26 | """ 27 | audits = [AptConfig([{'key': 'APT::Get::AllowUnauthenticated', 28 | 'expected': 'false'}])] 29 | 30 | settings = get_settings('os') 31 | clean_packages = settings['security']['packages_clean'] 32 | if clean_packages: 33 | security_packages = settings['security']['packages_list'] 34 | if security_packages: 35 | audits.append(RestrictedPackages(security_packages)) 36 | 37 | return audits 38 | -------------------------------------------------------------------------------- /hooks/charmhelpers/cli/benchmark.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from . import cmdline 16 | from charmhelpers.contrib.benchmark import Benchmark 17 | 18 | 19 | @cmdline.subcommand(command_name='benchmark-start') 20 | def start(): 21 | Benchmark.start() 22 | 23 | 24 | @cmdline.subcommand(command_name='benchmark-finish') 25 | def finish(): 26 | Benchmark.finish() 27 | 28 | 29 | @cmdline.subcommand_builder('benchmark-composite', description="Set the benchmark composite score") 30 | def service(subparser): 31 | subparser.add_argument("value", help="The composite score.") 32 | subparser.add_argument("units", help="The units the composite score represents, i.e., 'reads/sec'.") 33 | subparser.add_argument("direction", help="'asc' if a lower score is better, 'desc' if a higher score is better.") 34 | return Benchmark.set_composite_score 35 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/checks/securetty.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from charmhelpers.contrib.hardening.audits.file import TemplatedFile 16 | from charmhelpers.contrib.hardening.host import TEMPLATES_DIR 17 | from charmhelpers.contrib.hardening import utils 18 | 19 | 20 | def get_audits(): 21 | """Get OS hardening Secure TTY audits. 22 | 23 | :returns: dictionary of audits 24 | """ 25 | audits = [] 26 | audits.append(TemplatedFile('/etc/securetty', SecureTTYContext(), 27 | template_dir=TEMPLATES_DIR, 28 | mode=0o0400, user='root', group='root')) 29 | return audits 30 | 31 | 32 | class SecureTTYContext(object): 33 | 34 | def __call__(self): 35 | settings = utils.get_settings('os') 36 | ctxt = {'ttys': settings['auth']['root_ttys']} 37 | return ctxt 38 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/openstack/templates/section-ceph-bluestore-compression: -------------------------------------------------------------------------------- 1 | {# section header omitted as options can belong to multiple sections #} 2 | {% if bluestore_compression_algorithm -%} 3 | bluestore compression algorithm = {{ bluestore_compression_algorithm }} 4 | {% endif -%} 5 | {% if bluestore_compression_mode -%} 6 | bluestore compression mode = {{ bluestore_compression_mode }} 7 | {% endif -%} 8 | {% if bluestore_compression_required_ratio -%} 9 | bluestore compression required ratio = {{ bluestore_compression_required_ratio }} 10 | {% endif -%} 11 | {% if bluestore_compression_min_blob_size -%} 12 | bluestore compression min blob size = {{ bluestore_compression_min_blob_size }} 13 | {% endif -%} 14 | {% if bluestore_compression_min_blob_size_hdd -%} 15 | bluestore compression min blob size hdd = {{ bluestore_compression_min_blob_size_hdd }} 16 | {% endif -%} 17 | {% if bluestore_compression_min_blob_size_ssd -%} 18 | bluestore compression min blob size ssd = {{ bluestore_compression_min_blob_size_ssd }} 19 | {% endif -%} 20 | {% if bluestore_compression_max_blob_size -%} 21 | bluestore compression max blob size = {{ bluestore_compression_max_blob_size }} 22 | {% endif -%} 23 | {% if bluestore_compression_max_blob_size_hdd -%} 24 | bluestore compression max blob size hdd = {{ bluestore_compression_max_blob_size_hdd }} 25 | {% endif -%} 26 | {% if bluestore_compression_max_blob_size_ssd -%} 27 | bluestore compression max blob size ssd = {{ bluestore_compression_max_blob_size_ssd }} 28 | {% endif -%} 29 | -------------------------------------------------------------------------------- /actions/security_checklist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2019 Canonical Ltd 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import sys 18 | 19 | sys.path.append('hooks') 20 | 21 | import charmhelpers.contrib.openstack.audits as audits 22 | from charmhelpers.contrib.openstack.audits import ( 23 | openstack_security_guide, 24 | ) 25 | 26 | 27 | # Via the openstack_security_guide above, we are running the following 28 | # security assertions automatically: 29 | # 30 | # - validate-file-ownership 31 | # - validate-file-permissions 32 | 33 | 34 | def main(): 35 | config = { 36 | 'audit_type': audits.AuditType.OpenStackSecurityGuide, 37 | 'files': openstack_security_guide.FILE_ASSERTIONS['ceph-osd'], 38 | 'excludes': [ 39 | 'validate-uses-keystone', 40 | 'validate-uses-tls-for-glance', 41 | 'validate-uses-tls-for-keystone', 42 | ], 43 | } 44 | return audits.action_parse_results(audits.run(config)) 45 | 46 | 47 | if __name__ == "__main__": 48 | sys.exit(main()) 49 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/checks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from charmhelpers.core.hookenv import ( 16 | log, 17 | DEBUG, 18 | ) 19 | from charmhelpers.contrib.hardening.host.checks import ( 20 | apt, 21 | limits, 22 | login, 23 | minimize_access, 24 | pam, 25 | profile, 26 | securetty, 27 | suid_sgid, 28 | sysctl 29 | ) 30 | 31 | 32 | def run_os_checks(): 33 | log("Starting OS hardening checks.", level=DEBUG) 34 | checks = apt.get_audits() 35 | checks.extend(limits.get_audits()) 36 | checks.extend(login.get_audits()) 37 | checks.extend(minimize_access.get_audits()) 38 | checks.extend(pam.get_audits()) 39 | checks.extend(profile.get_audits()) 40 | checks.extend(securetty.get_audits()) 41 | checks.extend(suid_sgid.get_audits()) 42 | checks.extend(sysctl.get_audits()) 43 | 44 | for check in checks: 45 | log("Running '%s' check" % (check.__class__.__name__), level=DEBUG) 46 | check.ensure_compliance() 47 | 48 | log("OS hardening checks complete.", level=DEBUG) 49 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/openstack/alternatives.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ''' Helper for managing alternatives for file conflict resolution ''' 16 | 17 | import subprocess 18 | import shutil 19 | import os 20 | 21 | 22 | def install_alternative(name, target, source, priority=50): 23 | ''' Install alternative configuration ''' 24 | if (os.path.exists(target) and not os.path.islink(target)): 25 | # Move existing file/directory away before installing 26 | shutil.move(target, '{}.bak'.format(target)) 27 | cmd = [ 28 | 'update-alternatives', '--force', '--install', 29 | target, name, source, str(priority) 30 | ] 31 | subprocess.check_call(cmd) 32 | 33 | 34 | def remove_alternative(name, source): 35 | """Remove an installed alternative configuration file 36 | 37 | :param name: string name of the alternative to remove 38 | :param source: string full path to alternative to remove 39 | """ 40 | cmd = [ 41 | 'update-alternatives', '--remove', 42 | name, source 43 | ] 44 | subprocess.check_call(cmd) 45 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/defaults/ssh.yaml: -------------------------------------------------------------------------------- 1 | # NOTE: this file contains the default configuration for the 'ssh' hardening 2 | # code. If you want to override any settings you must add them to a file 3 | # called hardening.yaml in the root directory of your charm using the 4 | # name 'ssh' as the root key followed by any of the following with new 5 | # values. 6 | 7 | common: 8 | service_name: 'ssh' 9 | network_ipv6_enable: False # (type:boolean) 10 | ports: [22] 11 | remote_hosts: [] 12 | 13 | client: 14 | package: 'openssh-client' 15 | cbc_required: False # (type:boolean) 16 | weak_hmac: False # (type:boolean) 17 | weak_kex: False # (type:boolean) 18 | roaming: False 19 | password_authentication: 'no' 20 | 21 | server: 22 | host_key_files: ['/etc/ssh/ssh_host_rsa_key', '/etc/ssh/ssh_host_dsa_key', 23 | '/etc/ssh/ssh_host_ecdsa_key'] 24 | cbc_required: False # (type:boolean) 25 | weak_hmac: False # (type:boolean) 26 | weak_kex: False # (type:boolean) 27 | allow_root_with_key: False # (type:boolean) 28 | allow_tcp_forwarding: 'no' 29 | allow_agent_forwarding: 'no' 30 | allow_x11_forwarding: 'no' 31 | use_privilege_separation: 'sandbox' 32 | listen_to: ['0.0.0.0'] 33 | use_pam: 'no' 34 | package: 'openssh-server' 35 | password_authentication: 'no' 36 | alive_interval: '600' 37 | alive_count: '3' 38 | sftp_enable: False # (type:boolean) 39 | sftp_group: 'sftponly' 40 | sftp_chroot: '/home/%u' 41 | deny_users: [] 42 | allow_users: [] 43 | deny_groups: [] 44 | allow_groups: [] 45 | print_motd: 'no' 46 | print_last_log: 'no' 47 | use_dns: 'no' 48 | max_auth_tries: 2 49 | max_sessions: 10 50 | -------------------------------------------------------------------------------- /hooks/charmhelpers/fetch/python/debug.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # Copyright 2014-2015 Canonical Limited. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import atexit 19 | import sys 20 | 21 | from charmhelpers.fetch.python.rpdb import Rpdb 22 | from charmhelpers.core.hookenv import ( 23 | open_port, 24 | close_port, 25 | ERROR, 26 | log 27 | ) 28 | 29 | __author__ = "Jorge Niedbalski " 30 | 31 | DEFAULT_ADDR = "0.0.0.0" 32 | DEFAULT_PORT = 4444 33 | 34 | 35 | def _error(message): 36 | log(message, level=ERROR) 37 | 38 | 39 | def set_trace(addr=DEFAULT_ADDR, port=DEFAULT_PORT): 40 | """ 41 | Set a trace point using the remote debugger 42 | """ 43 | atexit.register(close_port, port) 44 | try: 45 | log("Starting a remote python debugger session on %s:%s" % (addr, 46 | port)) 47 | open_port(port) 48 | debugger = Rpdb(addr=addr, port=port) 49 | debugger.set_trace(sys._getframe().f_back) 50 | except Exception: 51 | _error("Cannot start a remote debug session on %s:%s" % (addr, 52 | port)) 53 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/defaults/mysql.yaml: -------------------------------------------------------------------------------- 1 | # NOTE: this file contains the default configuration for the 'mysql' hardening 2 | # code. If you want to override any settings you must add them to a file 3 | # called hardening.yaml in the root directory of your charm using the 4 | # name 'mysql' as the root key followed by any of the following with new 5 | # values. 6 | 7 | hardening: 8 | mysql-conf: /etc/mysql/my.cnf 9 | hardening-conf: /etc/mysql/conf.d/hardening.cnf 10 | 11 | security: 12 | # @see http://www.symantec.com/connect/articles/securing-mysql-step-step 13 | # @see http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_chroot 14 | chroot: None 15 | 16 | # @see http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_safe-user-create 17 | safe-user-create: 1 18 | 19 | # @see http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_secure-auth 20 | secure-auth: 1 21 | 22 | # @see http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_symbolic-links 23 | skip-symbolic-links: 1 24 | 25 | # @see http://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_skip-show-database 26 | skip-show-database: True 27 | 28 | # @see http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_local_infile 29 | local-infile: 0 30 | 31 | # @see https://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_allow-suspicious-udfs 32 | allow-suspicious-udfs: 0 33 | 34 | # @see https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_automatic_sp_privileges 35 | automatic-sp-privileges: 0 36 | 37 | # @see https://dev.mysql.com/doc/refman/5.7/en/server-options.html#option_mysqld_secure-file-priv 38 | secure-file-priv: /tmp 39 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/files.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2014-2015 Canonical Limited. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | __author__ = 'Jorge Niedbalski ' 19 | 20 | import os 21 | import subprocess 22 | 23 | 24 | def sed(filename, before, after, flags='g'): 25 | """ 26 | Search and replaces the given pattern on filename. 27 | 28 | :param filename: relative or absolute file path. 29 | :param before: expression to be replaced (see 'man sed') 30 | :param after: expression to replace with (see 'man sed') 31 | :param flags: sed-compatible regex flags in example, to make 32 | the search and replace case insensitive, specify ``flags="i"``. 33 | The ``g`` flag is always specified regardless, so you do not 34 | need to remember to include it when overriding this parameter. 35 | :returns: If the sed command exit code was zero then return, 36 | otherwise raise CalledProcessError. 37 | """ 38 | expression = r's/{0}/{1}/{2}'.format(before, 39 | after, flags) 40 | 41 | return subprocess.check_call(["sed", "-i", "-r", "-e", 42 | expression, 43 | os.path.expanduser(filename)]) 44 | -------------------------------------------------------------------------------- /hooks/charmhelpers/cli/unitdata.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from . import cmdline 16 | from charmhelpers.core import unitdata 17 | 18 | 19 | @cmdline.subcommand_builder('unitdata', description="Store and retrieve data") 20 | def unitdata_cmd(subparser): 21 | nested = subparser.add_subparsers() 22 | 23 | get_cmd = nested.add_parser('get', help='Retrieve data') 24 | get_cmd.add_argument('key', help='Key to retrieve the value of') 25 | get_cmd.set_defaults(action='get', value=None) 26 | 27 | getrange_cmd = nested.add_parser('getrange', help='Retrieve multiple data') 28 | getrange_cmd.add_argument('key', metavar='prefix', 29 | help='Prefix of the keys to retrieve') 30 | getrange_cmd.set_defaults(action='getrange', value=None) 31 | 32 | set_cmd = nested.add_parser('set', help='Store data') 33 | set_cmd.add_argument('key', help='Key to set') 34 | set_cmd.add_argument('value', help='Value to store') 35 | set_cmd.set_defaults(action='set') 36 | 37 | def _unitdata_cmd(action, key, value): 38 | if action == 'get': 39 | return unitdata.kv().get(key) 40 | elif action == 'getrange': 41 | return unitdata.kv().getrange(key) 42 | elif action == 'set': 43 | unitdata.kv().set(key, value) 44 | unitdata.kv().flush() 45 | return '' 46 | return _unitdata_cmd 47 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/checks/minimize_access.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from charmhelpers.contrib.hardening.audits.file import ( 16 | FilePermissionAudit, 17 | ReadOnly, 18 | ) 19 | from charmhelpers.contrib.hardening import utils 20 | 21 | 22 | def get_audits(): 23 | """Get OS hardening access audits. 24 | 25 | :returns: dictionary of audits 26 | """ 27 | audits = [] 28 | settings = utils.get_settings('os') 29 | 30 | # Remove write permissions from $PATH folders for all regular users. 31 | # This prevents changing system-wide commands from normal users. 32 | path_folders = {'/usr/local/sbin', 33 | '/usr/local/bin', 34 | '/usr/sbin', 35 | '/usr/bin', 36 | '/bin'} 37 | extra_user_paths = settings['environment']['extra_user_paths'] 38 | path_folders.update(extra_user_paths) 39 | audits.append(ReadOnly(path_folders)) 40 | 41 | # Only allow the root user to have access to the shadow file. 42 | audits.append(FilePermissionAudit('/etc/shadow', 'root', 'root', 0o0600)) 43 | 44 | if 'change_user' not in settings['security']['users_allow']: 45 | # su should only be accessible to user and group root, unless it is 46 | # expressly defined to allow users to change to root via the 47 | # security_users_allow config option. 48 | audits.append(FilePermissionAudit('/bin/su', 'root', 'root', 0o750)) 49 | 50 | return audits 51 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/checks/profile.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from charmhelpers.contrib.hardening.audits.file import TemplatedFile 16 | from charmhelpers.contrib.hardening.host import TEMPLATES_DIR 17 | from charmhelpers.contrib.hardening import utils 18 | 19 | 20 | def get_audits(): 21 | """Get OS hardening profile audits. 22 | 23 | :returns: dictionary of audits 24 | """ 25 | audits = [] 26 | 27 | settings = utils.get_settings('os') 28 | # If core dumps are not enabled, then don't allow core dumps to be 29 | # created as they may contain sensitive information. 30 | if not settings['security']['kernel_enable_core_dump']: 31 | audits.append(TemplatedFile('/etc/profile.d/pinerolo_profile.sh', 32 | ProfileContext(), 33 | template_dir=TEMPLATES_DIR, 34 | mode=0o0755, user='root', group='root')) 35 | if settings['security']['ssh_tmout']: 36 | audits.append(TemplatedFile('/etc/profile.d/99-hardening.sh', 37 | ProfileContext(), 38 | template_dir=TEMPLATES_DIR, 39 | mode=0o0644, user='root', group='root')) 40 | return audits 41 | 42 | 43 | class ProfileContext(object): 44 | 45 | def __call__(self): 46 | settings = utils.get_settings('os') 47 | ctxt = {'ssh_tmout': 48 | settings['security']['ssh_tmout']} 49 | return ctxt 50 | -------------------------------------------------------------------------------- /hooks/charmhelpers/fetch/python/rpdb.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Remote Python Debugger (pdb wrapper).""" 16 | 17 | import pdb 18 | import socket 19 | import sys 20 | 21 | __author__ = "Bertrand Janin " 22 | __version__ = "0.1.3" 23 | 24 | 25 | class Rpdb(pdb.Pdb): 26 | 27 | def __init__(self, addr="127.0.0.1", port=4444): 28 | """Initialize the socket and initialize pdb.""" 29 | 30 | # Backup stdin and stdout before replacing them by the socket handle 31 | self.old_stdout = sys.stdout 32 | self.old_stdin = sys.stdin 33 | 34 | # Open a 'reusable' socket to let the webapp reload on the same port 35 | self.skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 36 | self.skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) 37 | self.skt.bind((addr, port)) 38 | self.skt.listen(1) 39 | (clientsocket, address) = self.skt.accept() 40 | handle = clientsocket.makefile('rw') 41 | pdb.Pdb.__init__(self, completekey='tab', stdin=handle, stdout=handle) 42 | sys.stdout = sys.stdin = handle 43 | 44 | def shutdown(self): 45 | """Revert stdin and stdout, close the socket.""" 46 | sys.stdout = self.old_stdout 47 | sys.stdin = self.old_stdin 48 | self.skt.close() 49 | self.set_continue() 50 | 51 | def do_continue(self, arg): 52 | """Stop all operation on ``continue``.""" 53 | self.shutdown() 54 | return 1 55 | 56 | do_EOF = do_quit = do_exit = do_c = do_cont = do_continue 57 | -------------------------------------------------------------------------------- /actions/list_disks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2016 Canonical Ltd 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """ 18 | List disks 19 | 20 | The 'disks' key is populated with block devices that are known by udev, 21 | are not mounted and not mentioned in 'osd-journal' configuration option. 22 | 23 | The 'blacklist' key is populated with osd-devices in the blacklist stored 24 | in the local kv store of this specific unit. 25 | 26 | The 'non-pristine' key is populated with block devices that are known by 27 | udev, are not mounted, not mentioned in 'osd-journal' configuration option 28 | and are currently not eligible for use because of presence of foreign data. 29 | """ 30 | 31 | import sys 32 | import os 33 | 34 | sys.path.append('hooks/') 35 | sys.path.append('lib/') 36 | 37 | import charmhelpers.core.hookenv as hookenv 38 | 39 | import charms_ceph.utils 40 | import utils 41 | 42 | 43 | def list_disk(): 44 | non_pristine = [] 45 | osd_journal = [] 46 | for journal in utils.get_journal_devices(): 47 | osd_journal.append(os.path.realpath(journal)) 48 | 49 | for dev in list(set(charms_ceph.utils.unmounted_disks()) - 50 | set(osd_journal)): 51 | if (not charms_ceph.utils.is_active_bluestore_device(dev) and 52 | not charms_ceph.utils.is_pristine_disk(dev)): 53 | non_pristine.append(dev) 54 | 55 | hookenv.action_set({ 56 | 'disks': list(set(charms_ceph.utils.unmounted_disks()) - 57 | set(osd_journal)), 58 | 'blacklist': utils.get_blacklist(), 59 | 'non-pristine': non_pristine, 60 | }) 61 | 62 | 63 | if __name__ == '__main__': 64 | list_disk() 65 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/defaults/os.yaml: -------------------------------------------------------------------------------- 1 | # NOTE: this file contains the default configuration for the 'os' hardening 2 | # code. If you want to override any settings you must add them to a file 3 | # called hardening.yaml in the root directory of your charm using the 4 | # name 'os' as the root key followed by any of the following with new 5 | # values. 6 | 7 | general: 8 | desktop_enable: False # (type:boolean) 9 | 10 | environment: 11 | extra_user_paths: [] 12 | umask: 027 13 | root_path: / 14 | 15 | auth: 16 | pw_max_age: 60 17 | # discourage password cycling 18 | pw_min_age: 7 19 | retries: 5 20 | lockout_time: 600 21 | timeout: 60 22 | allow_homeless: False # (type:boolean) 23 | pam_passwdqc_enable: True # (type:boolean) 24 | pam_passwdqc_options: 'min=disabled,disabled,16,12,8' 25 | root_ttys: 26 | console 27 | tty1 28 | tty2 29 | tty3 30 | tty4 31 | tty5 32 | tty6 33 | uid_min: 1000 34 | gid_min: 1000 35 | sys_uid_min: 100 36 | sys_uid_max: 999 37 | sys_gid_min: 100 38 | sys_gid_max: 999 39 | chfn_restrict: 40 | 41 | security: 42 | users_allow: [] 43 | suid_sgid_enforce: True # (type:boolean) 44 | # user-defined blacklist and whitelist 45 | suid_sgid_blacklist: [] 46 | suid_sgid_whitelist: [] 47 | # if this is True, remove any suid/sgid bits from files that were not in the whitelist 48 | suid_sgid_dry_run_on_unknown: False # (type:boolean) 49 | suid_sgid_remove_from_unknown: False # (type:boolean) 50 | # remove packages with known issues 51 | packages_clean: True # (type:boolean) 52 | packages_list: 53 | xinetd 54 | inetd 55 | ypserv 56 | telnet-server 57 | rsh-server 58 | rsync 59 | kernel_enable_module_loading: True # (type:boolean) 60 | kernel_enable_core_dump: False # (type:boolean) 61 | ssh_tmout: 300 62 | 63 | sysctl: 64 | kernel_secure_sysrq: 244 # 4 + 16 + 32 + 64 + 128 65 | kernel_enable_sysrq: False # (type:boolean) 66 | forwarding: False # (type:boolean) 67 | ipv6_enable: False # (type:boolean) 68 | arp_restricted: True # (type:boolean) 69 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/host_factory/centos.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import yum 3 | import os 4 | 5 | from charmhelpers.core.strutils import BasicStringComparator 6 | 7 | 8 | class CompareHostReleases(BasicStringComparator): 9 | """Provide comparisons of Host releases. 10 | 11 | Use in the form of 12 | 13 | if CompareHostReleases(release) > 'trusty': 14 | # do something with mitaka 15 | """ 16 | 17 | def __init__(self, item): 18 | raise NotImplementedError( 19 | "CompareHostReleases() is not implemented for CentOS") 20 | 21 | 22 | def service_available(service_name): 23 | # """Determine whether a system service is available.""" 24 | if os.path.isdir('/run/systemd/system'): 25 | cmd = ['systemctl', 'is-enabled', service_name] 26 | else: 27 | cmd = ['service', service_name, 'is-enabled'] 28 | return subprocess.call(cmd) == 0 29 | 30 | 31 | def add_new_group(group_name, system_group=False, gid=None): 32 | cmd = ['groupadd'] 33 | if gid: 34 | cmd.extend(['--gid', str(gid)]) 35 | if system_group: 36 | cmd.append('-r') 37 | cmd.append(group_name) 38 | subprocess.check_call(cmd) 39 | 40 | 41 | def lsb_release(): 42 | """Return /etc/os-release in a dict.""" 43 | d = {} 44 | with open('/etc/os-release', 'r') as lsb: 45 | for l in lsb: 46 | s = l.split('=') 47 | if len(s) != 2: 48 | continue 49 | d[s[0].strip()] = s[1].strip() 50 | return d 51 | 52 | 53 | def cmp_pkgrevno(package, revno, pkgcache=None): 54 | """Compare supplied revno with the revno of the installed package. 55 | 56 | * 1 => Installed revno is greater than supplied arg 57 | * 0 => Installed revno is the same as supplied arg 58 | * -1 => Installed revno is less than supplied arg 59 | 60 | This function imports YumBase function if the pkgcache argument 61 | is None. 62 | """ 63 | if not pkgcache: 64 | y = yum.YumBase() 65 | packages = y.doPackageLists() 66 | pkgcache = {i.Name: i.version for i in packages['installed']} 67 | pkg = pkgcache[package] 68 | if pkg > revno: 69 | return 1 70 | if pkg < revno: 71 | return -1 72 | return 0 73 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/checks/limits.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from charmhelpers.contrib.hardening.audits.file import ( 16 | DirectoryPermissionAudit, 17 | TemplatedFile, 18 | ) 19 | from charmhelpers.contrib.hardening.host import TEMPLATES_DIR 20 | from charmhelpers.contrib.hardening import utils 21 | 22 | 23 | def get_audits(): 24 | """Get OS hardening security limits audits. 25 | 26 | :returns: dictionary of audits 27 | """ 28 | audits = [] 29 | settings = utils.get_settings('os') 30 | 31 | # Ensure that the /etc/security/limits.d directory is only writable 32 | # by the root user, but others can execute and read. 33 | audits.append(DirectoryPermissionAudit('/etc/security/limits.d', 34 | user='root', group='root', 35 | mode=0o755)) 36 | 37 | # If core dumps are not enabled, then don't allow core dumps to be 38 | # created as they may contain sensitive information. 39 | if not settings['security']['kernel_enable_core_dump']: 40 | audits.append(TemplatedFile('/etc/security/limits.d/10.hardcore.conf', 41 | SecurityLimitsContext(), 42 | template_dir=TEMPLATES_DIR, 43 | user='root', group='root', mode=0o0440)) 44 | return audits 45 | 46 | 47 | class SecurityLimitsContext(object): 48 | 49 | def __call__(self): 50 | settings = utils.get_settings('os') 51 | ctxt = {'disable_core_dump': 52 | not settings['security']['kernel_enable_core_dump']} 53 | return ctxt 54 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/audits/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | class BaseAudit(object): # NO-QA 17 | """Base class for hardening checks. 18 | 19 | The lifecycle of a hardening check is to first check to see if the system 20 | is in compliance for the specified check. If it is not in compliance, the 21 | check method will return a value which will be supplied to the. 22 | """ 23 | def __init__(self, *args, **kwargs): 24 | self.unless = kwargs.get('unless', None) 25 | super(BaseAudit, self).__init__() 26 | 27 | def ensure_compliance(self): 28 | """Checks to see if the current hardening check is in compliance or 29 | not. 30 | 31 | If the check that is performed is not in compliance, then an exception 32 | should be raised. 33 | """ 34 | pass 35 | 36 | def _take_action(self): 37 | """Determines whether to perform the action or not. 38 | 39 | Checks whether or not an action should be taken. This is determined by 40 | the truthy value for the unless parameter. If unless is a callback 41 | method, it will be invoked with no parameters in order to determine 42 | whether or not the action should be taken. Otherwise, the truthy value 43 | of the unless attribute will determine if the action should be 44 | performed. 45 | """ 46 | # Do the action if there isn't an unless override. 47 | if self.unless is None: 48 | return True 49 | 50 | # Invoke the callback if there is one. 51 | if hasattr(self.unless, '__call__'): 52 | return not self.unless() 53 | 54 | return not self.unless 55 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/ssh/templates/ssh_config: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # WARNING: This configuration file is maintained by Juju. Local changes may 3 | # be overwritten. 4 | ############################################################################### 5 | # This is the ssh client system-wide configuration file. See 6 | # ssh_config(5) for more information. This file provides defaults for 7 | # users, and the values can be changed in per-user configuration files 8 | # or on the command line. 9 | 10 | # Configuration data is parsed as follows: 11 | # 1. command line options 12 | # 2. user-specific file 13 | # 3. system-wide file 14 | # Any configuration value is only changed the first time it is set. 15 | # Thus, host-specific definitions should be at the beginning of the 16 | # configuration file, and defaults at the end. 17 | 18 | # Site-wide defaults for some commonly used options. For a comprehensive 19 | # list of available options, their meanings and defaults, please see the 20 | # ssh_config(5) man page. 21 | 22 | # Restrict the following configuration to be limited to this Host. 23 | {% if remote_hosts -%} 24 | Host {{ ' '.join(remote_hosts) }} 25 | {% endif %} 26 | ForwardAgent no 27 | ForwardX11 no 28 | ForwardX11Trusted yes 29 | RhostsRSAAuthentication no 30 | RSAAuthentication yes 31 | PasswordAuthentication {{ password_auth_allowed }} 32 | HostbasedAuthentication no 33 | GSSAPIAuthentication no 34 | GSSAPIDelegateCredentials no 35 | GSSAPIKeyExchange no 36 | GSSAPITrustDNS no 37 | BatchMode no 38 | CheckHostIP yes 39 | AddressFamily {{ addr_family }} 40 | ConnectTimeout 0 41 | StrictHostKeyChecking ask 42 | IdentityFile ~/.ssh/identity 43 | IdentityFile ~/.ssh/id_rsa 44 | IdentityFile ~/.ssh/id_dsa 45 | # The port at the destination should be defined 46 | {% for port in ports -%} 47 | Port {{ port }} 48 | {% endfor %} 49 | Protocol 2 50 | Cipher 3des 51 | {% if ciphers -%} 52 | Ciphers {{ ciphers }} 53 | {%- endif %} 54 | {% if macs -%} 55 | MACs {{ macs }} 56 | {%- endif %} 57 | {% if kexs -%} 58 | KexAlgorithms {{ kexs }} 59 | {%- endif %} 60 | EscapeChar ~ 61 | Tunnel no 62 | TunnelDevice any:any 63 | PermitLocalCommand no 64 | VisualHostKey no 65 | RekeyLimit 1G 1h 66 | SendEnv LANG LC_* 67 | HashKnownHosts yes 68 | {% if roaming -%} 69 | UseRoaming {{ roaming }} 70 | {% endif %} 71 | -------------------------------------------------------------------------------- /files/nagios/check_ceph_osd_services.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (C) 2018 Canonical 4 | # All Rights Reserved 5 | # Author: Alex Kavanagh 6 | 7 | import os 8 | import sys 9 | from datetime import datetime, timedelta 10 | 11 | CRON_CHECK_TMPFILE = 'ceph-osd-checks' 12 | NAGIOS_HOME = '/var/lib/nagios' 13 | CACHE_MAX_AGE = timedelta(minutes=10) 14 | 15 | STATE_OK = 0 16 | STATE_WARNING = 1 17 | STATE_CRITICAL = 2 18 | STATE_UNKNOWN = 3 19 | 20 | 21 | def run_main(): 22 | """Process the CRON_CHECK_TMP_FILE and see if any line is not OK. 23 | 24 | If a line is not OK, the main returns STATE_CRITICAL. 25 | If there are no lines, or the file doesn't exist, it returns STATE_UNKNOWN 26 | Otherwise it returns STATE_OK. 27 | 28 | :returns: nagios state 0,2 or 3 29 | """ 30 | _tmp_file = os.path.join(NAGIOS_HOME, CRON_CHECK_TMPFILE) 31 | 32 | if not os.path.isfile(_tmp_file): 33 | print("File '{}' doesn't exist".format(_tmp_file)) 34 | return STATE_UNKNOWN 35 | 36 | try: 37 | s = os.stat(_tmp_file) 38 | if datetime.now() - datetime.fromtimestamp(s.st_mtime) > CACHE_MAX_AGE: 39 | print("Status file is older than {}".format(CACHE_MAX_AGE)) 40 | return STATE_CRITICAL 41 | except Exception as e: 42 | print("Something went wrong grabbing stats for the file: {}".format( 43 | str(e))) 44 | return STATE_UNKNOWN 45 | 46 | try: 47 | with open(_tmp_file, 'rt') as f: 48 | lines = f.readlines() 49 | except Exception as e: 50 | print("Something went wrong reading the file: {}".format(str(e))) 51 | return STATE_UNKNOWN 52 | 53 | if not lines: 54 | print("checked status file is empty: {}".format(_tmp_file)) 55 | return STATE_UNKNOWN 56 | 57 | # finally, check that the file contains all ok lines. Unfortunately, it's 58 | # not consistent across releases, but what is consistent is that the check 59 | # command in the collect phase does fail, and so the start of the line is 60 | # 'Failed' 61 | state = STATE_OK 62 | for line in lines: 63 | print(line, end='') 64 | if line.startswith('Failed'): 65 | state = STATE_CRITICAL 66 | 67 | return state 68 | 69 | 70 | if __name__ == '__main__': 71 | sys.exit(run_main()) 72 | -------------------------------------------------------------------------------- /files/nagios/check_ceph_status.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2014 Canonical 4 | # All Rights Reserved 5 | # Author: Jacek Nykis 6 | 7 | import re 8 | import argparse 9 | import subprocess 10 | import nagios_plugin 11 | 12 | 13 | def check_ceph_status(args): 14 | if args.status_file: 15 | nagios_plugin.check_file_freshness(args.status_file, 3600) 16 | with open(args.status_file, "rt", encoding='UTF-8') as f: 17 | lines = f.readlines() 18 | else: 19 | lines = (subprocess 20 | .check_output(["ceph", "status"]) 21 | .decode('UTF-8') 22 | .split('\n')) 23 | status_data = dict( 24 | line.strip().split(' ', 1) for line in lines if len(line) > 1) 25 | 26 | if ('health' not in status_data or 27 | 'monmap' not in status_data or 28 | 'osdmap' not in status_data): 29 | raise nagios_plugin.UnknownError('UNKNOWN: status data is incomplete') 30 | 31 | if status_data['health'] != 'HEALTH_OK': 32 | msg = 'CRITICAL: ceph health status: "{}'.format(status_data['health']) 33 | if (len(status_data['health'].split(' '))) == 1: 34 | a = iter(lines) 35 | for line in a: 36 | if re.search('health', line) is not None: 37 | msg1 = next(a) 38 | msg += " " 39 | msg += msg1.strip() 40 | break 41 | msg += '"' 42 | raise nagios_plugin.CriticalError(msg) 43 | 44 | osds = re.search(r"^.*: (\d+) osds: (\d+) up, (\d+) in", 45 | status_data['osdmap']) 46 | if osds.group(1) > osds.group(2): # not all OSDs are "up" 47 | msg = 'CRITICAL: Some OSDs are not up. Total: {}, up: {}'.format( 48 | osds.group(1), osds.group(2)) 49 | raise nagios_plugin.CriticalError(msg) 50 | print("All OK") 51 | 52 | 53 | if __name__ == '__main__': 54 | parser = argparse.ArgumentParser(description='Check ceph status') 55 | parser.add_argument('-f', 56 | '--file', 57 | dest='status_file', 58 | default=False, 59 | help='Optional file with "ceph status" output') 60 | args = parser.parse_args() 61 | nagios_plugin.try_check(check_ceph_status, args) 62 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/templates/modules: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # WARNING: This configuration file is maintained by Juju. Local changes may 3 | # be overwritten. 4 | ############################################################################### 5 | # /etc/modules: kernel modules to load at boot time. 6 | # 7 | # This file contains the names of kernel modules that should be loaded 8 | # at boot time, one per line. Lines beginning with "#" are ignored. 9 | # Parameters can be specified after the module name. 10 | 11 | # Arch 12 | # ---- 13 | # 14 | # Modules for certains builds, contains support modules and some CPU-specific optimizations. 15 | 16 | {% if arch == "x86_64" -%} 17 | # Optimize for x86_64 cryptographic features 18 | twofish-x86_64-3way 19 | twofish-x86_64 20 | aes-x86_64 21 | salsa20-x86_64 22 | blowfish-x86_64 23 | {% endif -%} 24 | 25 | {% if cpuVendor == "intel" -%} 26 | # Intel-specific optimizations 27 | ghash-clmulni-intel 28 | aesni-intel 29 | kvm-intel 30 | {% endif -%} 31 | 32 | {% if cpuVendor == "amd" -%} 33 | # AMD-specific optimizations 34 | kvm-amd 35 | {% endif -%} 36 | 37 | kvm 38 | 39 | 40 | # Crypto 41 | # ------ 42 | 43 | # Some core modules which comprise strong cryptography. 44 | blowfish_common 45 | blowfish_generic 46 | ctr 47 | cts 48 | lrw 49 | lzo 50 | rmd160 51 | rmd256 52 | rmd320 53 | serpent 54 | sha512_generic 55 | twofish_common 56 | twofish_generic 57 | xts 58 | zlib 59 | 60 | 61 | # Drivers 62 | # ------- 63 | 64 | # Basics 65 | lp 66 | rtc 67 | loop 68 | 69 | # Filesystems 70 | ext2 71 | btrfs 72 | 73 | {% if desktop_enable -%} 74 | # Desktop 75 | psmouse 76 | snd 77 | snd_ac97_codec 78 | snd_intel8x0 79 | snd_page_alloc 80 | snd_pcm 81 | snd_timer 82 | soundcore 83 | usbhid 84 | {% endif -%} 85 | 86 | # Lib 87 | # --- 88 | xz 89 | 90 | 91 | # Net 92 | # --- 93 | 94 | # All packets needed for netfilter rules (ie iptables, ebtables). 95 | ip_tables 96 | x_tables 97 | iptable_filter 98 | iptable_nat 99 | 100 | # Targets 101 | ipt_LOG 102 | ipt_REJECT 103 | 104 | # Modules 105 | xt_connlimit 106 | xt_tcpudp 107 | xt_recent 108 | xt_limit 109 | xt_conntrack 110 | nf_conntrack 111 | nf_conntrack_ipv4 112 | nf_defrag_ipv4 113 | xt_state 114 | nf_nat 115 | 116 | # Addons 117 | xt_pknock -------------------------------------------------------------------------------- /hooks/charmhelpers/osplatform.py: -------------------------------------------------------------------------------- 1 | import platform 2 | import os 3 | 4 | 5 | def get_platform(): 6 | """Return the current OS platform. 7 | 8 | For example: if current os platform is Ubuntu then a string "ubuntu" 9 | will be returned (which is the name of the module). 10 | This string is used to decide which platform module should be imported. 11 | """ 12 | current_platform = _get_current_platform() 13 | 14 | if "Ubuntu" in current_platform: 15 | return "ubuntu" 16 | elif "CentOS" in current_platform: 17 | return "centos" 18 | elif "debian" in current_platform or "Debian" in current_platform: 19 | # Stock Python does not detect Ubuntu and instead returns debian. 20 | # Or at least it does in some build environments like Travis CI 21 | return "ubuntu" 22 | elif "elementary" in current_platform: 23 | # ElementaryOS fails to run tests locally without this. 24 | return "ubuntu" 25 | elif "Pop!_OS" in current_platform: 26 | # Pop!_OS also fails to run tests locally without this. 27 | return "ubuntu" 28 | else: 29 | raise RuntimeError("This module is not supported on {}." 30 | .format(current_platform)) 31 | 32 | 33 | def _get_current_platform(): 34 | """Return the current platform information for the OS. 35 | 36 | Attempts to lookup linux distribution information from the platform 37 | module for releases of python < 3.7. For newer versions of python, 38 | the platform is determined from the /etc/os-release file. 39 | """ 40 | # linux_distribution is deprecated and will be removed in Python 3.7 41 | # Warnings *not* disabled, as we certainly need to fix this. 42 | if hasattr(platform, 'linux_distribution'): 43 | tuple_platform = platform.linux_distribution() 44 | current_platform = tuple_platform[0] 45 | else: 46 | current_platform = _get_platform_from_fs() 47 | 48 | return current_platform 49 | 50 | 51 | def _get_platform_from_fs(): 52 | """Get Platform from /etc/os-release.""" 53 | with open(os.path.join(os.sep, 'etc', 'os-release')) as fin: 54 | content = dict( 55 | line.split('=', 1) 56 | for line in fin.read().splitlines() 57 | if '=' in line 58 | ) 59 | for k, v in content.items(): 60 | content[k] = v.strip('"') 61 | return content["NAME"] 62 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/kernel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2014-2015 Canonical Limited. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import re 19 | import subprocess 20 | 21 | from charmhelpers.osplatform import get_platform 22 | from charmhelpers.core.hookenv import ( 23 | log, 24 | INFO 25 | ) 26 | 27 | __platform__ = get_platform() 28 | if __platform__ == "ubuntu": 29 | from charmhelpers.core.kernel_factory.ubuntu import ( # NOQA:F401 30 | persistent_modprobe, 31 | update_initramfs, 32 | ) # flake8: noqa -- ignore F401 for this import 33 | elif __platform__ == "centos": 34 | from charmhelpers.core.kernel_factory.centos import ( # NOQA:F401 35 | persistent_modprobe, 36 | update_initramfs, 37 | ) # flake8: noqa -- ignore F401 for this import 38 | 39 | __author__ = "Jorge Niedbalski " 40 | 41 | 42 | def modprobe(module, persist=True): 43 | """Load a kernel module and configure for auto-load on reboot.""" 44 | cmd = ['modprobe', module] 45 | 46 | log('Loading kernel module %s' % module, level=INFO) 47 | 48 | subprocess.check_call(cmd) 49 | if persist: 50 | persistent_modprobe(module) 51 | 52 | 53 | def rmmod(module, force=False): 54 | """Remove a module from the linux kernel""" 55 | cmd = ['rmmod'] 56 | if force: 57 | cmd.append('-f') 58 | cmd.append(module) 59 | log('Removing kernel module %s' % module, level=INFO) 60 | return subprocess.check_call(cmd) 61 | 62 | 63 | def lsmod(): 64 | """Shows what kernel modules are currently loaded""" 65 | return subprocess.check_output(['lsmod'], 66 | universal_newlines=True) 67 | 68 | 69 | def is_module_loaded(module): 70 | """Checks if a kernel module is already loaded""" 71 | matches = re.findall('^%s[ ]+' % module, lsmod(), re.M) 72 | return len(matches) > 0 73 | -------------------------------------------------------------------------------- /files/apparmor/usr.bin.ceph-osd: -------------------------------------------------------------------------------- 1 | # vim:syntax=apparmor 2 | #include 3 | 4 | /usr/bin/ceph-osd { 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /usr/bin/ceph-osd mr, 11 | 12 | capability setgid, 13 | capability setuid, 14 | capability dac_override, 15 | capability dac_read_search, 16 | 17 | network inet stream, 18 | network inet6 stream, 19 | 20 | /etc/ceph/* r, 21 | /var/lib/charm/*/ceph.conf r, 22 | 23 | owner @{PROC}/@{pids}/auxv r, 24 | owner @{PROC}/@{pids}/net/dev r, 25 | owner @{PROC}/@{pids}/task/*/comm rw, 26 | 27 | @{PROC}/loadavg r, 28 | @{PROC}/1/cmdline r, 29 | @{PROC}/partitions r, 30 | @{PROC}/sys/kernel/random/uuid r, 31 | 32 | /var/lib/ceph/** rwkl, 33 | /srv/ceph/** rwkl, 34 | 35 | /var/log/ceph/* rwk, 36 | 37 | /{,var/}run/ceph/* rwk, 38 | /{,var/}tmp/ r, 39 | 40 | / r, 41 | /dev/ r, 42 | /dev/** rwk, 43 | /run/udev/data/* r, 44 | /sys/bus/nd/devices/ r, 45 | /sys/bus/nd/devices/** r, 46 | /sys/devices/** r, 47 | 48 | /run/blkid/blkid.tab r, 49 | 50 | /bin/dash rix, 51 | 52 | /usr/bin/lsb_release rix, 53 | /usr/share/distro-info/** r, 54 | /etc/lsb-release r, 55 | /etc/debian_version r, 56 | 57 | /usr/bin/sudo Px -> ceph-osd-sudo, 58 | } 59 | 60 | profile ceph-osd-sudo flags=(attach_disconnected) { 61 | #include 62 | #include 63 | #include 64 | #include 65 | 66 | capability audit_write, 67 | capability setgid, 68 | capability setuid, 69 | capability sys_resource, 70 | 71 | /usr/bin/sudo r, 72 | /usr/libexec/sudo/* mr, 73 | 74 | /etc/default/locale r, 75 | /etc/environment r, 76 | /etc/security/limits.d/ r, 77 | /etc/security/limits.d/* r, 78 | /etc/sudo.conf r, 79 | /etc/sudoers r, 80 | /etc/sudoers.d/ r, 81 | /etc/sudoers.d/* r, 82 | 83 | owner @{PROC}/1/limits r, 84 | owner @{PROC}/@{pids}/stat r, 85 | 86 | /usr/sbin/nvme Cx, 87 | /usr/sbin/smartctl Cx, 88 | 89 | profile /usr/sbin/nvme { 90 | #include 91 | 92 | /usr/sbin/nvme r, 93 | } 94 | 95 | profile /usr/sbin/smartctl { 96 | #include 97 | 98 | capability sys_admin, 99 | capability sys_rawio, 100 | 101 | /usr/sbin/smartctl r, 102 | /var/lib/smartmontools/** r, 103 | 104 | /dev/* r, 105 | /sys/devices/** r, 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/storage/linux/bcache.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import os 15 | import json 16 | 17 | from charmhelpers.core.hookenv import log 18 | 19 | stats_intervals = ['stats_day', 'stats_five_minute', 20 | 'stats_hour', 'stats_total'] 21 | 22 | SYSFS = '/sys' 23 | 24 | 25 | class Bcache(object): 26 | """Bcache behaviour 27 | """ 28 | 29 | def __init__(self, cachepath): 30 | self.cachepath = cachepath 31 | 32 | @classmethod 33 | def fromdevice(cls, devname): 34 | return cls('{}/block/{}/bcache'.format(SYSFS, devname)) 35 | 36 | def __str__(self): 37 | return self.cachepath 38 | 39 | def get_stats(self, interval): 40 | """Get cache stats 41 | """ 42 | intervaldir = 'stats_{}'.format(interval) 43 | path = "{}/{}".format(self.cachepath, intervaldir) 44 | out = dict() 45 | for elem in os.listdir(path): 46 | out[elem] = open('{}/{}'.format(path, elem)).read().strip() 47 | return out 48 | 49 | 50 | def get_bcache_fs(): 51 | """Return all cache sets 52 | """ 53 | cachesetroot = "{}/fs/bcache".format(SYSFS) 54 | try: 55 | dirs = os.listdir(cachesetroot) 56 | except OSError: 57 | log("No bcache fs found") 58 | return [] 59 | cacheset = set([Bcache('{}/{}'.format(cachesetroot, d)) for d in dirs if not d.startswith('register')]) 60 | return cacheset 61 | 62 | 63 | def get_stats_action(cachespec, interval): 64 | """Action for getting bcache statistics for a given cachespec. 65 | Cachespec can either be a device name, eg. 'sdb', which will retrieve 66 | cache stats for the given device, or 'global', which will retrieve stats 67 | for all cachesets 68 | """ 69 | if cachespec == 'global': 70 | caches = get_bcache_fs() 71 | else: 72 | caches = [Bcache.fromdevice(cachespec)] 73 | res = dict((c.cachepath, c.get_stats(interval)) for c in caches) 74 | return json.dumps(res, indent=4, separators=(',', ': ')) 75 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/templating.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | 17 | from charmhelpers.core.hookenv import ( 18 | log, 19 | DEBUG, 20 | WARNING, 21 | ) 22 | 23 | try: 24 | from jinja2 import FileSystemLoader, Environment 25 | except ImportError: 26 | from charmhelpers.fetch import apt_install 27 | from charmhelpers.fetch import apt_update 28 | apt_update(fatal=True) 29 | apt_install('python3-jinja2', fatal=True) 30 | from jinja2 import FileSystemLoader, Environment 31 | 32 | 33 | # NOTE: function separated from main rendering code to facilitate easier 34 | # mocking in unit tests. 35 | def write(path, data): 36 | with open(path, 'wb') as out: 37 | out.write(data) 38 | 39 | 40 | def get_template_path(template_dir, path): 41 | """Returns the template file which would be used to render the path. 42 | 43 | The path to the template file is returned. 44 | :param template_dir: the directory the templates are located in 45 | :param path: the file path to be written to. 46 | :returns: path to the template file 47 | """ 48 | return os.path.join(template_dir, os.path.basename(path)) 49 | 50 | 51 | def render_and_write(template_dir, path, context): 52 | """Renders the specified template into the file. 53 | 54 | :param template_dir: the directory to load the template from 55 | :param path: the path to write the templated contents to 56 | :param context: the parameters to pass to the rendering engine 57 | """ 58 | env = Environment(loader=FileSystemLoader(template_dir)) 59 | template_file = os.path.basename(path) 60 | template = env.get_template(template_file) 61 | log('Rendering from template: %s' % template.name, level=DEBUG) 62 | rendered_content = template.render(context) 63 | if not rendered_content: 64 | log("Render returned None - skipping '%s'" % path, 65 | level=WARNING) 66 | return 67 | 68 | write(path, rendered_content.encode('utf-8').strip()) 69 | log('Wrote template %s' % path, level=DEBUG) 70 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/sysctl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2014-2015 Canonical Limited. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import yaml 19 | 20 | from subprocess import check_call, CalledProcessError 21 | 22 | from charmhelpers.core.hookenv import ( 23 | log, 24 | DEBUG, 25 | ERROR, 26 | WARNING, 27 | ) 28 | 29 | from charmhelpers.core.host import is_container 30 | 31 | __author__ = 'Jorge Niedbalski R. ' 32 | 33 | 34 | def create(sysctl_dict, sysctl_file, ignore=False): 35 | """Creates a sysctl.conf file from a YAML associative array 36 | 37 | :param sysctl_dict: a dict or YAML-formatted string of sysctl 38 | options eg "{ 'kernel.max_pid': 1337 }" 39 | :type sysctl_dict: str 40 | :param sysctl_file: path to the sysctl file to be saved 41 | :type sysctl_file: str or unicode 42 | :param ignore: If True, ignore "unknown variable" errors. 43 | :type ignore: bool 44 | :returns: None 45 | """ 46 | if type(sysctl_dict) is not dict: 47 | try: 48 | sysctl_dict_parsed = yaml.safe_load(sysctl_dict) 49 | except yaml.YAMLError: 50 | log("Error parsing YAML sysctl_dict: {}".format(sysctl_dict), 51 | level=ERROR) 52 | return 53 | else: 54 | sysctl_dict_parsed = sysctl_dict 55 | 56 | with open(sysctl_file, "w") as fd: 57 | for key, value in sysctl_dict_parsed.items(): 58 | fd.write("{}={}\n".format(key, value)) 59 | 60 | log("Updating sysctl_file: {} values: {}".format(sysctl_file, 61 | sysctl_dict_parsed), 62 | level=DEBUG) 63 | 64 | call = ["sysctl", "-p", sysctl_file] 65 | if ignore: 66 | call.append("-e") 67 | 68 | try: 69 | check_call(call) 70 | except CalledProcessError as e: 71 | if is_container(): 72 | log("Error setting some sysctl keys in this container: {}".format(e.output), 73 | level=WARNING) 74 | else: 75 | raise e 76 | -------------------------------------------------------------------------------- /unit_tests/test_ceph_networking.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Ltd 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import test_utils 16 | import charmhelpers.core.hookenv as hookenv 17 | import utils as ceph_utils 18 | 19 | TO_PATCH_SPACES = [ 20 | 'network_get_primary_address', 21 | 'log', 22 | 'get_host_ip', 23 | 'config', 24 | 'get_network_addrs', 25 | 'cached', 26 | ] 27 | 28 | 29 | class CephNetworkSpaceTestCase(test_utils.CharmTestCase): 30 | def setUp(self): 31 | super(CephNetworkSpaceTestCase, self).setUp(ceph_utils, 32 | TO_PATCH_SPACES) 33 | self.config.side_effect = self.test_config.get 34 | 35 | def tearDown(self): 36 | # Reset @cached cache 37 | hookenv.cache = {} 38 | 39 | def test_no_network_space_support(self): 40 | self.get_host_ip.return_value = '192.168.2.1' 41 | self.network_get_primary_address.side_effect = NotImplementedError 42 | self.assertEqual(ceph_utils.get_cluster_addr(), 43 | '192.168.2.1') 44 | self.assertEqual(ceph_utils.get_public_addr(), 45 | '192.168.2.1') 46 | 47 | def test_public_network_space(self): 48 | self.network_get_primary_address.return_value = '10.20.40.2' 49 | self.assertEqual(ceph_utils.get_public_addr(), 50 | '10.20.40.2') 51 | self.network_get_primary_address.assert_called_with('public') 52 | self.config.assert_called_with('ceph-public-network') 53 | 54 | def test_cluster_network_space(self): 55 | self.network_get_primary_address.return_value = '10.20.50.2' 56 | self.assertEqual(ceph_utils.get_cluster_addr(), 57 | '10.20.50.2') 58 | self.network_get_primary_address.assert_called_with('cluster') 59 | self.config.assert_called_with('ceph-cluster-network') 60 | 61 | def test_config_options_in_use(self): 62 | self.get_network_addrs.return_value = ['192.122.20.2'] 63 | self.test_config.set('ceph-cluster-network', '192.122.20.0/24') 64 | self.assertEqual(ceph_utils.get_cluster_addr(), 65 | '192.122.20.2') 66 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/checks/login.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from charmhelpers.contrib.hardening.audits.file import TemplatedFile 16 | from charmhelpers.contrib.hardening.host import TEMPLATES_DIR 17 | from charmhelpers.contrib.hardening import utils 18 | 19 | 20 | def get_audits(): 21 | """Get OS hardening login.defs audits. 22 | 23 | :returns: dictionary of audits 24 | """ 25 | audits = [TemplatedFile('/etc/login.defs', LoginContext(), 26 | template_dir=TEMPLATES_DIR, 27 | user='root', group='root', mode=0o0444)] 28 | return audits 29 | 30 | 31 | class LoginContext(object): 32 | 33 | def __call__(self): 34 | settings = utils.get_settings('os') 35 | 36 | # Octal numbers in yaml end up being turned into decimal, 37 | # so check if the umask is entered as a string (e.g. '027') 38 | # or as an octal umask as we know it (e.g. 002). If its not 39 | # a string assume it to be octal and turn it into an octal 40 | # string. 41 | umask = settings['environment']['umask'] 42 | if not isinstance(umask, str): 43 | umask = '%s' % oct(umask) 44 | 45 | ctxt = { 46 | 'additional_user_paths': 47 | settings['environment']['extra_user_paths'], 48 | 'umask': umask, 49 | 'pwd_max_age': settings['auth']['pw_max_age'], 50 | 'pwd_min_age': settings['auth']['pw_min_age'], 51 | 'uid_min': settings['auth']['uid_min'], 52 | 'sys_uid_min': settings['auth']['sys_uid_min'], 53 | 'sys_uid_max': settings['auth']['sys_uid_max'], 54 | 'gid_min': settings['auth']['gid_min'], 55 | 'sys_gid_min': settings['auth']['sys_gid_min'], 56 | 'sys_gid_max': settings['auth']['sys_gid_max'], 57 | 'login_retries': settings['auth']['retries'], 58 | 'login_timeout': settings['auth']['timeout'], 59 | 'chfn_restrict': settings['auth']['chfn_restrict'], 60 | 'allow_login_without_home': settings['auth']['allow_homeless'] 61 | } 62 | 63 | return ctxt 64 | -------------------------------------------------------------------------------- /hooks/charmhelpers/fetch/giturl.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | from subprocess import check_output, CalledProcessError, STDOUT 17 | from charmhelpers.fetch import ( 18 | BaseFetchHandler, 19 | UnhandledSource, 20 | filter_installed_packages, 21 | install, 22 | ) 23 | 24 | if filter_installed_packages(['git']) != []: 25 | install(['git']) 26 | if filter_installed_packages(['git']) != []: 27 | raise NotImplementedError('Unable to install git') 28 | 29 | 30 | class GitUrlFetchHandler(BaseFetchHandler): 31 | """Handler for git branches via generic and github URLs.""" 32 | 33 | def can_handle(self, source): 34 | url_parts = self.parse_url(source) 35 | # TODO (mattyw) no support for ssh git@ yet 36 | if url_parts.scheme not in ('http', 'https', 'git', ''): 37 | return False 38 | elif not url_parts.scheme: 39 | return os.path.exists(os.path.join(source, '.git')) 40 | else: 41 | return True 42 | 43 | def clone(self, source, dest, branch="master", depth=None): 44 | if not self.can_handle(source): 45 | raise UnhandledSource("Cannot handle {}".format(source)) 46 | 47 | if os.path.exists(dest): 48 | cmd = ['git', '-C', dest, 'pull', source, branch] 49 | else: 50 | cmd = ['git', 'clone', source, dest, '--branch', branch] 51 | if depth: 52 | cmd.extend(['--depth', depth]) 53 | check_output(cmd, stderr=STDOUT) 54 | 55 | def install(self, source, branch="master", dest=None, depth=None): 56 | url_parts = self.parse_url(source) 57 | branch_name = url_parts.path.strip("/").split("/")[-1] 58 | if dest: 59 | dest_dir = os.path.join(dest, branch_name) 60 | else: 61 | dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched", 62 | branch_name) 63 | try: 64 | self.clone(source, dest_dir, branch, depth) 65 | except CalledProcessError as e: 66 | raise UnhandledSource(e) 67 | except OSError as e: 68 | raise UnhandledSource(e.strerror) 69 | return dest_dir 70 | -------------------------------------------------------------------------------- /hooks/charmhelpers/fetch/bzrurl.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | from subprocess import STDOUT, check_output 17 | from charmhelpers.fetch import ( 18 | BaseFetchHandler, 19 | UnhandledSource, 20 | filter_installed_packages, 21 | install, 22 | ) 23 | from charmhelpers.core.host import mkdir 24 | 25 | 26 | if filter_installed_packages(['bzr']) != []: 27 | install(['bzr']) 28 | if filter_installed_packages(['bzr']) != []: 29 | raise NotImplementedError('Unable to install bzr') 30 | 31 | 32 | class BzrUrlFetchHandler(BaseFetchHandler): 33 | """Handler for bazaar branches via generic and lp URLs.""" 34 | 35 | def can_handle(self, source): 36 | url_parts = self.parse_url(source) 37 | if url_parts.scheme not in ('bzr+ssh', 'lp', ''): 38 | return False 39 | elif not url_parts.scheme: 40 | return os.path.exists(os.path.join(source, '.bzr')) 41 | else: 42 | return True 43 | 44 | def branch(self, source, dest, revno=None): 45 | if not self.can_handle(source): 46 | raise UnhandledSource("Cannot handle {}".format(source)) 47 | cmd_opts = [] 48 | if revno: 49 | cmd_opts += ['-r', str(revno)] 50 | if os.path.exists(dest): 51 | cmd = ['bzr', 'pull'] 52 | cmd += cmd_opts 53 | cmd += ['--overwrite', '-d', dest, source] 54 | else: 55 | cmd = ['bzr', 'branch'] 56 | cmd += cmd_opts 57 | cmd += [source, dest] 58 | check_output(cmd, stderr=STDOUT) 59 | 60 | def install(self, source, dest=None, revno=None): 61 | url_parts = self.parse_url(source) 62 | branch_name = url_parts.path.strip("/").split("/")[-1] 63 | if dest: 64 | dest_dir = os.path.join(dest, branch_name) 65 | else: 66 | dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched", 67 | branch_name) 68 | 69 | if dest and not os.path.exists(dest): 70 | mkdir(dest, perms=0o755) 71 | 72 | try: 73 | self.branch(source, dest_dir, revno) 74 | except OSError as e: 75 | raise UnhandledSource(e.strerror) 76 | return dest_dir 77 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/hugepage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2014-2015 Canonical Limited. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import yaml 18 | from charmhelpers.core import fstab 19 | from charmhelpers.core import sysctl 20 | from charmhelpers.core.host import ( 21 | add_group, 22 | add_user_to_group, 23 | fstab_mount, 24 | mkdir, 25 | ) 26 | from charmhelpers.core.strutils import bytes_from_string 27 | from subprocess import check_output 28 | 29 | 30 | def hugepage_support(user, group='hugetlb', nr_hugepages=256, 31 | max_map_count=65536, mnt_point='/run/hugepages/kvm', 32 | pagesize='2MB', mount=True, set_shmmax=False): 33 | """Enable hugepages on system. 34 | 35 | Args: 36 | user (str) -- Username to allow access to hugepages to 37 | group (str) -- Group name to own hugepages 38 | nr_hugepages (int) -- Number of pages to reserve 39 | max_map_count (int) -- Number of Virtual Memory Areas a process can own 40 | mnt_point (str) -- Directory to mount hugepages on 41 | pagesize (str) -- Size of hugepages 42 | mount (bool) -- Whether to Mount hugepages 43 | """ 44 | group_info = add_group(group) 45 | gid = group_info.gr_gid 46 | add_user_to_group(user, group) 47 | if max_map_count < 2 * nr_hugepages: 48 | max_map_count = 2 * nr_hugepages 49 | sysctl_settings = { 50 | 'vm.nr_hugepages': nr_hugepages, 51 | 'vm.max_map_count': max_map_count, 52 | 'vm.hugetlb_shm_group': gid, 53 | } 54 | if set_shmmax: 55 | shmmax_current = int(check_output(['sysctl', '-n', 'kernel.shmmax'])) 56 | shmmax_minsize = bytes_from_string(pagesize) * nr_hugepages 57 | if shmmax_minsize > shmmax_current: 58 | sysctl_settings['kernel.shmmax'] = shmmax_minsize 59 | sysctl.create(yaml.dump(sysctl_settings), '/etc/sysctl.d/10-hugepage.conf') 60 | mkdir(mnt_point, owner='root', group='root', perms=0o755, force=False) 61 | lfstab = fstab.Fstab() 62 | fstab_entry = lfstab.get_entry_by_attr('mountpoint', mnt_point) 63 | if fstab_entry: 64 | lfstab.remove_entry(fstab_entry) 65 | entry = lfstab.Entry('nodev', mnt_point, 'hugetlbfs', 66 | 'mode=1770,gid={},pagesize={}'.format(gid, pagesize), 0, 0) 67 | lfstab.add_entry(entry) 68 | if mount: 69 | fstab_mount(mnt_point) 70 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/storage/linux/loopback.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import re 17 | from subprocess import ( 18 | check_call, 19 | check_output, 20 | ) 21 | 22 | 23 | ################################################## 24 | # loopback device helpers. 25 | ################################################## 26 | def loopback_devices(): 27 | ''' 28 | Parse through 'losetup -a' output to determine currently mapped 29 | loopback devices. Output is expected to look like: 30 | 31 | /dev/loop0: [0807]:961814 (/tmp/my.img) 32 | 33 | or: 34 | 35 | /dev/loop0: [0807]:961814 (/tmp/my.img (deleted)) 36 | 37 | :returns: dict: a dict mapping {loopback_dev: backing_file} 38 | ''' 39 | loopbacks = {} 40 | cmd = ['losetup', '-a'] 41 | output = check_output(cmd).decode('utf-8') 42 | devs = [d.strip().split(' ', 2) for d in output.splitlines() if d != ''] 43 | for dev, _, f in devs: 44 | loopbacks[dev.replace(':', '')] = re.search(r'\((.+)\)', f).groups()[0] 45 | return loopbacks 46 | 47 | 48 | def create_loopback(file_path): 49 | ''' 50 | Create a loopback device for a given backing file. 51 | 52 | :returns: str: Full path to new loopback device (eg, /dev/loop0) 53 | ''' 54 | file_path = os.path.abspath(file_path) 55 | check_call(['losetup', '--find', file_path]) 56 | for d, f in loopback_devices().items(): 57 | if f == file_path: 58 | return d 59 | 60 | 61 | def ensure_loopback_device(path, size): 62 | ''' 63 | Ensure a loopback device exists for a given backing file path and size. 64 | If it a loopback device is not mapped to file, a new one will be created. 65 | 66 | TODO: Confirm size of found loopback device. 67 | 68 | :returns: str: Full path to the ensured loopback device (eg, /dev/loop0) 69 | ''' 70 | for d, f in loopback_devices().items(): 71 | if f == path: 72 | return d 73 | 74 | if not os.path.exists(path): 75 | cmd = ['truncate', '--size', size, path] 76 | check_call(cmd) 77 | 78 | return create_loopback(path) 79 | 80 | 81 | def is_mapped_loopback_device(device): 82 | """ 83 | Checks if a given device name is an existing/mapped loopback device. 84 | :param device: str: Full path to the device (eg, /dev/loop1). 85 | :returns: str: Path to the backing file if is a loopback device 86 | empty string otherwise 87 | """ 88 | return loopback_devices().get(device, "") 89 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hahelpers/apache.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # 16 | # Copyright 2012 Canonical Ltd. 17 | # 18 | # This file is sourced from lp:openstack-charm-helpers 19 | # 20 | # Authors: 21 | # James Page 22 | # Adam Gandelman 23 | # 24 | 25 | import os 26 | 27 | from charmhelpers.core import host 28 | from charmhelpers.core.hookenv import ( 29 | config as config_get, 30 | relation_get, 31 | relation_ids, 32 | related_units as relation_list, 33 | log, 34 | INFO, 35 | ) 36 | 37 | # This file contains the CA cert from the charms ssl_ca configuration 38 | # option, in future the file name should be updated reflect that. 39 | CONFIG_CA_CERT_FILE = 'keystone_juju_ca_cert' 40 | 41 | 42 | def get_cert(cn=None): 43 | # TODO: deal with multiple https endpoints via charm config 44 | cert = config_get('ssl_cert') 45 | key = config_get('ssl_key') 46 | if not (cert and key): 47 | log("Inspecting identity-service relations for SSL certificate.", 48 | level=INFO) 49 | cert = key = None 50 | if cn: 51 | ssl_cert_attr = 'ssl_cert_{}'.format(cn) 52 | ssl_key_attr = 'ssl_key_{}'.format(cn) 53 | else: 54 | ssl_cert_attr = 'ssl_cert' 55 | ssl_key_attr = 'ssl_key' 56 | for r_id in relation_ids('identity-service'): 57 | for unit in relation_list(r_id): 58 | if not cert: 59 | cert = relation_get(ssl_cert_attr, 60 | rid=r_id, unit=unit) 61 | if not key: 62 | key = relation_get(ssl_key_attr, 63 | rid=r_id, unit=unit) 64 | return (cert, key) 65 | 66 | 67 | def get_ca_cert(): 68 | ca_cert = config_get('ssl_ca') 69 | if ca_cert is None: 70 | log("Inspecting identity-service relations for CA SSL certificate.", 71 | level=INFO) 72 | for r_id in (relation_ids('identity-service') + 73 | relation_ids('identity-credentials')): 74 | for unit in relation_list(r_id): 75 | if ca_cert is None: 76 | ca_cert = relation_get('ca_cert', 77 | rid=r_id, unit=unit) 78 | return ca_cert 79 | 80 | 81 | def retrieve_ca_cert(cert_file): 82 | cert = None 83 | if os.path.isfile(cert_file): 84 | with open(cert_file, 'rb') as crt: 85 | cert = crt.read() 86 | return cert 87 | 88 | 89 | def install_ca_cert(ca_cert): 90 | host.install_ca_cert(ca_cert, CONFIG_CA_CERT_FILE) 91 | -------------------------------------------------------------------------------- /actions/blacklist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2017 Canonical Ltd 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import os 18 | import sys 19 | 20 | sys.path.append('hooks') 21 | 22 | import charmhelpers.core.hookenv as hookenv 23 | import charmhelpers.core.unitdata as unitdata 24 | 25 | BLACKLIST_KEY = 'osd-blacklist' 26 | 27 | 28 | class Error(Exception): 29 | def __init__(self, message): 30 | self.message = message 31 | 32 | def __str__(self): 33 | return repr(self.message) 34 | 35 | 36 | def get_devices(): 37 | """Parse 'osd-devices' action parameter, returns list.""" 38 | devices = [] 39 | for path in hookenv.action_get('osd-devices').split(' '): 40 | path = path.strip() 41 | if not os.path.isabs(path): 42 | raise Error('{}: Not absolute path.'.format(path)) 43 | devices.append(path) 44 | return devices 45 | 46 | 47 | def blacklist_add(): 48 | """ 49 | Add devices given in 'osd-devices' action parameter to 50 | unit-local devices blacklist. 51 | """ 52 | db = unitdata.kv() 53 | blacklist = db.get(BLACKLIST_KEY, []) 54 | for device in get_devices(): 55 | if not os.path.exists(device): 56 | raise Error('{}: No such file or directory.'.format(device)) 57 | if device not in blacklist: 58 | blacklist.append(device) 59 | db.set(BLACKLIST_KEY, blacklist) 60 | db.flush() 61 | 62 | 63 | def blacklist_remove(): 64 | """ 65 | Remove devices given in 'osd-devices' action parameter from 66 | unit-local devices blacklist. 67 | """ 68 | db = unitdata.kv() 69 | blacklist = db.get(BLACKLIST_KEY, []) 70 | for device in get_devices(): 71 | try: 72 | blacklist.remove(device) 73 | except ValueError: 74 | raise Error('{}: Device not in blacklist.'.format(device)) 75 | db.set(BLACKLIST_KEY, blacklist) 76 | db.flush() 77 | 78 | 79 | # A dictionary of all the defined actions to callables 80 | ACTIONS = { 81 | "blacklist-add-disk": blacklist_add, 82 | "blacklist-remove-disk": blacklist_remove, 83 | } 84 | 85 | 86 | def main(args): 87 | """Main program""" 88 | action_name = os.path.basename(args[0]) 89 | try: 90 | action = ACTIONS[action_name] 91 | except KeyError: 92 | return "Action {} undefined".format(action_name) 93 | else: 94 | try: 95 | action() 96 | except Exception as e: 97 | hookenv.action_fail("Action {} failed: {}" 98 | "".format(action_name, str(e))) 99 | 100 | 101 | if __name__ == "__main__": 102 | sys.exit(main(sys.argv)) 103 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/mysql/checks/config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import subprocess 16 | 17 | from charmhelpers.core.hookenv import ( 18 | log, 19 | WARNING, 20 | ) 21 | from charmhelpers.contrib.hardening.audits.file import ( 22 | FilePermissionAudit, 23 | DirectoryPermissionAudit, 24 | TemplatedFile, 25 | ) 26 | from charmhelpers.contrib.hardening.mysql import TEMPLATES_DIR 27 | from charmhelpers.contrib.hardening import utils 28 | 29 | 30 | def get_audits(): 31 | """Get MySQL hardening config audits. 32 | 33 | :returns: dictionary of audits 34 | """ 35 | if subprocess.call(['which', 'mysql'], stdout=subprocess.PIPE) != 0: 36 | log("MySQL does not appear to be installed on this node - " 37 | "skipping mysql hardening", level=WARNING) 38 | return [] 39 | 40 | settings = utils.get_settings('mysql') 41 | hardening_settings = settings['hardening'] 42 | my_cnf = hardening_settings['mysql-conf'] 43 | 44 | audits = [ 45 | FilePermissionAudit(paths=[my_cnf], user='root', 46 | group='root', mode=0o0600), 47 | 48 | TemplatedFile(hardening_settings['hardening-conf'], 49 | MySQLConfContext(), 50 | TEMPLATES_DIR, 51 | mode=0o0750, 52 | user='mysql', 53 | group='root', 54 | service_actions=[{'service': 'mysql', 55 | 'actions': ['restart']}]), 56 | 57 | # MySQL and Percona charms do not allow configuration of the 58 | # data directory, so use the default. 59 | DirectoryPermissionAudit('/var/lib/mysql', 60 | user='mysql', 61 | group='mysql', 62 | recursive=False, 63 | mode=0o755), 64 | 65 | DirectoryPermissionAudit('/etc/mysql', 66 | user='root', 67 | group='root', 68 | recursive=False, 69 | mode=0o700), 70 | ] 71 | 72 | return audits 73 | 74 | 75 | class MySQLConfContext(object): 76 | """Defines the set of key/value pairs to set in a mysql config file. 77 | 78 | This context, when called, will return a dictionary containing the 79 | key/value pairs of setting to specify in the 80 | /etc/mysql/conf.d/hardening.cnf file. 81 | """ 82 | def __call__(self): 83 | settings = utils.get_settings('mysql') 84 | return { 85 | 'mysql_settings': [(k, v) for k, v in settings['security'].items()] 86 | } 87 | -------------------------------------------------------------------------------- /hooks/charmhelpers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Bootstrap charm-helpers, installing its dependencies if necessary using 16 | # only standard libraries. 17 | import functools 18 | import inspect 19 | import subprocess 20 | 21 | 22 | try: 23 | import yaml # NOQA:F401 24 | except ImportError: 25 | subprocess.check_call(['apt-get', 'install', '-y', 'python3-yaml']) 26 | import yaml # NOQA:F401 27 | 28 | 29 | # Holds a list of mapping of mangled function names that have been deprecated 30 | # using the @deprecate decorator below. This is so that the warning is only 31 | # printed once for each usage of the function. 32 | __deprecated_functions = {} 33 | 34 | 35 | def deprecate(warning, date=None, log=None): 36 | """Add a deprecation warning the first time the function is used. 37 | 38 | The date which is a string in semi-ISO8660 format indicates the year-month 39 | that the function is officially going to be removed. 40 | 41 | usage: 42 | 43 | @deprecate('use core/fetch/add_source() instead', '2017-04') 44 | def contributed_add_source_thing(...): 45 | ... 46 | 47 | And it then prints to the log ONCE that the function is deprecated. 48 | The reason for passing the logging function (log) is so that hookenv.log 49 | can be used for a charm if needed. 50 | 51 | :param warning: String to indicate what is to be used instead. 52 | :param date: Optional string in YYYY-MM format to indicate when the 53 | function will definitely (probably) be removed. 54 | :param log: The log function to call in order to log. If None, logs to 55 | stdout 56 | """ 57 | def wrap(f): 58 | 59 | @functools.wraps(f) 60 | def wrapped_f(*args, **kwargs): 61 | try: 62 | module = inspect.getmodule(f) 63 | file = inspect.getsourcefile(f) 64 | lines = inspect.getsourcelines(f) 65 | f_name = "{}-{}-{}..{}-{}".format( 66 | module.__name__, file, lines[0], lines[-1], f.__name__) 67 | except (IOError, TypeError): 68 | # assume it was local, so just use the name of the function 69 | f_name = f.__name__ 70 | if f_name not in __deprecated_functions: 71 | __deprecated_functions[f_name] = True 72 | s = "DEPRECATION WARNING: Function {} is being removed".format( 73 | f.__name__) 74 | if date: 75 | s = "{} on/around {}".format(s, date) 76 | if warning: 77 | s = "{} : {}".format(s, warning) 78 | if log: 79 | log(s) 80 | else: 81 | print(s) 82 | return f(*args, **kwargs) 83 | return wrapped_f 84 | return wrap 85 | -------------------------------------------------------------------------------- /files/nagios/collect_ceph_osd_services.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (C) 2018 Canonical 4 | # All Rights Reserved 5 | # Author: Alex Kavanagh 6 | 7 | import os 8 | import subprocess 9 | from pwd import getpwnam 10 | 11 | # fasteners only exists in Bionic, so this will fail on xenial and trusty 12 | try: 13 | import fasteners 14 | except ImportError: 15 | fasteners = None 16 | 17 | SYSTEMD_SYSTEM = '/run/systemd/system' 18 | LOCKFILE = '/var/lock/check-osds.lock' 19 | CRON_CHECK_TMPFILE = 'ceph-osd-checks' 20 | NAGIOS_HOME = '/var/lib/nagios' 21 | 22 | 23 | def init_is_systemd(): 24 | """Return True if the host system uses systemd, False otherwise.""" 25 | if lsb_release()['DISTRIB_CODENAME'] == 'trusty': 26 | return False 27 | return os.path.isdir(SYSTEMD_SYSTEM) 28 | 29 | 30 | def lsb_release(): 31 | """Return /etc/lsb-release in a dict""" 32 | d = {} 33 | with open('/etc/lsb-release', 'r') as lsb: 34 | for el in lsb: 35 | k, v = el.split('=') 36 | d[k.strip()] = v.strip() 37 | return d 38 | 39 | 40 | def get_osd_units(): 41 | """Returns a list of strings, one for each unit that is live""" 42 | cmd = '/bin/cat /var/lib/ceph/osd/ceph-*/whoami' 43 | try: 44 | output = (subprocess 45 | .check_output([cmd], shell=True).decode('utf-8') 46 | .split('\n')) 47 | return [u for u in output if u] 48 | except subprocess.CalledProcessError: 49 | return [] 50 | 51 | 52 | def do_status(): 53 | if init_is_systemd(): 54 | cmd = "/usr/local/lib/nagios/plugins/check_systemd.py ceph-osd@{}" 55 | else: 56 | cmd = "/sbin/status ceph-osd id={}" 57 | 58 | lines = [] 59 | 60 | for unit in get_osd_units(): 61 | try: 62 | output = (subprocess 63 | .check_output(cmd.format(unit).split(), 64 | stderr=subprocess.STDOUT) 65 | .decode('utf-8')) 66 | except subprocess.CalledProcessError as e: 67 | output = ("Failed: check command raised: {}" 68 | .format(e.output.decode('utf-8'))) 69 | lines.append(output) 70 | 71 | _tmp_file = os.path.join(NAGIOS_HOME, CRON_CHECK_TMPFILE) 72 | with open(_tmp_file, 'wt') as f: 73 | f.writelines(lines) 74 | 75 | # In cis hardened environments check_ceph_osd_services cannot 76 | # read _tmp_file due to restrained permissions (#LP1879667). 77 | # Changing the owner of the file to nagios solves this problem. 78 | # check_ceph_osd_services.py removes this file, so make 79 | # sure that we change permissions on a file that exists. 80 | nagios_uid = getpwnam('nagios').pw_uid 81 | nagios_gid = getpwnam('nagios').pw_gid 82 | if os.path.isfile(_tmp_file): 83 | os.chown(_tmp_file, nagios_uid, nagios_gid) 84 | 85 | 86 | def run_main(): 87 | # on bionic we can interprocess lock; we don't do it for older platforms 88 | if fasteners is not None: 89 | lock = fasteners.InterProcessLock(LOCKFILE) 90 | 91 | if lock.acquire(blocking=False): 92 | try: 93 | do_status() 94 | finally: 95 | lock.release() 96 | else: 97 | do_status() 98 | 99 | 100 | if __name__ == '__main__': 101 | run_main() 102 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/decorators.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # 16 | # Copyright 2014 Canonical Ltd. 17 | # 18 | # Authors: 19 | # Edward Hope-Morley 20 | # 21 | 22 | import time 23 | 24 | from charmhelpers.core.hookenv import ( 25 | log, 26 | INFO, 27 | ) 28 | 29 | 30 | def retry_on_exception(num_retries, base_delay=0, exc_type=Exception): 31 | """If the decorated function raises exception exc_type, allow num_retries 32 | retry attempts before raise the exception. 33 | """ 34 | def _retry_on_exception_inner_1(f): 35 | def _retry_on_exception_inner_2(*args, **kwargs): 36 | retries = num_retries 37 | multiplier = 1 38 | while True: 39 | try: 40 | return f(*args, **kwargs) 41 | except exc_type: 42 | if not retries: 43 | raise 44 | 45 | delay = base_delay * multiplier 46 | multiplier += 1 47 | log("Retrying '%s' %d more times (delay=%s)" % 48 | (f.__name__, retries, delay), level=INFO) 49 | retries -= 1 50 | if delay: 51 | time.sleep(delay) 52 | 53 | return _retry_on_exception_inner_2 54 | 55 | return _retry_on_exception_inner_1 56 | 57 | 58 | def retry_on_predicate(num_retries, predicate_fun, base_delay=0): 59 | """Retry based on return value 60 | 61 | The return value of the decorated function is passed to the given predicate_fun. If the 62 | result of the predicate is False, retry the decorated function up to num_retries times 63 | 64 | An exponential backoff up to base_delay^num_retries seconds can be introduced by setting 65 | base_delay to a nonzero value. The default is to run with a zero (i.e. no) delay 66 | 67 | :param num_retries: Max. number of retries to perform 68 | :type num_retries: int 69 | :param predicate_fun: Predicate function to determine if a retry is necessary 70 | :type predicate_fun: callable 71 | :param base_delay: Starting value in seconds for exponential delay, defaults to 0 (no delay) 72 | :type base_delay: float 73 | """ 74 | def _retry_on_pred_inner_1(f): 75 | def _retry_on_pred_inner_2(*args, **kwargs): 76 | retries = num_retries 77 | multiplier = 1 78 | delay = base_delay 79 | while True: 80 | result = f(*args, **kwargs) 81 | if predicate_fun(result) or retries <= 0: 82 | return result 83 | delay *= multiplier 84 | multiplier += 1 85 | log("Result {}, retrying '{}' {} more times (delay={})".format( 86 | result, f.__name__, retries, delay), level=INFO) 87 | retries -= 1 88 | if delay: 89 | time.sleep(delay) 90 | 91 | return _retry_on_pred_inner_2 92 | 93 | return _retry_on_pred_inner_1 94 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/host_factory/ubuntu.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | from charmhelpers.core.hookenv import cached 4 | from charmhelpers.core.strutils import BasicStringComparator 5 | 6 | 7 | UBUNTU_RELEASES = ( 8 | 'lucid', 9 | 'maverick', 10 | 'natty', 11 | 'oneiric', 12 | 'precise', 13 | 'quantal', 14 | 'raring', 15 | 'saucy', 16 | 'trusty', 17 | 'utopic', 18 | 'vivid', 19 | 'wily', 20 | 'xenial', 21 | 'yakkety', 22 | 'zesty', 23 | 'artful', 24 | 'bionic', 25 | 'cosmic', 26 | 'disco', 27 | 'eoan', 28 | 'focal', 29 | 'groovy', 30 | 'hirsute', 31 | 'impish', 32 | 'jammy', 33 | 'kinetic', 34 | 'lunar', 35 | 'mantic', 36 | 'noble', 37 | 'oracular', 38 | ) 39 | 40 | 41 | class CompareHostReleases(BasicStringComparator): 42 | """Provide comparisons of Ubuntu releases. 43 | 44 | Use in the form of 45 | 46 | if CompareHostReleases(release) > 'trusty': 47 | # do something with mitaka 48 | """ 49 | _list = UBUNTU_RELEASES 50 | 51 | 52 | def service_available(service_name): 53 | """Determine whether a system service is available""" 54 | try: 55 | subprocess.check_output( 56 | ['service', service_name, 'status'], 57 | stderr=subprocess.STDOUT).decode('UTF-8') 58 | except subprocess.CalledProcessError as e: 59 | return b'unrecognized service' not in e.output 60 | else: 61 | return True 62 | 63 | 64 | def add_new_group(group_name, system_group=False, gid=None): 65 | cmd = ['addgroup'] 66 | if gid: 67 | cmd.extend(['--gid', str(gid)]) 68 | if system_group: 69 | cmd.append('--system') 70 | else: 71 | cmd.extend([ 72 | '--group', 73 | ]) 74 | cmd.append(group_name) 75 | subprocess.check_call(cmd) 76 | 77 | 78 | def lsb_release(): 79 | """Return /etc/lsb-release in a dict""" 80 | d = {} 81 | with open('/etc/lsb-release', 'r') as lsb: 82 | for l in lsb: 83 | k, v = l.split('=') 84 | d[k.strip()] = v.strip() 85 | return d 86 | 87 | 88 | def get_distrib_codename(): 89 | """Return the codename of the distribution 90 | :returns: The codename 91 | :rtype: str 92 | """ 93 | return lsb_release()['DISTRIB_CODENAME'].lower() 94 | 95 | 96 | def cmp_pkgrevno(package, revno, pkgcache=None): 97 | """Compare supplied revno with the revno of the installed package. 98 | 99 | * 1 => Installed revno is greater than supplied arg 100 | * 0 => Installed revno is the same as supplied arg 101 | * -1 => Installed revno is less than supplied arg 102 | 103 | This function imports apt_cache function from charmhelpers.fetch if 104 | the pkgcache argument is None. Be sure to add charmhelpers.fetch if 105 | you call this function, or pass an apt_pkg.Cache() instance. 106 | """ 107 | from charmhelpers.fetch import apt_pkg, get_installed_version 108 | if not pkgcache: 109 | current_ver = get_installed_version(package) 110 | else: 111 | pkg = pkgcache[package] 112 | current_ver = pkg.current_ver 113 | 114 | return apt_pkg.version_compare(current_ver.ver_str, revno) 115 | 116 | 117 | @cached 118 | def arch(): 119 | """Return the package architecture as a string. 120 | 121 | :returns: the architecture 122 | :rtype: str 123 | :raises: subprocess.CalledProcessError if dpkg command fails 124 | """ 125 | return subprocess.check_output( 126 | ['dpkg', '--print-architecture'] 127 | ).rstrip().decode('UTF-8') 128 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/templating.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | 17 | from charmhelpers.core import host 18 | from charmhelpers.core import hookenv 19 | 20 | 21 | def render(source, target, context, owner='root', group='root', 22 | perms=0o444, templates_dir=None, encoding='UTF-8', 23 | template_loader=None, config_template=None): 24 | """ 25 | Render a template. 26 | 27 | The `source` path, if not absolute, is relative to the `templates_dir`. 28 | 29 | The `target` path should be absolute. It can also be `None`, in which 30 | case no file will be written. 31 | 32 | The context should be a dict containing the values to be replaced in the 33 | template. 34 | 35 | config_template may be provided to render from a provided template instead 36 | of loading from a file. 37 | 38 | The `owner`, `group`, and `perms` options will be passed to `write_file`. 39 | 40 | If omitted, `templates_dir` defaults to the `templates` folder in the charm. 41 | 42 | The rendered template will be written to the file as well as being returned 43 | as a string. 44 | 45 | Note: Using this requires python3-jinja2; if it is not installed, calling 46 | this will attempt to use charmhelpers.fetch.apt_install to install it. 47 | """ 48 | try: 49 | from jinja2 import FileSystemLoader, Environment, exceptions 50 | except ImportError: 51 | try: 52 | from charmhelpers.fetch import apt_install 53 | except ImportError: 54 | hookenv.log('Could not import jinja2, and could not import ' 55 | 'charmhelpers.fetch to install it', 56 | level=hookenv.ERROR) 57 | raise 58 | apt_install('python3-jinja2', fatal=True) 59 | from jinja2 import FileSystemLoader, Environment, exceptions 60 | 61 | if template_loader: 62 | template_env = Environment(loader=template_loader) 63 | else: 64 | if templates_dir is None: 65 | templates_dir = os.path.join(hookenv.charm_dir(), 'templates') 66 | template_env = Environment(loader=FileSystemLoader(templates_dir)) 67 | 68 | # load from a string if provided explicitly 69 | if config_template is not None: 70 | template = template_env.from_string(config_template) 71 | else: 72 | try: 73 | source = source 74 | template = template_env.get_template(source) 75 | except exceptions.TemplateNotFound as e: 76 | hookenv.log('Could not load template %s from %s.' % 77 | (source, templates_dir), 78 | level=hookenv.ERROR) 79 | raise e 80 | content = template.render(context) 81 | if target is not None: 82 | target_dir = os.path.dirname(target) 83 | if not os.path.exists(target_dir): 84 | # This is a terrible default directory permission, as the file 85 | # or its siblings will often contain secrets. 86 | host.mkdir(os.path.dirname(target), owner, group, perms=0o755) 87 | host.write_file(target, content.encode(encoding), owner, group, perms) 88 | return content 89 | -------------------------------------------------------------------------------- /templates/ceph.conf: -------------------------------------------------------------------------------- 1 | [global] 2 | {%- if old_auth %} 3 | auth supported = {{ auth_supported }} 4 | {%- else %} 5 | auth cluster required = {{ auth_supported }} 6 | auth service required = {{ auth_supported }} 7 | auth client required = {{ auth_supported }} 8 | {%- endif %} 9 | 10 | mon host = {{ mon_hosts }} 11 | fsid = {{ fsid }} 12 | 13 | log to syslog = {{ use_syslog }} 14 | err to syslog = {{ use_syslog }} 15 | clog to syslog = {{ use_syslog }} 16 | debug osd = {{ loglevel }}/5 17 | 18 | {% if ms_bind_ipv6 %} 19 | ms_bind_ipv6 = true 20 | {%- endif %} 21 | {%- if ms_bind_ipv4 == false %} 22 | ms_bind_ipv4 = false 23 | {% endif %} 24 | {% if ceph_public_network is string %} 25 | public network = {{ ceph_public_network }} 26 | {%- endif %} 27 | {%- if ceph_cluster_network is string %} 28 | cluster network = {{ ceph_cluster_network }} 29 | {%- endif %} 30 | {%- if public_addr %} 31 | public addr = {{ public_addr }} 32 | {%- endif %} 33 | {%- if cluster_addr %} 34 | cluster addr = {{ cluster_addr }} 35 | {%- endif %} 36 | {%- if crush_location %} 37 | crush location = {{crush_location}} 38 | {%- endif %} 39 | {%- if upgrade_in_progress %} 40 | setuser match path = /var/lib/ceph/$type/$cluster-$id 41 | {%- endif %} 42 | {%- if crush_initial_weight is not none %} 43 | osd crush initial weight = {{ crush_initial_weight }} 44 | {%- endif %} 45 | {% if global -%} 46 | # The following are user-provided options provided via the config-flags charm option. 47 | # User-provided [global] section config 48 | {% for key in global -%} 49 | {{ key }} = {{ global[key] }} 50 | {% endfor %} 51 | {% endif %} 52 | 53 | {% if bluestore_experimental -%} 54 | enable experimental unrecoverable data corrupting features = bluestore rocksdb 55 | {%- endif %} 56 | 57 | [client.osd-upgrade] 58 | keyring = /var/lib/ceph/osd/ceph.client.osd-upgrade.keyring 59 | 60 | [client.osd-removal] 61 | keyring = /var/lib/ceph/osd/ceph.client.osd-removal.keyring 62 | 63 | [client.crash] 64 | keyring = /var/lib/ceph/osd/ceph.client.crash.keyring 65 | 66 | [mon] 67 | keyring = /var/lib/ceph/mon/$cluster-$id/keyring 68 | 69 | [mds] 70 | keyring = /var/lib/ceph/mds/$cluster-$id/keyring 71 | 72 | [osd] 73 | keyring = /var/lib/ceph/osd/$cluster-$id/keyring 74 | 75 | {% if not bluestore_experimental -%} 76 | osd objectstore = bluestore 77 | {%- endif %} 78 | {% if bluestore_block_wal_size -%} 79 | bluestore block wal size = {{ bluestore_block_wal_size }} 80 | {%- endif %} 81 | {% if bluestore_block_db_size -%} 82 | bluestore block db size = {{ bluestore_block_db_size }} 83 | {%- endif %} 84 | {% include 'section-ceph-bluestore-compression' %} 85 | 86 | bdev enable discard = {{ bdev_discard }} 87 | bdev async discard = {{ bdev_discard }} 88 | 89 | {%- if short_object_len %} 90 | osd max object name len = 256 91 | osd max object namespace len = 64 92 | {% endif %} 93 | {%- if osd_max_backfills %} 94 | osd max backfills = {{ osd_max_backfills }} 95 | {%- endif %} 96 | {%- if osd_recovery_max_active %} 97 | osd recovery max active = {{ osd_recovery_max_active }} 98 | {%- endif %} 99 | 100 | {% if osd_from_client -%} 101 | # The following are charm provided options provided via the mon relation. 102 | {% for key in osd_from_client -%} 103 | {{ key }} = {{ osd_from_client[key] }} 104 | {% endfor %} 105 | {% endif %} 106 | {% if osd_from_client_conflict -%} 107 | # The following are charm provided options which conflict with options from 108 | # config-flags. 109 | {% for key in osd_from_client_conflict -%} 110 | # {{ key }} = {{ osd_from_client_conflict[key] }} 111 | {% endfor %} 112 | {% endif %} 113 | {% if osd -%} 114 | # The following are user-provided options provided via the config-flags charm option. 115 | {% for key in osd -%} 116 | {{ key }} = {{ osd[key] }} 117 | {% endfor %} 118 | {% endif %} 119 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/audits/apt.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from charmhelpers.fetch import ( 16 | apt_cache, 17 | apt_purge 18 | ) 19 | from charmhelpers.core.hookenv import ( 20 | log, 21 | DEBUG, 22 | WARNING, 23 | ) 24 | from charmhelpers.contrib.hardening.audits import BaseAudit 25 | from charmhelpers.fetch import ubuntu_apt_pkg as apt_pkg 26 | 27 | 28 | class AptConfig(BaseAudit): 29 | 30 | def __init__(self, config, **kwargs): 31 | self.config = config 32 | 33 | def verify_config(self): 34 | apt_pkg.init() 35 | for cfg in self.config: 36 | value = apt_pkg.config.get(cfg['key'], cfg.get('default', '')) 37 | if value and value != cfg['expected']: 38 | log("APT config '%s' has unexpected value '%s' " 39 | "(expected='%s')" % 40 | (cfg['key'], value, cfg['expected']), level=WARNING) 41 | 42 | def ensure_compliance(self): 43 | self.verify_config() 44 | 45 | 46 | class RestrictedPackages(BaseAudit): 47 | """Class used to audit restricted packages on the system.""" 48 | 49 | def __init__(self, pkgs, **kwargs): 50 | super(RestrictedPackages, self).__init__(**kwargs) 51 | if isinstance(pkgs, str) or not hasattr(pkgs, '__iter__'): 52 | self.pkgs = pkgs.split() 53 | else: 54 | self.pkgs = pkgs 55 | 56 | def ensure_compliance(self): 57 | cache = apt_cache() 58 | 59 | for p in self.pkgs: 60 | if p not in cache: 61 | continue 62 | 63 | pkg = cache[p] 64 | if not self.is_virtual_package(pkg): 65 | if not pkg.current_ver: 66 | log("Package '%s' is not installed." % pkg.name, 67 | level=DEBUG) 68 | continue 69 | else: 70 | log("Restricted package '%s' is installed" % pkg.name, 71 | level=WARNING) 72 | self.delete_package(cache, pkg) 73 | else: 74 | log("Checking restricted virtual package '%s' provides" % 75 | pkg.name, level=DEBUG) 76 | self.delete_package(cache, pkg) 77 | 78 | def delete_package(self, cache, pkg): 79 | """Deletes the package from the system. 80 | 81 | Deletes the package form the system, properly handling virtual 82 | packages. 83 | 84 | :param cache: the apt cache 85 | :param pkg: the package to remove 86 | """ 87 | if self.is_virtual_package(pkg): 88 | log("Package '%s' appears to be virtual - purging provides" % 89 | pkg.name, level=DEBUG) 90 | for _p in pkg.provides_list: 91 | self.delete_package(cache, _p[2].parent_pkg) 92 | elif not pkg.current_ver: 93 | log("Package '%s' not installed" % pkg.name, level=DEBUG) 94 | return 95 | else: 96 | log("Purging package '%s'" % pkg.name, level=DEBUG) 97 | apt_purge(pkg.name) 98 | 99 | def is_virtual_package(self, pkg): 100 | return (pkg.get('has_provides', False) and 101 | not pkg.get('has_versions', False)) 102 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Classic charm (with zaza): ./tox.ini 2 | # This file is managed centrally by release-tools and should not be modified 3 | # within individual charm repos. See the 'global' dir contents for available 4 | # choices of tox.ini for OpenStack Charms: 5 | # https://github.com/openstack-charmers/release-tools 6 | # 7 | # TODO: Distill the func test requirements from the lint/unit test 8 | # requirements. They are intertwined. Also, Zaza itself should specify 9 | # all of its own requirements and if it doesn't, fix it there. 10 | [tox] 11 | envlist = pep8,py3 12 | # NOTE: Avoid build/test env pollution by not enabling sitepackages. 13 | sitepackages = False 14 | # NOTE: Avoid false positives by not skipping missing interpreters. 15 | skip_missing_interpreters = False 16 | 17 | [testenv] 18 | # We use tox mainly for virtual environment management for test requirements 19 | # and do not install the charm code as a Python package into that environment. 20 | # Ref: https://tox.wiki/en/latest/config.html#skip_install 21 | skip_install = True 22 | setenv = VIRTUAL_ENV={envdir} 23 | PYTHONHASHSEED=0 24 | CHARM_DIR={envdir} 25 | commands = stestr run --slowest {posargs} 26 | allowlist_externals = 27 | charmcraft 28 | {toxinidir}/rename.sh 29 | passenv = 30 | HOME 31 | TERM 32 | CS_* 33 | OS_* 34 | TEST_* 35 | deps = -r{toxinidir}/test-requirements.txt 36 | 37 | [testenv:build] 38 | basepython = python3 39 | deps = -r{toxinidir}/build-requirements.txt 40 | # charmcraft clean is done to ensure that 41 | # `tox -e build` always performs a clean, repeatable build. 42 | # For faster rebuilds during development, 43 | # directly run `charmcraft -v pack && ./rename.sh`. 44 | commands = 45 | charmcraft clean 46 | charmcraft -v pack 47 | {toxinidir}/rename.sh 48 | charmcraft clean 49 | 50 | 51 | [testenv:py38] 52 | basepython = python3.8 53 | deps = -r{toxinidir}/requirements.txt 54 | -r{toxinidir}/test-requirements.txt 55 | 56 | [testenv:py310] 57 | basepython = python3.10 58 | deps = -r{toxinidir}/requirements.txt 59 | -r{toxinidir}/test-requirements.txt 60 | 61 | [testenv:py3] 62 | basepython = python3 63 | deps = -r{toxinidir}/requirements.txt 64 | -r{toxinidir}/test-requirements.txt 65 | 66 | [testenv:pep8] 67 | basepython = python3 68 | deps = flake8==7.1.1 69 | git+https://github.com/juju/charm-tools.git 70 | commands = flake8 {posargs} hooks unit_tests tests actions lib files 71 | charm-proof 72 | 73 | [testenv:cover] 74 | # Technique based heavily upon 75 | # https://github.com/openstack/nova/blob/master/tox.ini 76 | basepython = python3 77 | deps = -r{toxinidir}/requirements.txt 78 | -r{toxinidir}/test-requirements.txt 79 | setenv = 80 | {[testenv]setenv} 81 | PYTHON=coverage run 82 | commands = 83 | coverage erase 84 | stestr run --slowest {posargs} 85 | coverage combine 86 | coverage html -d cover 87 | coverage xml -o cover/coverage.xml 88 | coverage report 89 | 90 | [coverage:run] 91 | branch = True 92 | concurrency = multiprocessing 93 | parallel = True 94 | source = 95 | . 96 | omit = 97 | .tox/* 98 | */charmhelpers/* 99 | unit_tests/* 100 | 101 | [testenv:venv] 102 | basepython = python3 103 | commands = {posargs} 104 | 105 | [testenv:func-noop] 106 | basepython = python3 107 | commands = 108 | functest-run-suite --help 109 | 110 | [testenv:func] 111 | basepython = python3 112 | commands = 113 | functest-run-suite --keep-model 114 | 115 | [testenv:func-smoke] 116 | basepython = python3 117 | commands = 118 | functest-run-suite --keep-model --smoke 119 | 120 | [testenv:func-dev] 121 | basepython = python3 122 | commands = 123 | functest-run-suite --keep-model --dev 124 | 125 | [testenv:func-target] 126 | basepython = python3 127 | commands = 128 | functest-run-suite --keep-model --bundle {posargs} 129 | 130 | [flake8] 131 | ignore = E402,E226,W503,W504 132 | exclude = */charmhelpers 133 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/audits/apache.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import re 16 | import subprocess 17 | 18 | from charmhelpers.core.hookenv import ( 19 | log, 20 | INFO, 21 | ERROR, 22 | ) 23 | 24 | from charmhelpers.contrib.hardening.audits import BaseAudit 25 | 26 | 27 | class DisabledModuleAudit(BaseAudit): 28 | """Audits Apache2 modules. 29 | 30 | Determines if the apache2 modules are enabled. If the modules are enabled 31 | then they are removed in the ensure_compliance. 32 | """ 33 | def __init__(self, modules): 34 | if modules is None: 35 | self.modules = [] 36 | elif isinstance(modules, str): 37 | self.modules = [modules] 38 | else: 39 | self.modules = modules 40 | 41 | def ensure_compliance(self): 42 | """Ensures that the modules are not loaded.""" 43 | if not self.modules: 44 | return 45 | 46 | try: 47 | loaded_modules = self._get_loaded_modules() 48 | non_compliant_modules = [] 49 | for module in self.modules: 50 | if module in loaded_modules: 51 | log("Module '%s' is enabled but should not be." % 52 | (module), level=INFO) 53 | non_compliant_modules.append(module) 54 | 55 | if len(non_compliant_modules) == 0: 56 | return 57 | 58 | for module in non_compliant_modules: 59 | self._disable_module(module) 60 | self._restart_apache() 61 | except subprocess.CalledProcessError as e: 62 | log('Error occurred auditing apache module compliance. ' 63 | 'This may have been already reported. ' 64 | 'Output is: %s' % e.output, level=ERROR) 65 | 66 | @staticmethod 67 | def _get_loaded_modules(): 68 | """Returns the modules which are enabled in Apache.""" 69 | output = subprocess.check_output(['apache2ctl', '-M']).decode('utf-8') 70 | modules = [] 71 | for line in output.splitlines(): 72 | # Each line of the enabled module output looks like: 73 | # module_name (static|shared) 74 | # Plus a header line at the top of the output which is stripped 75 | # out by the regex. 76 | matcher = re.search(r'^ (\S*)_module (\S*)', line) 77 | if matcher: 78 | modules.append(matcher.group(1)) 79 | return modules 80 | 81 | @staticmethod 82 | def _disable_module(module): 83 | """Disables the specified module in Apache.""" 84 | try: 85 | subprocess.check_call(['a2dismod', module]) 86 | except subprocess.CalledProcessError as e: 87 | # Note: catch error here to allow the attempt of disabling 88 | # multiple modules in one go rather than failing after the 89 | # first module fails. 90 | log('Error occurred disabling module %s. ' 91 | 'Output is: %s' % (module, e.output), level=ERROR) 92 | 93 | @staticmethod 94 | def _restart_apache(): 95 | """Restarts the apache process""" 96 | subprocess.check_output(['service', 'apache2', 'restart']) 97 | 98 | @staticmethod 99 | def is_ssl_enabled(): 100 | """Check if SSL module is enabled or not""" 101 | return 'ssl' in DisabledModuleAudit._get_loaded_modules() 102 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/apache/checks/config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import re 17 | import subprocess 18 | 19 | 20 | from charmhelpers.core.hookenv import ( 21 | log, 22 | INFO, 23 | ) 24 | from charmhelpers.contrib.hardening.audits.file import ( 25 | FilePermissionAudit, 26 | DirectoryPermissionAudit, 27 | NoReadWriteForOther, 28 | TemplatedFile, 29 | DeletedFile 30 | ) 31 | from charmhelpers.contrib.hardening.audits.apache import DisabledModuleAudit 32 | from charmhelpers.contrib.hardening.apache import TEMPLATES_DIR 33 | from charmhelpers.contrib.hardening import utils 34 | 35 | 36 | def get_audits(): 37 | """Get Apache hardening config audits. 38 | 39 | :returns: dictionary of audits 40 | """ 41 | if subprocess.call(['which', 'apache2'], stdout=subprocess.PIPE) != 0: 42 | log("Apache server does not appear to be installed on this node - " 43 | "skipping apache hardening", level=INFO) 44 | return [] 45 | 46 | context = ApacheConfContext() 47 | settings = utils.get_settings('apache') 48 | audits = [ 49 | FilePermissionAudit(paths=os.path.join( 50 | settings['common']['apache_dir'], 'apache2.conf'), 51 | user='root', group='root', mode=0o0640), 52 | 53 | TemplatedFile(os.path.join(settings['common']['apache_dir'], 54 | 'mods-available/alias.conf'), 55 | context, 56 | TEMPLATES_DIR, 57 | mode=0o0640, 58 | user='root', 59 | service_actions=[{'service': 'apache2', 60 | 'actions': ['restart']}]), 61 | 62 | TemplatedFile(os.path.join(settings['common']['apache_dir'], 63 | 'conf-enabled/99-hardening.conf'), 64 | context, 65 | TEMPLATES_DIR, 66 | mode=0o0640, 67 | user='root', 68 | service_actions=[{'service': 'apache2', 69 | 'actions': ['restart']}]), 70 | 71 | DirectoryPermissionAudit(settings['common']['apache_dir'], 72 | user='root', 73 | group='root', 74 | mode=0o0750), 75 | 76 | DisabledModuleAudit(settings['hardening']['modules_to_disable']), 77 | 78 | NoReadWriteForOther(settings['common']['apache_dir']), 79 | 80 | DeletedFile(['/var/www/html/index.html']) 81 | ] 82 | 83 | return audits 84 | 85 | 86 | class ApacheConfContext(object): 87 | """Defines the set of key/value pairs to set in a apache config file. 88 | 89 | This context, when called, will return a dictionary containing the 90 | key/value pairs of setting to specify in the 91 | /etc/apache/conf-enabled/hardening.conf file. 92 | """ 93 | def __call__(self): 94 | settings = utils.get_settings('apache') 95 | ctxt = settings['hardening'] 96 | 97 | out = subprocess.check_output(['apache2', '-v']).decode('utf-8') 98 | ctxt['apache_version'] = re.search(r'.+version: Apache/(.+?)\s.+', 99 | out).group(1) 100 | ctxt['apache_icondir'] = '/usr/share/apache2/icons/' 101 | return ctxt 102 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/harden.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from collections import OrderedDict 16 | 17 | from charmhelpers.core.hookenv import ( 18 | config, 19 | log, 20 | DEBUG, 21 | WARNING, 22 | ) 23 | from charmhelpers.contrib.hardening.host.checks import run_os_checks 24 | from charmhelpers.contrib.hardening.ssh.checks import run_ssh_checks 25 | from charmhelpers.contrib.hardening.mysql.checks import run_mysql_checks 26 | from charmhelpers.contrib.hardening.apache.checks import run_apache_checks 27 | 28 | _DISABLE_HARDENING_FOR_UNIT_TEST = False 29 | 30 | 31 | def harden(overrides=None): 32 | """Hardening decorator. 33 | 34 | This is the main entry point for running the hardening stack. In order to 35 | run modules of the stack you must add this decorator to charm hook(s) and 36 | ensure that your charm config.yaml contains the 'harden' option set to 37 | one or more of the supported modules. Setting these will cause the 38 | corresponding hardening code to be run when the hook fires. 39 | 40 | This decorator can and should be applied to more than one hook or function 41 | such that hardening modules are called multiple times. This is because 42 | subsequent calls will perform auditing checks that will report any changes 43 | to resources hardened by the first run (and possibly perform compliance 44 | actions as a result of any detected infractions). 45 | 46 | :param overrides: Optional list of stack modules used to override those 47 | provided with 'harden' config. 48 | :returns: Returns value returned by decorated function once executed. 49 | """ 50 | if overrides is None: 51 | overrides = [] 52 | 53 | def _harden_inner1(f): 54 | _logged = False 55 | 56 | def _harden_inner2(*args, **kwargs): 57 | # knock out hardening via a config var; normally it won't get 58 | # disabled. 59 | nonlocal _logged 60 | if _DISABLE_HARDENING_FOR_UNIT_TEST: 61 | return f(*args, **kwargs) 62 | if not _logged: 63 | log("Hardening function '%s'" % (f.__name__), level=DEBUG) 64 | _logged = True 65 | RUN_CATALOG = OrderedDict([('os', run_os_checks), 66 | ('ssh', run_ssh_checks), 67 | ('mysql', run_mysql_checks), 68 | ('apache', run_apache_checks)]) 69 | 70 | enabled = overrides[:] or (config("harden") or "").split() 71 | if enabled: 72 | modules_to_run = [] 73 | # modules will always be performed in the following order 74 | for module, func in RUN_CATALOG.items(): 75 | if module in enabled: 76 | enabled.remove(module) 77 | modules_to_run.append(func) 78 | 79 | if enabled: 80 | log("Unknown hardening modules '%s' - ignoring" % 81 | (', '.join(enabled)), level=WARNING) 82 | 83 | for hardener in modules_to_run: 84 | log("Executing hardening module '%s'" % 85 | (hardener.__name__), level=DEBUG) 86 | hardener() 87 | else: 88 | log("No hardening applied to '%s'" % (f.__name__), level=DEBUG) 89 | 90 | return f(*args, **kwargs) 91 | return _harden_inner2 92 | 93 | return _harden_inner1 94 | -------------------------------------------------------------------------------- /actions/zap_disk.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2018 Canonical Ltd 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import os 18 | import sys 19 | 20 | sys.path.append('lib') 21 | sys.path.append('hooks') 22 | 23 | import charmhelpers.core.hookenv as hookenv 24 | from charmhelpers.contrib.storage.linux.utils import ( 25 | is_block_device, 26 | is_device_mounted, 27 | zap_disk, 28 | ) 29 | from charmhelpers.core.unitdata import kv 30 | from charms_ceph.utils import is_active_bluestore_device 31 | from charms_ceph.utils import is_mapped_luks_device 32 | from charmhelpers.contrib.storage.linux.lvm import is_lvm_physical_volume 33 | from charmhelpers.core.hookenv import log 34 | 35 | 36 | class ZapDiskError(Exception): 37 | pass 38 | 39 | 40 | def get_devices(): 41 | """Parse 'devices' action parameter, returns list.""" 42 | devices, errors = [], [] 43 | 44 | for path in hookenv.action_get('devices').split(' '): 45 | path = path.strip() 46 | if not os.path.isabs(path): 47 | errors.append('{}: Not absolute path.'.format(path)) 48 | elif not os.path.exists(path): 49 | errors.append('{}: Device does not exist.'.format(path)) 50 | else: 51 | devices.append(path) 52 | 53 | if errors: 54 | raise ZapDiskError(", ".join(errors)) 55 | 56 | return devices 57 | 58 | 59 | def zap(): 60 | if not hookenv.action_get('i-really-mean-it'): 61 | hookenv.action_fail('i-really-mean-it is a required parameter') 62 | return 63 | 64 | failed_devices = [] 65 | not_block_devices = [] 66 | lvm_devices = [] 67 | try: 68 | devices = get_devices() 69 | except ZapDiskError as error: 70 | hookenv.action_fail("Failed due to: {}".format(error)) 71 | return 72 | 73 | for device in devices: 74 | if is_lvm_physical_volume(device): 75 | lvm_devices.append(device) 76 | if not is_block_device(device): 77 | not_block_devices.append(device) 78 | if (is_device_mounted(device) or 79 | is_active_bluestore_device(device) or 80 | is_mapped_luks_device(device)): 81 | failed_devices.append(device) 82 | 83 | if lvm_devices or failed_devices or not_block_devices: 84 | message = "" 85 | if lvm_devices: 86 | log('Cannot zap a device used by lvm') 87 | message = "{} devices are lvm devices: {}".format( 88 | len(lvm_devices), 89 | ", ".join(lvm_devices)) 90 | if failed_devices: 91 | message += "{} devices are mounted: {}".format( 92 | len(failed_devices), 93 | ", ".join(failed_devices)) 94 | if not_block_devices: 95 | if len(message): 96 | message += "\n\n" 97 | message += "{} devices are not block devices: {}".format( 98 | len(not_block_devices), 99 | ", ".join(not_block_devices)) 100 | hookenv.action_fail(message) 101 | return 102 | db = kv() 103 | used_devices = db.get('osd-devices', []) 104 | for device in devices: 105 | zap_disk(device) 106 | if device in used_devices: 107 | used_devices.remove(device) 108 | db.set('osd-devices', used_devices) 109 | db.flush() 110 | hookenv.action_set({ 111 | 'message': "{} disk(s) have been zapped, to use them as OSDs, run: \n" 112 | "juju run {} add-disk osd-devices=\"{}\"".format( 113 | len(devices), 114 | hookenv.local_unit(), 115 | " ".join(devices)) 116 | }) 117 | 118 | 119 | if __name__ == "__main__": 120 | zap() 121 | -------------------------------------------------------------------------------- /hooks/charmhelpers/core/strutils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2014-2015 Canonical Limited. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import re 19 | 20 | TRUTHY_STRINGS = {'y', 'yes', 'true', 't', 'on'} 21 | FALSEY_STRINGS = {'n', 'no', 'false', 'f', 'off'} 22 | 23 | 24 | def bool_from_string(value, truthy_strings=TRUTHY_STRINGS, falsey_strings=FALSEY_STRINGS, assume_false=False): 25 | """Interpret string value as boolean. 26 | 27 | Returns True if value translates to True otherwise False. 28 | """ 29 | if isinstance(value, str): 30 | value = str(value) 31 | else: 32 | msg = "Unable to interpret non-string value '%s' as boolean" % (value) 33 | raise ValueError(msg) 34 | 35 | value = value.strip().lower() 36 | 37 | if value in truthy_strings: 38 | return True 39 | elif value in falsey_strings or assume_false: 40 | return False 41 | 42 | msg = "Unable to interpret string value '%s' as boolean" % (value) 43 | raise ValueError(msg) 44 | 45 | 46 | def bytes_from_string(value): 47 | """Interpret human readable string value as bytes. 48 | 49 | Returns int 50 | """ 51 | BYTE_POWER = { 52 | 'K': 1, 53 | 'KB': 1, 54 | 'M': 2, 55 | 'MB': 2, 56 | 'G': 3, 57 | 'GB': 3, 58 | 'T': 4, 59 | 'TB': 4, 60 | 'P': 5, 61 | 'PB': 5, 62 | } 63 | if isinstance(value, str): 64 | value = str(value) 65 | else: 66 | msg = "Unable to interpret non-string value '%s' as bytes" % (value) 67 | raise ValueError(msg) 68 | matches = re.match("([0-9]+)([a-zA-Z]+)", value) 69 | if matches: 70 | size = int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)]) 71 | else: 72 | # Assume that value passed in is bytes 73 | try: 74 | size = int(value) 75 | except ValueError: 76 | msg = "Unable to interpret string value '%s' as bytes" % (value) 77 | raise ValueError(msg) 78 | return size 79 | 80 | 81 | class BasicStringComparator(object): 82 | """Provides a class that will compare strings from an iterator type object. 83 | Used to provide > and < comparisons on strings that may not necessarily be 84 | alphanumerically ordered. e.g. OpenStack or Ubuntu releases AFTER the 85 | z-wrap. 86 | """ 87 | 88 | _list = None 89 | 90 | def __init__(self, item): 91 | if self._list is None: 92 | raise Exception("Must define the _list in the class definition!") 93 | try: 94 | self.index = self._list.index(item) 95 | except Exception: 96 | raise KeyError("Item '{}' is not in list '{}'" 97 | .format(item, self._list)) 98 | 99 | def __eq__(self, other): 100 | assert isinstance(other, str) or isinstance(other, self.__class__) 101 | return self.index == self._list.index(other) 102 | 103 | def __ne__(self, other): 104 | return not self.__eq__(other) 105 | 106 | def __lt__(self, other): 107 | assert isinstance(other, str) or isinstance(other, self.__class__) 108 | return self.index < self._list.index(other) 109 | 110 | def __ge__(self, other): 111 | return not self.__lt__(other) 112 | 113 | def __gt__(self, other): 114 | assert isinstance(other, str) or isinstance(other, self.__class__) 115 | return self.index > self._list.index(other) 116 | 117 | def __le__(self, other): 118 | return not self.__gt__(other) 119 | 120 | def __str__(self): 121 | """Always give back the item at the index so it can be used in 122 | comparisons like: 123 | 124 | s_mitaka = CompareOpenStack('mitaka') 125 | s_newton = CompareOpenstack('newton') 126 | 127 | assert s_newton > s_mitaka 128 | 129 | @returns: 130 | """ 131 | return self._list[self.index] 132 | -------------------------------------------------------------------------------- /unit_tests/test_config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Ltd 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os.path 16 | import shutil 17 | import tempfile 18 | import sys 19 | import test_utils 20 | 21 | from unittest.mock import patch, MagicMock 22 | 23 | # python-apt is not installed as part of test-requirements but is imported by 24 | # some charmhelpers modules so create a fake import. 25 | mock_apt = MagicMock() 26 | sys.modules['apt'] = mock_apt 27 | mock_apt.apt_pkg = MagicMock() 28 | 29 | 30 | with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec: 31 | mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f: 32 | lambda *args, **kwargs: f(*args, **kwargs)) 33 | import ceph_hooks as hooks 34 | 35 | TO_PATCH = [ 36 | 'config', 37 | 'is_block_device', 38 | 'get_blacklist', 39 | ] 40 | 41 | 42 | class GetDevicesTestCase(test_utils.CharmTestCase): 43 | 44 | def setUp(self): 45 | super(GetDevicesTestCase, self).setUp(hooks, TO_PATCH) 46 | self.config.side_effect = self.test_config.get 47 | self.tmp_dir = tempfile.mkdtemp() 48 | self.bd = { 49 | os.path.join(self.tmp_dir, "device1"): True, 50 | os.path.join(self.tmp_dir, "device1"): True, 51 | os.path.join(self.tmp_dir, "link"): True, 52 | os.path.join(self.tmp_dir, "device"): True, 53 | } 54 | self.is_block_device.side_effect = lambda x: self.bd.get(x, False) 55 | self.get_blacklist.return_value = [] 56 | self.addCleanup(shutil.rmtree, self.tmp_dir) 57 | 58 | def test_get_devices_empty(self): 59 | """ 60 | If osd-devices is set to an empty string, get_devices() returns 61 | an empty list. 62 | """ 63 | self.test_config.set("osd-devices", "") 64 | self.assertEqual([], hooks.get_devices()) 65 | 66 | def test_get_devices_non_existing_files(self): 67 | """ 68 | If osd-devices points to a file that doesn't exist, it's still 69 | returned by get_devices(). 70 | """ 71 | non_existing = os.path.join(self.tmp_dir, "no-such-file") 72 | self.test_config.set("osd-devices", non_existing) 73 | self.assertEqual([non_existing], hooks.get_devices()) 74 | 75 | def test_get_devices_multiple(self): 76 | """ 77 | Multiple devices can be specified in osd-devices by separating 78 | them with spaces. 79 | """ 80 | device1 = os.path.join(self.tmp_dir, "device1") 81 | device2 = os.path.join(self.tmp_dir, "device2") 82 | self.test_config.set("osd-devices", "{} {}".format(device1, device2)) 83 | self.assertEqual([device1, device2], hooks.get_devices()) 84 | 85 | def test_get_devices_extra_spaces(self): 86 | """ 87 | Multiple spaces do not result in additional devices. 88 | """ 89 | device1 = os.path.join(self.tmp_dir, "device1") 90 | device2 = os.path.join(self.tmp_dir, "device2") 91 | self.test_config.set("osd-devices", "{} {}".format(device1, device2)) 92 | self.assertEqual([device1, device2], hooks.get_devices()) 93 | 94 | def test_get_devices_non_absolute_path(self): 95 | """ 96 | Charm does not allow relative paths as this may result in a path 97 | on the root device/within the charm directory. 98 | """ 99 | device1 = os.path.join(self.tmp_dir, "device1") 100 | device2 = "foo" 101 | self.test_config.set("osd-devices", "{} {}".format(device1, device2)) 102 | self.assertEqual([device1], hooks.get_devices()) 103 | 104 | def test_get_devices_symlink(self): 105 | """ 106 | If a symlink is specified in osd-devices, get_devices() does not 107 | resolve it and returns the symlink provided. 108 | """ 109 | device = os.path.join(self.tmp_dir, "device") 110 | link = os.path.join(self.tmp_dir, "link") 111 | os.symlink(device, link) 112 | self.test_config.set("osd-devices", link) 113 | self.assertEqual([link], hooks.get_devices()) 114 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/hardening/host/checks/pam.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from subprocess import ( 16 | check_output, 17 | CalledProcessError, 18 | ) 19 | 20 | from charmhelpers.core.hookenv import ( 21 | log, 22 | DEBUG, 23 | ERROR, 24 | ) 25 | from charmhelpers.fetch import ( 26 | apt_install, 27 | apt_purge, 28 | apt_update, 29 | ) 30 | from charmhelpers.contrib.hardening.audits.file import ( 31 | TemplatedFile, 32 | DeletedFile, 33 | ) 34 | from charmhelpers.contrib.hardening import utils 35 | from charmhelpers.contrib.hardening.host import TEMPLATES_DIR 36 | 37 | 38 | def get_audits(): 39 | """Get OS hardening PAM authentication audits. 40 | 41 | :returns: dictionary of audits 42 | """ 43 | audits = [] 44 | 45 | settings = utils.get_settings('os') 46 | 47 | if settings['auth']['pam_passwdqc_enable']: 48 | audits.append(PasswdqcPAM('/etc/passwdqc.conf')) 49 | 50 | if settings['auth']['retries']: 51 | audits.append(Tally2PAM('/usr/share/pam-configs/tally2')) 52 | else: 53 | audits.append(DeletedFile('/usr/share/pam-configs/tally2')) 54 | 55 | return audits 56 | 57 | 58 | class PasswdqcPAMContext(object): 59 | 60 | def __call__(self): 61 | ctxt = {} 62 | settings = utils.get_settings('os') 63 | 64 | ctxt['auth_pam_passwdqc_options'] = \ 65 | settings['auth']['pam_passwdqc_options'] 66 | 67 | return ctxt 68 | 69 | 70 | class PasswdqcPAM(TemplatedFile): 71 | """The PAM Audit verifies the linux PAM settings.""" 72 | def __init__(self, path): 73 | super(PasswdqcPAM, self).__init__(path=path, 74 | template_dir=TEMPLATES_DIR, 75 | context=PasswdqcPAMContext(), 76 | user='root', 77 | group='root', 78 | mode=0o0640) 79 | 80 | def pre_write(self): 81 | # Always remove? 82 | for pkg in ['libpam-ccreds', 'libpam-cracklib']: 83 | log("Purging package '%s'" % pkg, level=DEBUG), 84 | apt_purge(pkg) 85 | 86 | apt_update(fatal=True) 87 | for pkg in ['libpam-passwdqc']: 88 | log("Installing package '%s'" % pkg, level=DEBUG), 89 | apt_install(pkg) 90 | 91 | def post_write(self): 92 | """Updates the PAM configuration after the file has been written""" 93 | try: 94 | check_output(['pam-auth-update', '--package']) 95 | except CalledProcessError as e: 96 | log('Error calling pam-auth-update: %s' % e, level=ERROR) 97 | 98 | 99 | class Tally2PAMContext(object): 100 | 101 | def __call__(self): 102 | ctxt = {} 103 | settings = utils.get_settings('os') 104 | 105 | ctxt['auth_lockout_time'] = settings['auth']['lockout_time'] 106 | ctxt['auth_retries'] = settings['auth']['retries'] 107 | 108 | return ctxt 109 | 110 | 111 | class Tally2PAM(TemplatedFile): 112 | """The PAM Audit verifies the linux PAM settings.""" 113 | def __init__(self, path): 114 | super(Tally2PAM, self).__init__(path=path, 115 | template_dir=TEMPLATES_DIR, 116 | context=Tally2PAMContext(), 117 | user='root', 118 | group='root', 119 | mode=0o0640) 120 | 121 | def pre_write(self): 122 | # Always remove? 123 | apt_purge('libpam-ccreds') 124 | apt_update(fatal=True) 125 | apt_install('libpam-modules') 126 | 127 | def post_write(self): 128 | """Updates the PAM configuration after the file has been written""" 129 | try: 130 | check_output(['pam-auth-update', '--package']) 131 | except CalledProcessError as e: 132 | log('Error calling pam-auth-update: %s' % e, level=ERROR) 133 | -------------------------------------------------------------------------------- /hooks/charmhelpers/contrib/openstack/files/check_deferred_restarts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright 2014-2022 Canonical Limited. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """ 18 | Checks for services with deferred restarts. 19 | 20 | This Nagios check will parse /var/lib/policy-rd.d/ 21 | to find any restarts that are currently deferred. 22 | """ 23 | 24 | import argparse 25 | import glob 26 | import sys 27 | import yaml 28 | 29 | 30 | DEFERRED_EVENTS_DIR = '/var/lib/policy-rc.d' 31 | 32 | 33 | def get_deferred_events(): 34 | """Return a list of deferred events dicts from policy-rc.d files. 35 | 36 | Events are read from DEFERRED_EVENTS_DIR and are of the form: 37 | { 38 | action: restart, 39 | policy_requestor_name: rabbitmq-server, 40 | policy_requestor_type: charm, 41 | reason: 'Pkg update', 42 | service: rabbitmq-server, 43 | time: 1614328743 44 | } 45 | 46 | :raises OSError: Raised in case of a system error while reading a policy file 47 | :raises yaml.YAMLError: Raised if parsing a policy file fails 48 | 49 | :returns: List of deferred event dictionaries 50 | :rtype: list 51 | """ 52 | deferred_events_files = glob.glob( 53 | '{}/*.deferred'.format(DEFERRED_EVENTS_DIR)) 54 | 55 | deferred_events = [] 56 | for event_file in deferred_events_files: 57 | with open(event_file, 'r') as f: 58 | event = yaml.safe_load(f) 59 | deferred_events.append(event) 60 | 61 | return deferred_events 62 | 63 | 64 | def get_deferred_restart_services(application=None): 65 | """Returns a list of services with deferred restarts. 66 | 67 | :param str application: Name of the application that blocked the service restart. 68 | If application is None, all services with deferred restarts 69 | are returned. Services which are blocked by a non-charm 70 | requestor are always returned. 71 | 72 | :raises OSError: Raised in case of a system error while reading a policy file 73 | :raises yaml.YAMLError: Raised if parsing a policy file fails 74 | 75 | :returns: List of services with deferred restarts belonging to application. 76 | :rtype: list 77 | """ 78 | 79 | deferred_restart_events = filter( 80 | lambda e: e['action'] == 'restart', get_deferred_events()) 81 | 82 | deferred_restart_services = set() 83 | for restart_event in deferred_restart_events: 84 | if application: 85 | if ( 86 | restart_event['policy_requestor_type'] != 'charm' or 87 | restart_event['policy_requestor_type'] == 'charm' and 88 | restart_event['policy_requestor_name'] == application 89 | ): 90 | deferred_restart_services.add(restart_event['service']) 91 | else: 92 | deferred_restart_services.add(restart_event['service']) 93 | 94 | return list(deferred_restart_services) 95 | 96 | 97 | def main(): 98 | """Check for services with deferred restarts.""" 99 | parser = argparse.ArgumentParser( 100 | description='Check for services with deferred restarts') 101 | parser.add_argument( 102 | '--application', help='Check services belonging to this application only') 103 | 104 | args = parser.parse_args() 105 | 106 | services = set(get_deferred_restart_services(args.application)) 107 | 108 | if len(services) == 0: 109 | print('OK: No deferred service restarts.') 110 | sys.exit(0) 111 | else: 112 | print( 113 | 'CRITICAL: Restarts are deferred for services: {}.'.format(', '.join(services))) 114 | sys.exit(1) 115 | 116 | 117 | if __name__ == '__main__': 118 | try: 119 | main() 120 | except OSError as e: 121 | print('CRITICAL: A system error occurred: {} ({})'.format(e.errno, e.strerror)) 122 | sys.exit(1) 123 | except yaml.YAMLError as e: 124 | print('CRITICAL: Failed to parse a policy file: {}'.format(str(e))) 125 | sys.exit(1) 126 | except Exception as e: 127 | print('CRITICAL: An unknown error occurred: {}'.format(str(e))) 128 | sys.exit(1) 129 | --------------------------------------------------------------------------------