Commit e1673be6 authored by Ian Patel's avatar Ian Patel
Browse files

Improve default stunnel behavior

* Improve detection of root user
* Require OCSPaia and checkHost options for stunnel
* Install and use a hand-managed trust store
parent 555154b7
amazon-efs-utils*
!dist/amazon-efs-utils.control
!dist/amazon-efs-utils.spec
/amazon-efs-utils*
.coverage
.pytest_cache
......
MIT License
Copyright 2017 Amazon.com, Inc. or its affiliates.
Copyright 2017 Amazon.com, Inc. or its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......
......@@ -12,22 +12,37 @@ SPECFILE = $(PACKAGE_NAME).spec
BUILD_DIR = build/rpmbuild
export PYTHONPATH := $(shell pwd)/src
.PHONY: clean
clean:
rm -rf $(BUILD_DIR)
rm -rf $(PACKAGE_NAME)
rm -f $(SOURCE_TARBALL)
rm -f $(SPECFILE)
.PHONY: tarball
tarball: clean
mkdir -p $(PACKAGE_NAME)
cp -rp dist $(PACKAGE_NAME)
cp -rp src $(PACKAGE_NAME)
mkdir -p $(PACKAGE_NAME)/dist
cp -p dist/amazon-efs-mount-watchdog.conf $(PACKAGE_NAME)/dist
cp -p dist/amazon-efs-mount-watchdog.service $(PACKAGE_NAME)/dist
cp -p dist/efs-utils.conf $(PACKAGE_NAME)/dist
cp -p dist/efs-utils.crt $(PACKAGE_NAME)/dist
mkdir -p $(PACKAGE_NAME)/src
cp -rp src/mount_efs $(PACKAGE_NAME)/src
cp -rp src/watchdog $(PACKAGE_NAME)/src
tar -czf $(SOURCE_TARBALL) $(PACKAGE_NAME)/*
.PHONY: specfile
specfile: clean
ln -sf dist/$(SPECFILE) $(SPECFILE)
.PHONY: sources
sources: tarball specfile
.PHONY: rpm-only
rpm-only:
mkdir -p $(BUILD_DIR)/{SPECS,COORD_SOURCES,DATA_SOURCES,BUILD,RPMS,SOURCES,SRPMS}
cp $(SPECFILE) $(BUILD_DIR)/SPECS
......@@ -35,8 +50,13 @@ rpm-only:
rpmbuild -ba --define "_topdir `pwd`/$(BUILD_DIR)" $(BUILD_DIR)/SPECS/$(SPECFILE)
cp $(BUILD_DIR)/RPMS/*/*rpm build
.PHONY: rpm
rpm: sources rpm-only
.PHONY: deb
deb:
./build-deb.sh
.PHONY: test
test:
pytest
......
......@@ -13,6 +13,13 @@ The `efs-utils` package has been verified against the following Linux distributi
| Debian 9 | `deb` | `systemd` |
| Ubuntu 16.04 | `deb` | `systemd` |
## Prerequisites
* `nfs-utils` (RHEL/CentOS/Amazon Linux) or `nfs-common` (Debian/Ubuntu)
* OpenSSL 1.0.2+
* Python 2.7+
* `stunnel` 4.56+
## Installation
### On Amazon Linux distributions
......@@ -103,6 +110,12 @@ For more information on mounting with the mount helper, see the [documentation](
`efs-utils` contains a watchdog process to monitor the health of TLS mounts. This process is managed by either `upstart` or `systemd` depending on your Linux distribution, and is started automatically the first time an EFS file system is mounted over TLS.
## Upgrading stunnel for RHEL/CentOS
By default, when using the EFS mount helper with TLS, it enforces use of the Online Certificate Status Protocol (OCSP) and certificate hostname checking. The EFS mount helper uses the `stunnel` program for its TLS functionality. Please note that some versions of Linux do not include a version of `stunnel` that supports these TLS features by default. When using such a Linux version, mounting an EFS file system using TLS will fail.
Once you’ve installed the `amazon-efs-utils` package, to upgrade your system’s version of `stunnel`, see [Upgrading Stunnel](https://docs.aws.amazon.com/efs/latest/ug/using-amazon-efs-utils.html#upgrading-stunnel).
## License Summary
This code is made available under the MIT license.
......
......@@ -11,6 +11,7 @@ set -ex
BASE_DIR=$(pwd)
BUILD_ROOT=${BASE_DIR}/build/debbuild
VERSION=1.2
echo 'Cleaning deb build workspace'
rm -rf ${BUILD_ROOT}
......@@ -27,12 +28,12 @@ mkdir -p ${BUILD_ROOT}/var/log/amazon/efs
echo 'Copying application files'
install -p -m 644 dist/amazon-efs-mount-watchdog.conf ${BUILD_ROOT}/etc/init
install -p -m 644 dist/amazon-efs-mount-watchdog.service ${BUILD_ROOT}/etc/systemd/system
install -p -m 444 dist/efs-utils.crt ${BUILD_ROOT}/etc/amazon/efs
install -p -m 644 dist/efs-utils.conf ${BUILD_ROOT}/etc/amazon/efs
install -p -m 755 src/mount_efs/__init__.py ${BUILD_ROOT}/sbin/mount.efs
install -p -m 755 src/watchdog/__init__.py ${BUILD_ROOT}/usr/bin/amazon-efs-mount-watchdog
echo 'Copying install scripts'
install -p -m 755 dist/scriptlets/before-upgrade ${BUILD_ROOT}/preinst
install -p -m 755 dist/scriptlets/after-install-upgrade ${BUILD_ROOT}/postinst
install -p -m 755 dist/scriptlets/before-remove ${BUILD_ROOT}/prerm
install -p -m 755 dist/scriptlets/after-remove ${BUILD_ROOT}/postrm
......@@ -40,6 +41,9 @@ install -p -m 755 dist/scriptlets/after-remove ${BUILD_ROOT}/postrm
echo 'Copying control file'
install -p -m 644 dist/amazon-efs-utils.control ${BUILD_ROOT}/control
echo 'Copying conffiles'
install -p -m 644 dist/amazon-efs-utils.conffiles ${BUILD_ROOT}/conffiles
echo 'Creating deb binary file'
echo '2.0'> ${BUILD_ROOT}/debian-binary
......@@ -48,12 +52,12 @@ find ${BUILD_ROOT} -type d | xargs chmod 755;
echo 'Creating tar'
cd ${BUILD_ROOT}
tar czf control.tar.gz control preinst postinst prerm postrm --owner=0 --group=0
tar czf control.tar.gz control conffiles postinst prerm postrm --owner=0 --group=0
tar czf data.tar.gz etc sbin usr var --owner=0 --group=0
cd ${BASE_DIR}
echo 'Building deb'
DEB=${BUILD_ROOT}/amazon-efs-utils-1.0-1.deb
DEB=${BUILD_ROOT}/amazon-efs-utils-${VERSION}-1.deb
ar r ${DEB} ${BUILD_ROOT}/debian-binary
ar r ${DEB} ${BUILD_ROOT}/control.tar.gz
ar r ${DEB} ${BUILD_ROOT}/data.tar.gz
......
/etc/amazon/efs/efs-utils.conf
Package: amazon-efs-utils
Architecture: all
Version: 1.0
Version: 1.2
Section: utils
Depends: python|python2, nfs-common, stunnel4 (>= 4.56)
Priority: optional
......
......@@ -8,14 +8,19 @@
%if 0%{?amzn1}
%global python_requires system-python
%global with_systemd 0
%else
%global python_requires python2
%endif
%if 0%{?amzn1} || 0%{?rhel} == 6
%global with_systemd 0
%else
%global with_systemd 1
%endif
Name : amazon-efs-utils
Version : 1.1
Version : 1.2
Release : 1%{?dist}
Summary : This package provides utilities for simplifying the use of EFS file systems
......@@ -64,6 +69,7 @@ mkdir -p %{buildroot}%{_bindir}
mkdir -p %{buildroot}%{_localstatedir}/log/amazon/efs
install -p -m 644 %{_builddir}/%{name}/dist/efs-utils.conf %{buildroot}%{_sysconfdir}/amazon/efs
install -p -m 444 %{_builddir}/%{name}/dist/efs-utils.crt %{buildroot}%{_sysconfdir}/amazon/efs
install -p -m 755 %{_builddir}/%{name}/src/mount_efs/__init__.py %{buildroot}/sbin/mount.efs
install -p -m 755 %{_builddir}/%{name}/src/watchdog/__init__.py %{buildroot}%{_bindir}/amazon-efs-mount-watchdog
......@@ -74,6 +80,7 @@ install -p -m 755 %{_builddir}/%{name}/src/watchdog/__init__.py %{buildroot}%{_b
%else
%config(noreplace) %{_sysconfdir}/init/amazon-efs-mount-watchdog.conf
%endif
%{_sysconfdir}/amazon/efs/efs-utils.crt
/sbin/mount.efs
%{_bindir}/amazon-efs-mount-watchdog
/var/log/amazon
......
......@@ -15,11 +15,11 @@ logging_file_count = 10
dns_name_format = {fs_id}.efs.{region}.amazonaws.com
stunnel_debug_enabled = false
# Validate the certificate hostname on mount. Requires stunnel >= 5.15.
stunnel_check_cert_hostname = false
# Validate the certificate hostname on mount. This option is not supported by certain stunnel versions.
stunnel_check_cert_hostname = true
# Use OCSP to check certificate validity. Requires stunnel >= 5.10.
stunnel_check_cert_validity = false
# Use OCSP to check certificate validity. This option is not supported by certain stunnel versions.
stunnel_check_cert_validity = true
# Define the port range that the TLS tunnel will choose from
port_range_lower_bound = 20049
......
#
# Copyright 2017-2018 Amazon.com, Inc. and its affiliates. All Rights Reserved.
#
# Licensed under the MIT License. See the LICENSE accompanying this file
# for the specific language governing permissions and limitations under
# the License.
#
# Amazon Root CA 1
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL
MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj
ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM
9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw
IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6
VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L
93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm
jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA
A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI
U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs
N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv
o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU
5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy
rqXRfboQnoZsG4q5WTP468SQvvG5
-----END CERTIFICATE-----
# Amazon Root CA 2
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF
ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL
MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK
gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ
W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg
1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K
8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r
2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me
z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR
8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj
mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz
7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6
+XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI
0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB
Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm
UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2
LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY
+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS
k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl
7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm
btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl
urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+
fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63
n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE
76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H
9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT
4PsJYGw=
-----END CERTIFICATE-----
# Amazon Root CA 3
-----BEGIN CERTIFICATE-----
MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl
ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr
ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr
BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM
YyRIHN8wfdVoOw==
-----END CERTIFICATE-----
# Amazon Root CA 4
-----BEGIN CERTIFICATE-----
MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi
9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk
M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB
MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw
CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
1KyLa2tJElMzrdfkviT8tQp21KW8EA==
-----END CERTIFICATE-----
\ No newline at end of file
if [ $(cat /proc/1/comm) = systemd ]; then
systemctl daemon-reload
fi
if [ -z "$2" ]; then
if [ $(cat /proc/1/comm) = systemd ]; then
systemctl daemon-reload
fi
fi
\ No newline at end of file
if [ $(cat /proc/1/comm) = init ]; then
/sbin/stop amazon-efs-mount-watchdog &> /dev/null || true
elif [ $(cat /proc/1/comm) = systemd ]; then
systemctl --no-reload disable amazon-efs-mount-watchdog.service &> /dev/null || true
systemctl stop amazon-efs-mount-watchdog.service &> /dev/null || true
if [ -z "$2" ]; then
if [ $(cat /proc/1/comm) = init ]; then
/sbin/stop amazon-efs-mount-watchdog &> /dev/null || true
elif [ $(cat /proc/1/comm) = systemd ]; then
systemctl --no-reload disable amazon-efs-mount-watchdog.service &> /dev/null || true
systemctl stop amazon-efs-mount-watchdog.service &> /dev/null || true
fi
fi
if [ $(cat /proc/1/comm) = init ]; then
/sbin/stop amazon-efs-mount-watchdog &> /dev/null || true
elif [ $(cat /proc/1/comm) = systemd ]; then
systemctl stop amazon-efs-mount-watchdog.service &> /dev/null || true
fi
......@@ -30,7 +30,6 @@
#
# The script will add recommended mount options, if not provided in fstab.
import getpass
import json
import logging
import os
......@@ -55,7 +54,7 @@ except ImportError:
from urllib.error import URLError
from urllib.request import urlopen
VERSION = 1.0
VERSION = '1.2'
CONFIG_FILE = '/etc/amazon/efs/efs-utils.conf'
CONFIG_SECTION = 'mount'
......@@ -70,12 +69,7 @@ FS_NAME_RE = re.compile('^(?P<fs_id>fs-[0-9a-f]+)(?::(?P<path>/.*))?$')
INSTANCE_METADATA_SERVICE_URL = 'http://169.254.169.254/latest/dynamic/instance-identity/document/'
DEFAULT_STUNNEL_VERIFY_LEVEL = 2
DEFAULT_STUNNEL_CAFILE_PATHS = [
'/etc/pki/tls/certs/ca-bundle.crt',
'/etc/ssl/certs/ca-certificates.crt',
]
TLS_TUNNEL_BUFFER_SIZE = 4096 + 1024 * 1024
DEFAULT_STUNNEL_CAFILE = '/etc/amazon/efs/efs-utils.crt'
EFS_ONLY_OPTIONS = [
'cafile',
......@@ -192,7 +186,7 @@ def choose_tls_port(config):
def get_mount_specific_filename(fs_id, mountpoint, tls_port):
return '%s.%s.%d' % (fs_id, os.path.abspath(mountpoint).replace(os.sep, '.').lstrip('.'), tls_port)
return '%s.%s.%d' % (fs_id, os.path.abspath(mountpoint).replace(os.sep, '.').lstrip('.'), tls_port)
def serialize_stunnel_config(config, header=None):
......@@ -211,22 +205,40 @@ def serialize_stunnel_config(config, header=None):
return lines
def add_stunnel_ca_options(efs_config, options, default_stunnel_cafile_paths=DEFAULT_STUNNEL_CAFILE_PATHS):
if 'capath' in options:
efs_config['CApath'] = options['capath']
elif 'cafile' in options:
efs_config['CAfile'] = options['cafile']
else:
for cafile_path in default_stunnel_cafile_paths:
if os.path.exists(cafile_path):
efs_config['CAfile'] = cafile_path
break
else:
fatal_error('Failed to find a certificate authority file for verification',
'Failed to find a CAfile. defaults="%s"' % DEFAULT_STUNNEL_CAFILE_PATHS)
def add_stunnel_ca_options(efs_config, stunnel_cafile=DEFAULT_STUNNEL_CAFILE):
if not os.path.exists(stunnel_cafile):
fatal_error('Failed to find the EFS certificate authority file for verification',
'Failed to find the EFS CAfile "%s"' % stunnel_cafile)
efs_config['CAfile'] = stunnel_cafile
def is_stunnel_option_supported(stunnel_output, stunnel_option_name):
supported = False
for line in stunnel_output:
if line.startswith(stunnel_option_name):
supported = True
break
if not supported:
logging.warn('stunnel does not support "%s"', stunnel_option_name)
return supported
def get_version_specific_stunnel_options(config):
proc = subprocess.Popen(['stunnel', '-help'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
proc.wait()
_, err = proc.communicate()
stunnel_output = err.splitlines()
def write_stunnel_config_file(config, state_file_dir, fs_id, mountpoint, tls_port, dns_name, options, log_dir=LOG_DIR):
check_host_supported = is_stunnel_option_supported(stunnel_output, 'checkHost')
oscp_aia_supported = is_stunnel_option_supported(stunnel_output, 'OCSPaia')
return check_host_supported, oscp_aia_supported
def write_stunnel_config_file(config, state_file_dir, fs_id, mountpoint, tls_port, dns_name, verify_level, log_dir=LOG_DIR):
"""
Serializes stunnel configuration to a file. Unfortunately this does not conform to Python's config file format, so we have to
hand-serialize it.
......@@ -242,15 +254,25 @@ def write_stunnel_config_file(config, state_file_dir, fs_id, mountpoint, tls_por
efs_config = dict(STUNNEL_EFS_CONFIG)
efs_config['accept'] = efs_config['accept'] % tls_port
efs_config['connect'] = efs_config['connect'] % dns_name
efs_config['verify'] = options.get('verify', DEFAULT_STUNNEL_VERIFY_LEVEL)
if efs_config['verify'] > 0:
add_stunnel_ca_options(efs_config, options)
efs_config['verify'] = verify_level
if verify_level > 0:
add_stunnel_ca_options(efs_config)
check_host, oscp_aia = get_version_specific_stunnel_options(config)
tls_controls_message = 'WARNING: Your client lacks sufficient controls to properly enforce TLS. Please upgrade stunnel, ' \
'or disable "%%s" in %s.\nSee %s for more detail.' % (CONFIG_FILE,
'https://docs.aws.amazon.com/console/efs/troubleshooting-tls')
if config.getboolean(CONFIG_SECTION, 'stunnel_check_cert_hostname'):
if check_host:
efs_config['checkHost'] = dns_name
elif config.getboolean(CONFIG_SECTION, 'stunnel_check_cert_hostname'):
fatal_error(tls_controls_message % 'stunnel_check_cert_hostname')
if config.getboolean(CONFIG_SECTION, 'stunnel_check_cert_validity'):
if oscp_aia:
efs_config['OCSPaia'] = 'yes'
elif config.getboolean(CONFIG_SECTION, 'stunnel_check_cert_validity'):
fatal_error(tls_controls_message % 'stunnel_check_cert_validity')
stunnel_config = '\n'.join(serialize_stunnel_config(global_config) + serialize_stunnel_config(efs_config, 'efs'))
logging.debug('Writing stunnel configuration:\n%s', stunnel_config)
......@@ -331,8 +353,8 @@ def check_network_status(fs_id, init_system):
def start_watchdog(init_system):
if init_system == 'init':
p = subprocess.Popen(['/sbin/status', WATCHDOG_SERVICE], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
status, _ = p.communicate()
proc = subprocess.Popen(['/sbin/status', WATCHDOG_SERVICE], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
status, _ = proc.communicate()
if 'stop' in status:
with open(os.devnull, 'w') as devnull:
subprocess.Popen(['/sbin/start', WATCHDOG_SERVICE], stdout=devnull, stderr=devnull)
......@@ -362,8 +384,10 @@ def bootstrap_tls(config, init_system, dns_name, fs_id, mountpoint, options, sta
tls_port = choose_tls_port(config)
options['tlsport'] = tls_port
verify_level = int(options.get('verify', DEFAULT_STUNNEL_VERIFY_LEVEL))
options['verify'] = verify_level
stunnel_config_file = write_stunnel_config_file(config, state_file_dir, fs_id, mountpoint, tls_port, dns_name, options)
stunnel_config_file = write_stunnel_config_file(config, state_file_dir, fs_id, mountpoint, tls_port, dns_name, verify_level)
tunnel_args = ['stunnel', stunnel_config_file]
......@@ -416,18 +440,18 @@ def mount_nfs(dns_name, path, mountpoint, options):
else:
mount_path = '%s:%s' % (dns_name, path)
command = ['/sbin/mount.nfs4', '-o', get_nfs_mount_options(options), mount_path, mountpoint]
command = ['/sbin/mount.nfs4', mount_path, mountpoint, '-o', get_nfs_mount_options(options)]
logging.info('Executing: "%s"', ' '.join(command))
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = process.communicate()
proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
if process.returncode == 0:
if proc.returncode == 0:
logging.info('Successfully mounted %s at %s', dns_name, mountpoint)
else:
message = 'Failed to mount %s at %s: returncode=%d, stderr="%s"' % (dns_name, mountpoint, process.returncode, err.strip())
fatal_error(err.strip(), message, process.returncode)
message = 'Failed to mount %s at %s: returncode=%d, stderr="%s"' % (dns_name, mountpoint, proc.returncode, err.strip())
fatal_error(err.strip(), message, proc.returncode)
def parse_arguments(args=None):
......@@ -443,7 +467,7 @@ def parse_arguments(args=None):
usage(out=sys.stdout, exit_code=0)
if '--version' in args[1:]:
sys.stdout.write('%s Version: %.1f\n' % (args[0], VERSION))
sys.stdout.write('%s Version: %s\n' % (args[0], VERSION))
sys.exit(0)
fsname = None
......@@ -471,7 +495,7 @@ def parse_arguments(args=None):
def assert_root():
if 'root' != getpass.getuser():
if os.geteuid() != 0:
sys.stderr.write('only root can run mount.efs\n')
sys.exit(1)
......@@ -483,7 +507,22 @@ def read_config(config_file=CONFIG_FILE):
def bootstrap_logging(config, log_dir=LOG_DIR):
level = config.get(CONFIG_SECTION, 'logging_level')
raw_level = config.get(CONFIG_SECTION, 'logging_level')
levels = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL
}
level = levels.get(raw_level.lower())
level_error = False
if not level:
# delay logging error about malformed log level until after logging is configured
level_error = True
level = logging.INFO
max_bytes = config.getint(CONFIG_SECTION, 'logging_max_bytes')
file_count = config.getint(CONFIG_SECTION, 'logging_file_count')
......@@ -494,6 +533,9 @@ def bootstrap_logging(config, log_dir=LOG_DIR):
logger.setLevel(level)
logger.addHandler(handler)
if level_error:
logging.error('Malformed logging level "%s", setting logging level to %s', raw_level, level)
def get_dns_name(config, fs_id):
def _validate_replacement_field_count(format_str, expected_ct):
......@@ -519,7 +561,7 @@ def get_dns_name(config, fs_id):
socket.gethostbyname(dns_name)
except socket.gaierror:
fatal_error('Failed to resolve "%s" - check that your file system ID is correct.\nSee %s for more detail.'
% (dns_name, 'https://docs.aws.amazon.com/efs/latest/ug/mounting-fs-mount-cmd-dns-name.html'),
% (dns_name, 'https://docs.aws.amazon.com/console/efs/mount-dns-name'),
'Failed to resolve "%s"' % dns_name)
return dns_name
......@@ -542,7 +584,7 @@ def main():
config = read_config()
bootstrap_logging(config)
logging.info('version=%.1f options=%s', VERSION, options)
logging.info('version=%s options=%s', VERSION, options)
init_system = get_init_system()
check_network_status(fs_id, init_system)
......
......@@ -8,7 +8,6 @@
#
import errno
import getpass
import json
import logging
import logging.handlers
......@@ -26,7 +25,7 @@ try:
except ImportError:
from configparser import ConfigParser
VERSION = 1.0
VERSION = '1.2'
CONFIG_FILE = '/etc/amazon/efs/efs-utils.conf'
CONFIG_SECTION = 'mount-watchdog'
......@@ -49,7 +48,22 @@ def fatal_error(user_message, log_message=None):
def bootstrap_logging(config, log_dir=LOG_DIR):
level = config.get(CONFIG_SECTION, 'logging_level')
raw_level = config.get(CONFIG_SECTION, 'logging_level')
levels = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL
}
level = levels.get(raw_level.lower())
level_error = False
if not level:
# delay logging error about malformed log level until after logging is configured
level_error = True
level = logging.INFO
max_bytes = config.getint(CONFIG_SECTION, 'logging_max_bytes')
file_count = config.getint(CONFIG_SECTION, 'logging_file_count')
......@@ -60,6 +74,9 @@ def bootstrap_logging(config, log_dir=LOG_DIR):
logger.setLevel(level)
logger.addHandler(handler)
if level_error:
logging.error('Malformed logging level "%s", setting logging level to %s', raw_level, level)
def get_file_safe_mountpoint(mountpoint):
mountpoint = os.path.abspath(mountpoint).replace(os.sep, '.')
......@@ -231,12 +248,12 @@ def parse_arguments(args=None):
sys.exit(0)
if '--version' in args[1:]:
sys.stdout.write('%s Version: %.1f\n' % (args[0], VERSION))
sys.stdout.write('%s Version: %s\n' % (args[0], VERSION))
sys.exit(0)
def assert_root():
if 'root' != getpass.getuser():