├── 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 |
--------------------------------------------------------------------------------