diff --git a/.env.example b/.env.example index 590a183..fd2a94c 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,10 @@ +ENABLE_SSHD_BOOTSTRAP=true +ENABLE_SSHD_WRAPPER=true SSH_AUTHORIZED_KEYS= -SSH_AUTOSTART_SSHD=true -SSH_AUTOSTART_SSHD_BOOTSTRAP=true SSH_CHROOT_DIRECTORY=%h SSH_INHERIT_ENVIRONMENT=false SSH_PASSWORD_AUTHENTICATION=false SSH_SUDO=ALL=(ALL) ALL -SSH_TIMEZONE=UTC SSH_USER=app-admin SSH_USER_FORCE_SFTP=false SSH_USER_HOME=/home/%u @@ -13,4 +12,5 @@ SSH_USER_ID=500:500 SSH_USER_PASSWORD= SSH_USER_PASSWORD_HASHED=false SSH_USER_PRIVATE_KEY= -SSH_USER_SHELL=/bin/bash \ No newline at end of file +SSH_USER_SHELL=/bin/bash +SYSTEM_TIMEZONE=UTC diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc0a2a..85ab386 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,37 @@ Summary of release changes for Version 1 - CentOS-6 +### 1.11.0 - 2019-06-20 + +- Deprecates `SSH_AUTOSTART_SSHD`, replaced with `ENABLE_SSHD_WRAPPER`. +- Deprecates `SSH_AUTOSTART_SSHD_BOOTSTRAP`, replaced with `ENABLE_SSHD_BOOTSTRAP`. +- Deprecates `SSH_AUTOSTART_SUPERVISOR_STDOUT`, replaced with `ENABLE_SUPERVISOR_STDOUT`. +- Deprecates `SSH_TIMEZONE`, replaced with `SYSTEM_TIMEZONE`. +- Updates source image from `centos6.10` tag to `6.10`. +- Updates supervisord to 3.4.0. +- Updates default value of `ENABLE_SUPERVISOR_STDOUT` to false. +- Updates `sshd-bootstrap` and `sshd-wrapper` configuration to send error log output to stderr. +- Updates order of values in SSH/SFTP Details log output. +- Updates bootstrap timer to use UTC date timestamps. +- Updates bootstrap supervisord configuration file/priority to `20-sshd-bootstrap.conf`/`20`. +- Updates wrapper supervisord configuration file/priority to `50-sshd-wrapper.conf`/`50`. +- Adds reference to `python-setuptools` in README; removed in error. +- Adds `inspect`, `reload` and `top` Makefile targets. +- Adds improved lock/state file implementation in bootstrap and wrapper scripts. +- Adds improved `clean` Makefile target; includes exited containers and dangling images. +- Adds improved wait on bootstrap completion in wrapper script. +- Adds `system-timezone` and `system-timezone-wrapper` to handle system time zone setup. +- Adds system time zone validation to healthcheck. +- Fixes port incrementation failures when installing systemd units via `scmi`. +- Fixes etcd port registration failures when installing systemd units via `scmi` with the `--register` option. +- Fixes binary paths in systemd unit files for compatibility with both EL and Ubuntu hosts. +- Fixes use of printf binary instead of builtin in systemd unit files. +- Fixes docker host connection status check in Makefile. +- Fixes make clean error thrown when removing exited containers. +- Removes support for long image tags (i.e. centos-6-1.x.x). +- Removes system time zone setup from `sshd-bootstrap`. +- Removes redundant directory test from `sshd-bootstrap`; state file ensures it's a one-shot process. + ### 1.10.1 - 2019-02-28 - Deprecates use of `supervisor_stdout` - the default value of `SSH_AUTOSTART_SUPERVISOR_STDOUT` will be switched to "false" in a future release. diff --git a/Dockerfile b/Dockerfile index 6930ad3..65171de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM centos:centos6.10 +FROM centos:6.10 -ARG RELEASE_VERSION="1.10.1" +ARG RELEASE_VERSION="1.11.0" # ------------------------------------------------------------------------------ # - Import the RPM GPG keys for repositories @@ -23,6 +23,10 @@ RUN rpm --rebuilddb \ centos-release-scl-rh \ epel-release \ https://centos6.iuscommunity.org/ius-release.rpm \ + && yum -y install \ + --setopt=tsflags=nodocs \ + --disableplugin=fastestmirror \ + inotify-tools-3.14-1.el6 \ openssh-clients-5.3p1-123.el6_9 \ openssh-server-5.3p1-123.el6_9 \ python-setuptools-0.6.10-4.el6_9 \ @@ -30,6 +34,7 @@ RUN rpm --rebuilddb \ yum-plugin-versionlock-1.1.30-42.el6_10 \ xz-4.999.9-0.5.beta.20091007git.el6 \ && yum versionlock add \ + inotify-tools \ openssh \ openssh-clients \ openssh-server \ @@ -45,7 +50,7 @@ RUN rpm --rebuilddb \ sysvinit-tools \ && yum clean all \ && easy_install \ - 'supervisor == 3.3.5' \ + 'supervisor == 3.4.0' \ 'supervisor-stdout == 0.1.1' \ && mkdir -p \ /var/log/supervisor/ \ @@ -93,9 +98,9 @@ RUN ln -sf \ -e "s~{{RELEASE_VERSION}}~${RELEASE_VERSION}~g" \ /etc/systemd/system/centos-ssh@.service \ && chmod 644 \ - /etc/{supervisord.conf,supervisord.d/sshd-{bootstrap,wrapper}.conf} \ + /etc/{supervisord.conf,supervisord.d/{20-sshd-bootstrap,50-sshd-wrapper}.conf} \ && chmod 700 \ - /usr/{bin/healthcheck,sbin/{scmi,sshd-{bootstrap,wrapper}}} + /usr/{bin/healthcheck,sbin/{scmi,sshd-{bootstrap,wrapper},system-{timezone,timezone-wrapper}}} EXPOSE 22 @@ -103,15 +108,14 @@ EXPOSE 22 # Set default environment variables # ------------------------------------------------------------------------------ ENV \ + ENABLE_SSHD_BOOTSTRAP="true" \ + ENABLE_SSHD_WRAPPER="true" \ + ENABLE_SUPERVISOR_STDOUT="false" \ SSH_AUTHORIZED_KEYS="" \ - SSH_AUTOSTART_SSHD="true" \ - SSH_AUTOSTART_SSHD_BOOTSTRAP="true" \ - SSH_AUTOSTART_SUPERVISOR_STDOUT="true" \ SSH_CHROOT_DIRECTORY="%h" \ SSH_INHERIT_ENVIRONMENT="false" \ SSH_PASSWORD_AUTHENTICATION="false" \ SSH_SUDO="ALL=(ALL) ALL" \ - SSH_TIMEZONE="UTC" \ SSH_USER="app-admin" \ SSH_USER_FORCE_SFTP="false" \ SSH_USER_HOME="/home/%u" \ @@ -119,7 +123,8 @@ ENV \ SSH_USER_PASSWORD="" \ SSH_USER_PASSWORD_HASHED="false" \ SSH_USER_PRIVATE_KEY="" \ - SSH_USER_SHELL="/bin/bash" + SSH_USER_SHELL="/bin/bash" \ + SYSTEM_TIMEZONE="UTC" # ------------------------------------------------------------------------------ # Set image metadata diff --git a/Makefile b/Makefile index 5ebb343..6416f1b 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ Targets: distclean Clean up distribution artifacts. exec COMMAND [ARG...] Run command in a the running container. help Show this help. + inspect [-f \"FORMAT\"] Return low-level information on the container. install Terminate running container and run the docker create template. images Show container's image details. @@ -32,14 +33,19 @@ Targets: pull Pull the release image from the registry. Requires the DOCKER_IMAGE_TAG variable. ps Display the details of the container process. + reload Send SIGHUP to the PID 1 container process. restart Restarts the container. rm Force remove the container. + rm-exited Force remove all containers in the exited state. rmi Untag (remove) the image. + rmi-dangling Untag (remove) images not referenced by any + container. run Execute the run container template. start Start the container in the created state. stop Stop the container when in a running state. terminate Unpause, stop and remove the container. test Run all test cases. + top [ps OPTIONS] Display the running processes of the container. unpause Unpause the container when in a paused state. Variables: @@ -60,6 +66,8 @@ Variables: artifacts are placed. - NO_CACHE When true, no cache will be used while running the build target. + - RELOAD_SIGNAL Default signal is SIGHUP. Use to set an alternative + signal value. - STARTUP_TIME Defines the number of seconds expected to complete the startup process, including the bootstrap where applicable. @@ -69,7 +77,7 @@ endef include environment.mk include default.mk -# UI constants +.DEFAULT_GOAL := build COLOUR_NEGATIVE := \033[1;31m COLOUR_POSITIVE := \033[1;32m COLOUR_RESET := \033[0m @@ -106,25 +114,22 @@ PREFIX_SUB_STEP_POSITIVE := $(shell \ "$(PREFIX_SUB_STEP)" \ "$(COLOUR_RESET)"; \ ) - -.DEFAULT_GOAL := build - -# Package prerequisites docker := $(shell \ command -v docker \ ) -xz := $(shell \ - command -v xz \ +docker-status := $(shell \ + if ! docker version > /dev/null; \ + then \ + printf -- 'ERROR'; \ + else \ + printf -- 'OK'; \ + fi \ ) - -# Testing prerequisites shpec := $(shell \ command -v shpec \ ) - -# Used to test docker host is accessible -get-docker-info := $(shell \ - $(docker) info \ +xz := $(shell \ + command -v xz \ ) define get-docker-image-id @@ -166,6 +171,7 @@ endef distclean \ exec \ help \ + inspect \ install \ images \ load \ @@ -174,14 +180,18 @@ endef pause \ pull \ ps \ + reload \ restart \ rm \ + rm-exited \ rmi \ + rmi-dangling \ run \ start \ stop \ terminate \ test \ + top \ unpause _prerequisites: @@ -193,38 +203,34 @@ ifeq ($(xz),) $(error "Please install the xz package.") endif -ifeq ($(get-docker-info),) - $(error "Unable to connect to docker host.") +ifneq ($(docker-status),OK) + $(error "Docker server host error.") endif _require-docker-container: @ if [[ -z $$($(docker) ps -aq --filter "name=$(DOCKER_NAME)") ]]; \ then \ - printf -- '%sThis operation requires the %s container.\n' \ + >&2 printf -- '%sThis operation requires the %s container.\n' \ "$(PREFIX_STEP_NEGATIVE)" \ - "$(DOCKER_NAME)" \ - >&2; \ - printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ + "$(DOCKER_NAME)"; \ + >&2 printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ "$(PREFIX_SUB_STEP)" \ "$(DOCKER_NAME)" \ - "install" \ - >&2; \ + "install"; \ exit 1; \ fi _require-docker-container-not: @ if [[ -n $$($(docker) ps -aq --filter "name=$(DOCKER_NAME)") ]]; \ then \ - printf -- '%sThis operation requires the %s container %s.\n' \ + >&2 printf -- '%sThis operation requires the %s container %s.\n' \ "$(PREFIX_STEP_NEGATIVE)" \ "$(DOCKER_NAME)" \ - "be removed or renamed" \ - >&2; \ - printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ + "be removed or renamed"; \ + >&2 printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ "$(PREFIX_SUB_STEP)" \ "$(DOCKER_NAME)" \ - "rm" \ - >&2; \ + "rm"; \ exit 1; \ fi @@ -234,16 +240,14 @@ _require-docker-container-not-status-paused: --filter "status=paused" \ ) ]]; \ then \ - printf -- '%sThis operation requires the %s container %s.\n' \ + >&2 printf -- '%sThis operation requires the %s container %s.\n' \ "$(PREFIX_STEP_NEGATIVE)" \ "$(DOCKER_NAME)" \ - "to be unpaused" \ - >&2; \ - printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ + "to be unpaused"; \ + >&2 printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ "$(PREFIX_SUB_STEP)" \ "$(DOCKER_NAME)" \ - "unpause" \ - >&2; \ + "unpause"; \ exit 1; \ fi @@ -253,16 +257,14 @@ _require-docker-container-status-created: --filter "status=created" \ ) ]]; \ then \ - printf -- '%sThis operation requires the %s container %s.\n' \ + >&2 printf -- '%sThis operation requires the %s container %s.\n' \ "$(PREFIX_STEP_NEGATIVE)" \ "$(DOCKER_NAME)" \ - "to be created" \ - >&2; \ - printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ + "to be created"; \ + >&2 printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ "$(PREFIX_SUB_STEP)" \ "$(DOCKER_NAME)" \ - "install" \ - >&2; \ + "install"; \ exit 1; \ fi @@ -272,16 +274,14 @@ _require-docker-container-status-exited: --filter "status=exited" \ ) ]]; \ then \ - printf -- '%sThis operation requires the %s container %s.\n' \ + >&2 printf -- '%sThis operation requires the %s container %s.\n' \ "$(PREFIX_STEP_NEGATIVE)" \ "$(DOCKER_NAME)" \ - "to be exited" \ - >&2; \ - printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ + "to be exited"; \ + >&2 printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ "$(PREFIX_SUB_STEP)" \ "$(DOCKER_NAME)" \ - "stop" \ - >&2; \ + "stop"; \ exit 1; \ fi @@ -291,16 +291,14 @@ _require-docker-container-status-paused: --filter "status=paused" \ ) ]]; \ then \ - printf -- '%sThis operation requires the %s container %s.\n' \ + >&2 printf -- '%sThis operation requires the %s container %s.\n' \ "$(PREFIX_STEP_NEGATIVE)" \ "$(DOCKER_NAME)" \ - "to be paused" \ - >&2; \ - printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ + "to be paused"; \ + >&2 printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ "$(PREFIX_SUB_STEP)" \ "$(DOCKER_NAME)" \ - "pause" \ - >&2; \ + "pause"; \ exit 1; \ fi @@ -310,42 +308,37 @@ _require-docker-container-status-running: --filter "status=running" \ ) ]]; \ then \ - printf -- '%sThis operation requires the %s container %s.\n' \ + >&2 printf -- '%sThis operation requires the %s container %s.\n' \ "$(PREFIX_STEP_NEGATIVE)" \ "$(DOCKER_NAME)" \ - "to be running" \ - >&2; \ - printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ + "to be running"; \ + >&2 printf -- '%sTry: DOCKER_NAME=%s make %s\n' \ "$(PREFIX_SUB_STEP)" \ "$(DOCKER_NAME)" \ - "start" \ - >&2; \ + "start"; \ exit 1; \ fi _require-docker-image-tag: @ if ! [[ "$(DOCKER_IMAGE_TAG)" =~ $(DOCKER_IMAGE_TAG_PATTERN) ]]; \ then \ - printf -- '%sInvalid %s value: %s\n' \ + >&2 printf -- '%sInvalid %s value: %s\n' \ "$(PREFIX_STEP_NEGATIVE)" \ "DOCKER_IMAGE_TAG" \ - "$(DOCKER_IMAGE_TAG)" \ - >&2; \ + "$(DOCKER_IMAGE_TAG)"; \ exit 1; \ fi _require-docker-release-tag: @ if ! [[ "$(DOCKER_IMAGE_TAG)" =~ $(DOCKER_IMAGE_RELEASE_TAG_PATTERN) ]]; \ then \ - printf -- '%sInvalid %s value: %s\n' \ + >&2 printf -- '%sInvalid %s value: %s\n' \ "$(PREFIX_STEP_NEGATIVE)" \ "DOCKER_IMAGE_TAG" \ - "$(DOCKER_IMAGE_TAG)" \ - >&2; \ - printf -- '%s%s\n' \ + "$(DOCKER_IMAGE_TAG)"; \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP)" \ - "A release tag is required for this operation." \ - >&2; \ + "A release tag is required for this operation."; \ exit 1; \ fi @@ -359,18 +352,16 @@ _require-package-path: fi; \ if [[ ! $${?} -eq 0 ]]; \ then \ - printf -- '%s%s: %s\n' \ + >&2 printf -- '%s%s: %s\n' \ "$(PREFIX_STEP_NEGATIVE)" \ "Failed to make package path" \ - "$(DIST_PATH)" \ - >&2; \ + "$(DIST_PATH)"; \ exit 1; \ elif [[ -z $(DIST_PATH) ]]; \ then \ - printf -- '%sUndefined %s\n' \ + >&2 printf -- '%sUndefined %s\n' \ "$(PREFIX_STEP_NEGATIVE)" \ - "DIST_PATH" \ - >&2; \ + "DIST_PATH"; \ exit 1; \ fi @@ -415,10 +406,9 @@ build: \ "$(PREFIX_SUB_STEP_POSITIVE)" \ "Build complete"; \ else \ - printf -- '%s%s\n' \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ - "Build error" \ - >&2; \ + "Build error"; \ exit 1; \ fi @@ -426,7 +416,9 @@ clean: \ _prerequisites \ | \ terminate \ - rmi + rm-exited \ + rmi \ + rmi-dangling create: \ _prerequisites \ @@ -456,10 +448,9 @@ create: \ "$(PREFIX_SUB_STEP_POSITIVE)" \ "Container created"; \ else \ - printf -- '%s%s\n' \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ - "Container creation failed" \ - >&2; \ + "Container creation failed"; \ exit 1; \ fi @@ -506,10 +497,9 @@ dist: \ "$(PREFIX_SUB_STEP_POSITIVE)" \ "Package saved"; \ else \ - printf -- '%s%s\n' \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ - "Package save error" \ - >&2; \ + "Package save error"; \ exit 1; \ fi; \ fi @@ -547,10 +537,9 @@ distclean: \ "$(PREFIX_SUB_STEP_POSITIVE)" \ "Package cleanup complete"; \ else \ - printf -- '%s%s\n' \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ - "Package cleanup failed" \ - >&2; \ + "Package cleanup failed"; \ exit 1; \ fi; \ else \ @@ -560,7 +549,9 @@ distclean: \ fi exec: \ - _prerequisites + _prerequisites \ + _require-docker-container \ + _require-docker-container-status-running @ $(docker) exec -it $(DOCKER_NAME) $(filter-out $@, $(MAKECMDGOALS)) %:; @: @@ -572,17 +563,29 @@ images: \ help: \ _usage +inspect: \ + _prerequisites \ + _require-docker-container \ + _require-docker-container-status-running + @ $(docker) inspect \ + --type=container \ + $(filter-out $@, $(MAKECMDGOALS)) \ + $(DOCKER_NAME) +%:; @: + install: | \ _prerequisites \ terminate \ create logs: \ - _prerequisites + _prerequisites \ + _require-docker-container @ $(docker) logs $(DOCKER_NAME) logs-delayed: \ - _prerequisites + _prerequisites \ + _require-docker-container @ sleep $(STARTUP_TIME) @ $(MAKE) logs @@ -608,15 +611,13 @@ load: \ "$(DOCKER_IMAGE_TAG)"; \ if [[ ! -s $($@_dist_path)/$($@_dist_file) ]]; \ then \ - printf -- '%s%s\n' \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ - "Package not found" \ - >&2; \ - printf -- '%sTry: DOCKER_IMAGE_TAG=%s make %s\n' \ + "Package not found"; \ + >&2 printf -- '%sTry: DOCKER_IMAGE_TAG=%s make %s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ "$(DOCKER_IMAGE_TAG)" \ - "dist" \ - >&2; \ + "dist"; \ exit 1; \ else \ $(xz) -dc \ @@ -632,6 +633,7 @@ load: \ pause: \ _prerequisites \ + _require-docker-container \ _require-docker-container-status-running @ printf -- '%s%s\n' \ "$(PREFIX_STEP)" \ @@ -660,10 +662,9 @@ pull: \ "$(PREFIX_SUB_STEP_POSITIVE)" \ "Image pulled"; \ else \ - printf -- '%s%s\n' \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ - "Error pulling image" \ - >&2; \ + "Error pulling image"; \ exit 1; \ fi @@ -673,6 +674,19 @@ ps: \ @ $(docker) ps -as \ --filter "name=$(DOCKER_NAME)" +reload: \ + _prerequisites \ + _require-docker-container \ + _require-docker-container-status-running + @ printf -- '%s%s\n' \ + "$(PREFIX_STEP)" \ + "Reloading container" + @ $(docker) exec $(DOCKER_NAME) \ + kill -$(RELOAD_SIGNAL) 1 + @ printf -- '%s%s\n' \ + "$(PREFIX_SUB_STEP_POSITIVE)" \ + "Container reloaded" + restart: \ _prerequisites \ _require-docker-container \ @@ -710,14 +724,33 @@ rm: \ "$(PREFIX_SUB_STEP_POSITIVE)" \ "Container removed"; \ else \ - printf -- '%s%s\n' \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ - "Container removal failed" \ - >&2; \ + "Container removal failed"; \ exit 1; \ fi; \ fi +rm-exited: \ + _prerequisites + @ if [[ -z $$($(docker) ps -aq \ + --filter "status=exited" \ + ) ]]; \ + then \ + printf -- '%s%s\n' \ + "$(PREFIX_STEP)" \ + "Exited containers removal skipped"; \ + else \ + printf -- '%s%s\n' \ + "$(PREFIX_STEP)" \ + "Removing exited containers"; \ + $(docker) rm -f \ + $$($(docker) ps -aq \ + --filter "status=exited" \ + ) \ + 1> /dev/null; \ + fi + rmi: \ _prerequisites \ _require-docker-image-tag \ @@ -742,10 +775,9 @@ rmi: \ "$(PREFIX_SUB_STEP_POSITIVE)" \ "Image untagged"; \ else \ - printf -- '%s%s\n' \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ - "Error untagging image" \ - >&2; \ + "Error untagging image"; \ exit 1; \ fi; \ else \ @@ -754,6 +786,26 @@ rmi: \ "Untagging image skipped"; \ fi +rmi-dangling: \ + _prerequisites + @ if [[ -z $$($(docker) images -q \ + --filter "dangling=true" \ + ) ]]; \ + then \ + printf -- '%s%s\n' \ + "$(PREFIX_STEP)" \ + "Untagging dangling images skipped"; \ + else \ + printf -- '%s%s\n' \ + "$(PREFIX_STEP)" \ + "Untagging dangling images"; \ + $(docker) rmi \ + $$($(docker) images -q \ + --filter "dangling=true" \ + ) \ + 1> /dev/null; \ + fi + run: \ _prerequisites \ _require-docker-image-tag @@ -783,10 +835,9 @@ run: \ "$(PREFIX_SUB_STEP_POSITIVE)" \ "Container running"; \ else \ - printf -- '%s%s\n' \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ - "Container run failed" \ - >&2; \ + "Container run failed"; \ exit 1; \ fi @@ -816,15 +867,15 @@ start: \ "$(PREFIX_SUB_STEP_POSITIVE)" \ "Container started"; \ else \ - printf -- '%s%s\n' \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ - "Container start failed" \ - >&2; \ + "Container start failed"; \ exit 1; \ fi stop: \ _prerequisites \ + _require-docker-container \ _require-docker-container-not-status-paused \ _require-docker-container-status-running @ printf -- '%s%s\n' \ @@ -847,14 +898,20 @@ stop: \ "$(PREFIX_SUB_STEP_POSITIVE)" \ "Container stopped"; \ else \ - printf -- '%s%s\n' \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ - "Error stopping container" \ - >&2; \ + "Error stopping container"; \ exit 1; \ fi; \ fi +top: \ + _prerequisites \ + _require-docker-container \ + _require-docker-container-status-running + @ $(docker) top $(DOCKER_NAME) $(filter-out $@, $(MAKECMDGOALS)) +%:; @: + terminate: \ _prerequisites @ if [[ -z $$($(docker) ps -aq --filter "name=$(DOCKER_NAME)") ]]; \ @@ -905,10 +962,9 @@ terminate: \ "$(PREFIX_SUB_STEP_POSITIVE)" \ "Container terminated"; \ else \ - printf -- '%s%s\n' \ + >&2 printf -- '%s%s\n' \ "$(PREFIX_SUB_STEP_NEGATIVE)" \ - "Container termination failed" \ - >&2; \ + "Container termination failed"; \ exit 1; \ fi; \ fi @@ -926,6 +982,7 @@ test: \ unpause: \ _prerequisites \ + _require-docker-container \ _require-docker-container-status-paused @ printf -- '%s%s\n' \ "$(PREFIX_STEP)" \ diff --git a/README-short.txt b/README-short.txt index 904db0a..cbd20ed 100644 --- a/README-short.txt +++ b/README-short.txt @@ -1 +1 @@ -CentOS-6 6.10 x86_64 / CentOS-7 7.5.1804 x86_64 - SCL/EPEL/IUS Repos / Supervisor / OpenSSH. \ No newline at end of file +CentOS-6 6.10 x86_64 / CentOS-7 7.6.1810 x86_64 - SCL/EPEL/IUS Repos / Supervisor / OpenSSH. \ No newline at end of file diff --git a/README.md b/README.md index df9533c..9265301 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,22 @@ centos-ssh ========== -Docker Images of CentOS-6 6.10 x86_64 / CentOS-7 7.5.1804 x86_64 +Docker Images of CentOS-6 6.10 x86_64 / CentOS-7 7.6.1810 x86_64 Includes public key authentication, Automated password generation and supports custom configuration via environment variables. ## Overview & links -The latest CentOS-6 / CentOS-7 based releases can be pulled from the `centos-6` / `centos-7` Docker tags respectively. For production use it is recommended to select a specific release tag - the convention is `centos-6-1.10.1` OR `1.10.1` for the [1.10.1](https://github.com/jdeathe/centos-ssh/tree/1.10.1) release tag and `centos-7-2.5.1` OR `2.5.1` for the [2.5.1](https://github.com/jdeathe/centos-ssh/tree/2.5.1) release tag. +The latest CentOS-6 / CentOS-7 based releases can be pulled from the `centos-6` / `centos-7` Docker tags respectively. For production use it is recommended to select a specific release tag as shown in the examples. ### Tags and respective `Dockerfile` links -- `centos-7`,`centos-7-2.5.1`,`2.5.1` [(centos-7/Dockerfile)](https://github.com/jdeathe/centos-ssh/blob/centos-7/Dockerfile) -- `centos-6`,`centos-6-1.10.1`,`1.10.1` [(centos-6/Dockerfile)](https://github.com/jdeathe/centos-ssh/blob/centos-6/Dockerfile) +- `centos-7`,[`2.6.0`](https://github.com/jdeathe/centos-ssh/releases/tag/2.6.0) [(centos-7/Dockerfile)](https://github.com/jdeathe/centos-ssh/blob/centos-7/Dockerfile) +- `centos-6`,[`1.11.0`](https://github.com/jdeathe/centos-ssh/releases/tag/1.11.0) [(centos-6/Dockerfile)](https://github.com/jdeathe/centos-ssh/blob/centos-6/Dockerfile) The Dockerfile can be used to build a base image that is the bases for several other docker images. -Included in the build are the [SCL](https://www.softwarecollections.org/), [EPEL](http://fedoraproject.org/wiki/EPEL) and [IUS](https://ius.io) repositories. Installed packages include [OpenSSH](http://www.openssh.com/portable.html) secure shell, [Sudo](http://www.courtesan.com/sudo/) and [vim-minimal](http://www.vim.org/) are along with [supervisor](http://supervisord.org/) and [supervisor-stdout](https://github.com/coderanger/supervisor-stdout). +Included in the build are the [SCL](https://www.softwarecollections.org/), [EPEL](http://fedoraproject.org/wiki/EPEL) and [IUS](https://ius.io) repositories. Installed packages include [inotify-tools](https://github.com/rvoicilas/inotify-tools/wiki), [OpenSSH](http://www.openssh.com/portable.html) secure shell, [Sudo](http://www.courtesan.com/sudo/), [vim-minimal](http://www.vim.org/), python-setuptools, [supervisor](http://supervisord.org/) and [supervisor-stdout](https://github.com/coderanger/supervisor-stdout). [Supervisor](http://supervisord.org/) is used to start and the sshd daemon when a docker container based on this image is run. @@ -42,7 +42,7 @@ Run up an SSH container named 'ssh.1' from the docker image 'jdeathe/centos-ssh' $ docker run -d \ --name ssh.1 \ -p 2020:22 \ - jdeathe/centos-ssh:1.10.1 + jdeathe/centos-ssh:1.11.0 ``` Check the logs for the password (required for sudo). @@ -76,7 +76,7 @@ $ docker run -d \ --name sftp.1 \ -p 2021:22 \ -e SSH_USER_FORCE_SFTP=true \ - jdeathe/centos-ssh:1.10.1 + jdeathe/centos-ssh:1.11.0 ``` Connect using the `sftp` command line client with the [insecure private key](https://github.com/mitchellh/vagrant/blob/master/keys/vagrant). @@ -106,10 +106,10 @@ $ docker run \ --rm \ --privileged \ --volume /:/media/root \ - jdeathe/centos-ssh:1.10.1 \ + jdeathe/centos-ssh:1.11.0 \ /usr/sbin/scmi install \ --chroot=/media/root \ - --tag=1.10.1 \ + --tag=1.11.0 \ --name=ssh.1 \ --setopt="--volume {{NAME}}.config-ssh:/etc/ssh" ``` @@ -123,10 +123,10 @@ $ docker run \ --rm \ --privileged \ --volume /:/media/root \ - jdeathe/centos-ssh:1.10.1 \ + jdeathe/centos-ssh:1.11.0 \ /usr/sbin/scmi uninstall \ --chroot=/media/root \ - --tag=1.10.1 \ + --tag=1.11.0 \ --name=ssh.1 \ --setopt="--volume {{NAME}}.config-ssh:/etc/ssh" ``` @@ -140,10 +140,10 @@ $ docker run \ --rm \ --privileged \ --volume /:/media/root \ - jdeathe/centos-ssh:1.10.1 \ + jdeathe/centos-ssh:1.11.0 \ /usr/sbin/scmi install \ --chroot=/media/root \ - --tag=1.10.1 \ + --tag=1.11.0 \ --name=ssh.1 \ --manager=systemd \ --register \ @@ -159,7 +159,7 @@ Since release tags `1.7.2` / `2.1.2` the install template has been added to the _NOTE:_ A prerequisite of the following examples is that the image has been pulled (or loaded from the release package). ``` -$ docker pull jdeathe/centos-ssh:1.10.1 +$ docker pull jdeathe/centos-ssh:1.11.0 ``` To see detailed information about the image run `scmi` with the `--info` option. To see all available `scmi` options run with the `--help` option. @@ -168,7 +168,7 @@ To see detailed information about the image run `scmi` with the `--info` option. $ eval "sudo -E $( docker inspect \ -f "{{.ContainerConfig.Labels.install}}" \ - jdeathe/centos-ssh:1.10.1 + jdeathe/centos-ssh:1.11.0 ) --info" ``` @@ -178,7 +178,7 @@ To perform an installation using the docker name `ssh.2` simply use the `--name` $ eval "sudo -E $( docker inspect \ -f "{{.ContainerConfig.Labels.install}}" \ - jdeathe/centos-ssh:1.10.1 + jdeathe/centos-ssh:1.11.0 ) --name=ssh.2" ``` @@ -188,7 +188,7 @@ To uninstall use the *same command* that was used to install but with the `unins $ eval "sudo -E $( docker inspect \ -f "{{.ContainerConfig.Labels.uninstall}}" \ - jdeathe/centos-ssh:1.10.1 + jdeathe/centos-ssh:1.11.0 ) --name=ssh.2" ``` @@ -201,7 +201,7 @@ To see detailed information about the image run `scmi` with the `--info` option. ``` $ sudo -E atomic install \ -n ssh.3 \ - jdeathe/centos-ssh:1.10.1 \ + jdeathe/centos-ssh:1.11.0 \ --info ``` @@ -210,14 +210,14 @@ To perform an installation using the docker name `ssh.3` simply use the `-n` opt ``` $ sudo -E atomic install \ -n ssh.3 \ - jdeathe/centos-ssh:1.10.1 + jdeathe/centos-ssh:1.11.0 ``` Alternatively, you could use the `scmi` options `--name` or `-n` for naming the container. ``` $ sudo -E atomic install \ - jdeathe/centos-ssh:1.10.1 \ + jdeathe/centos-ssh:1.11.0 \ --name ssh.3 ``` @@ -226,7 +226,7 @@ To uninstall use the *same command* that was used to install but with the `unins ``` $ sudo -E atomic uninstall \ -n ssh.3 \ - jdeathe/centos-ssh:1.10.1 + jdeathe/centos-ssh:1.11.0 ``` #### Using environment variables @@ -242,7 +242,7 @@ $ docker stop ssh.1 \ --name ssh.1 \ -p :22 \ --env "SSH_USER=app-user" \ - jdeathe/centos-ssh:1.10.1 + jdeathe/centos-ssh:1.11.0 ``` Now you can find out the app-admin, (sudoer), user's password by inspecting the container's logs @@ -254,44 +254,75 @@ $ docker logs ssh.1 The output of the logs should show the auto-generated password for the app-admin and root users, (if not try again after a few seconds). ``` -2019-01-17 18:56:09,093 WARN No file matches via include "/etc/supervisord.d/*.ini" -2019-01-17 18:56:09,093 INFO Included extra file "/etc/supervisord.d/sshd-bootstrap.conf" during parsing -2019-01-17 18:56:09,093 INFO Included extra file "/etc/supervisord.d/sshd-wrapper.conf" during parsing -2019-01-17 18:56:09,093 INFO Set uid to user 0 succeeded -2019-01-17 18:56:09,098 INFO supervisord started with pid 1 -2019-01-17 18:56:10,064 INFO spawned: 'supervisor_stdout' with pid 16 -2019-01-17 18:56:10,066 INFO spawned: 'sshd-bootstrap' with pid 17 -2019-01-17 18:56:10,067 INFO spawned: 'sshd-wrapper' with pid 18 -2019-01-17 18:56:10,089 INFO success: supervisor_stdout entered RUNNING state, process has stayed up for > than 0 seconds (startsecs) -2019-01-17 18:56:10,089 INFO success: sshd-bootstrap entered RUNNING state, process has stayed up for > than 0 seconds (startsecs) -2019-01-17 18:56:10,089 INFO success: sshd-wrapper entered RUNNING state, process has stayed up for > than 0 seconds (startsecs) +2019-06-20 00:10:35,306 WARN No file matches via include "/etc/supervisord.d/*.ini" +2019-06-20 00:10:35,306 INFO Included extra file "/etc/supervisord.d/00-supervisor_stdout.conf" during parsing +2019-06-20 00:10:35,307 INFO Included extra file "/etc/supervisord.d/10-system-timezone-wrapper.conf" during parsing +2019-06-20 00:10:35,307 INFO Included extra file "/etc/supervisord.d/20-sshd-bootstrap.conf" during parsing +2019-06-20 00:10:35,307 INFO Included extra file "/etc/supervisord.d/50-sshd-wrapper.conf" during parsing +2019-06-20 00:10:35,307 INFO Set uid to user 0 succeeded +2019-06-20 00:10:35,310 INFO supervisord started with pid 1 +2019-06-20 00:10:36,315 INFO spawned: 'system-timezone-wrapper' with pid 9 +2019-06-20 00:10:36,318 INFO spawned: 'sshd-bootstrap' with pid 10 +2019-06-20 00:10:36,320 INFO spawned: 'sshd-wrapper' with pid 11 +INFO: sshd-wrapper waiting on sshd-bootstrap +2019-06-20 00:10:36,328 INFO success: system-timezone-wrapper entered RUNNING state, process has stayed up for > than 0 seconds (startsecs) +2019-06-20 00:10:36,328 INFO success: sshd-bootstrap entered RUNNING state, process has stayed up for > than 0 seconds (startsecs) + +================================================================================ +System Time Zone Details +-------------------------------------------------------------------------------- +timezone : UTC +-------------------------------------------------------------------------------- +0.00640178 + +2019-06-20 00:10:36,346 INFO exited: system-timezone-wrapper (exit status 0; expected) ================================================================================ SSH Details -------------------------------------------------------------------------------- -user : centos -password : YNT8fPEbpqrMJdpx -password authentication : no -id : 500:500 -home : /home/centos chroot path : N/A -shell : /bin/bash -sudo : ALL=(ALL) ALL +home : /home/app-admin +id : 500:500 key fingerprints : dd:3b:b8:2e:85:04:06:e9:ab:ff:a8:0a:c0:04:6e:d6 (insecure key) +password : uIEqLkiacCvxaN45 +password authentication : no +rsa private key fingerprint : +N/A rsa host key fingerprint : -d0:8e:c7:b4:9b:ce:10:a6:a0:38:78:74:c5:68:cc:a8 -timezone : UTC +7d:6f:d2:e8:7e:84:dd:ff:98:05:5e:6f:35:66:51:53 +shell : /bin/bash +sudo : ALL=(ALL) ALL +user : app-admin -------------------------------------------------------------------------------- -0.485003 +0.516901 -2019-01-17 18:56:10,568 INFO exited: sshd-bootstrap (exit status 0; expected) +INFO: sshd-wrapper starting sshd +2019-06-20 00:10:36,852 INFO exited: sshd-bootstrap (exit status 0; expected) +Server listening on 0.0.0.0 port 22. +Server listening on :: port 22. +2019-06-20 00:10:41,872 INFO success: sshd-wrapper entered RUNNING state, process has stayed up for > than 5 seconds (startsecs) ``` #### Environment Variables There are several environmental variables defined at runtime these allow the operator to customise the running container. +##### ENABLE_SSHD_BOOTSTRAP & ENABLE_SSHD_WRAPPER + +It may be desirable to prevent the startup of the sshd-bootstrap script and/or sshd daemon. For example, when using an image built from this Dockerfile as the source for another Dockerfile you could disable both sshd-booststrap and sshd from startup by setting `ENABLE_SSHD_BOOTSTRAP` and `ENABLE_SSHD_WRAPPER` to `false`. The benefit of this is to reduce the number of running processes in the final container. + +``` +... + --env "ENABLE_SSHD_BOOTSTRAP=false" \ + --env "ENABLE_SSHD_WRAPPER=false" \ +... +``` + +##### ENABLE_SUPERVISOR_STDOUT + +This image has `supervisor_stdout` installed which can be used to allow a process controlled by supervisord to send output to both a log file and stdout. It is recommended to simply output to stdout in order to reduce the number of running processes to a minimum. Setting `ENABLE_SUPERVISOR_STDOUT` to "false" will prevent the startup of `supervisor_stdout`. Where an image requires this feature for its logging output `ENABLE_SUPERVISOR_STDOUT` should be set to "true". + ##### SSH_AUTHORIZED_KEYS As detailed below the public key added for the SSH user is insecure by default. This is intentional and allows for access using a known private key. Using `SSH_AUTHORIZED_KEYS` you can replace the insecure public key with another one (or several). Further details on how to create your own private + public key pair are provided below. If adding more than one key it is recommended to either base64 encode the value or use a container file path in combination with a bind mounted file or Docker Swarm config etc. @@ -323,21 +354,6 @@ Using `SSH_AUTHORIZED_KEYS` with a container file path allows for the authorized ... ``` -##### SSH_AUTOSTART_SSHD & SSH_AUTOSTART_SSHD_BOOTSTRAP - -It may be desirable to prevent the startup of the sshd daemon and/or sshd-bootstrap script. For example, when using an image built from this Dockerfile as the source for another Dockerfile you could disable both sshd and sshd-booststrap from startup by setting `SSH_AUTOSTART_SSHD` and `SSH_AUTOSTART_SSHD_BOOTSTRAP` to `false`. The benefit of this is to reduce the number of running processes in the final container. - -``` -... - --env "SSH_AUTOSTART_SSHD=false" \ - --env "SSH_AUTOSTART_SSHD_BOOTSTRAP=false" \ -... -``` - -##### SSH_AUTOSTART_SUPERVISOR_STDOUT - -This image has `supervisor_stdout` installed which can be used to allow a process controlled by supervisord to send output to both a log file and stdout. It is recommended to simply output to stdout in order to reduce the number of running processes to a minimum. Setting `SSH_AUTOSTART_SUPERVISOR_STDOUT` to "false" will prevent the startup of `supervisor_stdout`. Where an image requires this feature for its logging output `SSH_AUTOSTART_SUPERVISOR_STDOUT` should be set to "true". - ##### SSH_CHROOT_DIRECTORY This option is only applicable when `SSH_USER_FORCE_SFTP` is set to `true`. When using the SFTP option the user is jailed into the ChrootDirectory. The value can contain the placeholders `%h` and `%u` which will be replaced with the values of `SSH_USER_HOME` and `SSH_USER` respectively. The default value of `%h` is the best choice in most cases but the user requires a sub-directory in their HOME directory which they have write access to. If no volume is mounted into the path of the SSH user's HOME directory then a directory named `_data` is created automatically. If you need the user to be able to write to their HOME directory then use an alternative value such as `/chroot/%u` so that the user's HOME path, (relative to the ChrootDirectory), becomes `/chroot/app-admin/home/app-admin` by default. @@ -378,16 +394,6 @@ On first run the SSH user is created with a the sudo rule `ALL=(ALL) ALL` which ... ``` -##### SSH_TIMEZONE - -If you require a locale based system time zone `SSH_TIMEZONE` can be used when running the container. - -``` -... - --env "SSH_TIMEZONE=Europe/London" \ -... -``` - ##### SSH_USER On first run the SSH user is created with the default username of "app-admin". If you require an alternative username `SSH_USER` can be used when running the container. @@ -471,6 +477,16 @@ If set to a valid container file path the value will be read from the file - thi ... ``` +##### SYSTEM_TIMEZONE + +If you require a locale based system time zone `SYSTEM_TIMEZONE` can be used when running the container. + +``` +... + --env "SYSTEM_TIMEZONE=Europe/London" \ +... +``` + ###### Generating a crypt SHA-512 password hash To generate a hashed password string for the password `Passw0rd!`, use the following method. diff --git a/default.mk b/default.mk index 380e2c7..29c97fe 100644 --- a/default.mk +++ b/default.mk @@ -41,15 +41,14 @@ DOCKER_PUBLISH := $(shell \ define DOCKER_CONTAINER_PARAMETERS --name $(DOCKER_NAME) \ --restart $(DOCKER_RESTART_POLICY) \ +--env "ENABLE_SSHD_BOOTSTRAP=$(ENABLE_SSHD_BOOTSTRAP)" \ +--env "ENABLE_SSHD_WRAPPER=$(ENABLE_SSHD_WRAPPER)" \ +--env "ENABLE_SUPERVISOR_STDOUT=$(ENABLE_SUPERVISOR_STDOUT)" \ --env "SSH_AUTHORIZED_KEYS=$(SSH_AUTHORIZED_KEYS)" \ ---env "SSH_AUTOSTART_SSHD=$(SSH_AUTOSTART_SSHD)" \ ---env "SSH_AUTOSTART_SSHD_BOOTSTRAP=$(SSH_AUTOSTART_SSHD_BOOTSTRAP)" \ ---env "SSH_AUTOSTART_SUPERVISOR_STDOUT=$(SSH_AUTOSTART_SUPERVISOR_STDOUT)" \ --env "SSH_CHROOT_DIRECTORY=$(SSH_CHROOT_DIRECTORY)" \ --env "SSH_INHERIT_ENVIRONMENT=$(SSH_INHERIT_ENVIRONMENT)" \ --env "SSH_PASSWORD_AUTHENTICATION=$(SSH_PASSWORD_AUTHENTICATION)" \ --env "SSH_SUDO=$(SSH_SUDO)" \ ---env "SSH_TIMEZONE=$(SSH_TIMEZONE)" \ --env "SSH_USER=$(SSH_USER)" \ --env "SSH_USER_FORCE_SFTP=$(SSH_USER_FORCE_SFTP)" \ --env "SSH_USER_HOME=$(SSH_USER_HOME)" \ @@ -57,5 +56,6 @@ define DOCKER_CONTAINER_PARAMETERS --env "SSH_USER_PASSWORD=$(SSH_USER_PASSWORD)" \ --env "SSH_USER_PASSWORD_HASHED=$(SSH_USER_PASSWORD_HASHED)" \ --env "SSH_USER_PRIVATE_KEY=$(SSH_USER_PRIVATE_KEY)" \ ---env "SSH_USER_SHELL=$(SSH_USER_SHELL)" +--env "SSH_USER_SHELL=$(SSH_USER_SHELL)" \ +--env "SYSTEM_TIMEZONE=$(SYSTEM_TIMEZONE)" endef diff --git a/docker-compose.yml b/docker-compose.yml index a95abaa..e96ea99 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,14 +28,13 @@ services: context: "." dockerfile: "Dockerfile" environment: + ENABLE_SSHD_BOOTSTRAP: "${ENABLE_SSHD_BOOTSTRAP}" + ENABLE_SSHD_WRAPPER: "${ENABLE_SSHD_WRAPPER}" SSH_AUTHORIZED_KEYS: "${SSH_AUTHORIZED_KEYS}" - SSH_AUTOSTART_SSHD: "${SSH_AUTOSTART_SSHD}" - SSH_AUTOSTART_SSHD_BOOTSTRAP: "${SSH_AUTOSTART_SSHD_BOOTSTRAP}" SSH_CHROOT_DIRECTORY: "${SSH_CHROOT_DIRECTORY}" SSH_INHERIT_ENVIRONMENT: "${SSH_INHERIT_ENVIRONMENT}" SSH_PASSWORD_AUTHENTICATION: "${SSH_PASSWORD_AUTHENTICATION}" SSH_SUDO: "${SSH_SUDO}" - SSH_TIMEZONE: "${SSH_TIMEZONE}" SSH_USER: "${SSH_USER}" SSH_USER_FORCE_SFTP: "${SSH_USER_FORCE_SFTP}" SSH_USER_HOME: "${SSH_USER_HOME}" @@ -44,6 +43,7 @@ services: SSH_USER_PASSWORD_HASHED: "${SSH_USER_PASSWORD_HASHED}" SSH_USER_PRIVATE_KEY: "${SSH_USER_PRIVATE_KEY}" SSH_USER_SHELL: "${SSH_USER_SHELL}" + SYSTEM_TIMEZONE: "${SYSTEM_TIMEZONE}" image: "jdeathe/centos-ssh:latest" ports: - "2020:22" diff --git a/environment.mk b/environment.mk index 9a6c64f..f404c49 100644 --- a/environment.mk +++ b/environment.mk @@ -1,46 +1,36 @@ # ------------------------------------------------------------------------------ # Constants # ------------------------------------------------------------------------------ -DOCKER_USER := jdeathe DOCKER_IMAGE_NAME := centos-ssh +DOCKER_IMAGE_RELEASE_TAG_PATTERN := ^[1-2]\.[0-9]+\.[0-9]+$ +DOCKER_IMAGE_TAG_PATTERN := ^(latest|[1-2]\.[0-9]+\.[0-9]+)$ SHPEC_ROOT := test/shpec - -# Tag validation patterns -DOCKER_IMAGE_TAG_PATTERN := ^(latest|centos-[6-7]|((1|2|centos-(6-1|7-2))\.[0-9]+\.[0-9]+))$ -DOCKER_IMAGE_RELEASE_TAG_PATTERN := ^(1|2|centos-(6-1|7-2))\.[0-9]+\.[0-9]+$ +DOCKER_USER := jdeathe # ------------------------------------------------------------------------------ # Variables # ------------------------------------------------------------------------------ - -# Docker image/container settings +DIST_PATH ?= ./dist DOCKER_CONTAINER_OPTS ?= DOCKER_IMAGE_TAG ?= latest DOCKER_NAME ?= ssh.1 DOCKER_PORT_MAP_TCP_22 ?= 2020 DOCKER_RESTART_POLICY ?= always - -# Docker build --no-cache parameter NO_CACHE ?= false - -# Directory path for release packages -DIST_PATH ?= ./dist - -# Number of seconds expected to complete container startup including bootstrap. +RELOAD_SIGNAL ?= HUP STARTUP_TIME ?= 2 # ------------------------------------------------------------------------------ # Application container configuration # ------------------------------------------------------------------------------ +ENABLE_SSHD_BOOTSTRAP ?= true +ENABLE_SSHD_WRAPPER ?= true +ENABLE_SUPERVISOR_STDOUT ?= false SSH_AUTHORIZED_KEYS ?= -SSH_AUTOSTART_SSHD ?= true -SSH_AUTOSTART_SSHD_BOOTSTRAP ?= true -SSH_AUTOSTART_SUPERVISOR_STDOUT ?= true SSH_CHROOT_DIRECTORY ?= %h SSH_INHERIT_ENVIRONMENT ?= false SSH_PASSWORD_AUTHENTICATION ?= false SSH_SUDO ?= ALL=(ALL) ALL -SSH_TIMEZONE ?= UTC SSH_USER ?= app-admin SSH_USER_FORCE_SFTP ?= false SSH_USER_HOME ?= /home/%u @@ -49,3 +39,4 @@ SSH_USER_PASSWORD ?= SSH_USER_PASSWORD_HASHED ?= false SSH_USER_PRIVATE_KEY ?= SSH_USER_SHELL ?= /bin/bash +SYSTEM_TIMEZONE ?= UTC diff --git a/src/etc/supervisord.conf b/src/etc/supervisord.conf index 944c30c..8923827 100755 --- a/src/etc/supervisord.conf +++ b/src/etc/supervisord.conf @@ -1,4 +1,5 @@ [supervisord] +environment = SSH_AUTOSTART_SSHD="%(ENV_ENABLE_SSHD_WRAPPER)s",SSH_AUTOSTART_SSHD_BOOTSTRAP="%(ENV_ENABLE_SSHD_BOOTSTRAP)s",SSH_AUTOSTART_SUPERVISOR_STDOUT="%(ENV_ENABLE_SSHD_BOOTSTRAP)s",SSH_TIMEZONE="%(ENV_SYSTEM_TIMEZONE)s" logfile = /dev/null logfile_maxbytes = 0 logfile_backups = 0 diff --git a/src/etc/supervisord.d/00-supervisor_stdout.conf b/src/etc/supervisord.d/00-supervisor_stdout.conf index 98bbcac..f904819 100644 --- a/src/etc/supervisord.d/00-supervisor_stdout.conf +++ b/src/etc/supervisord.d/00-supervisor_stdout.conf @@ -1,5 +1,5 @@ [eventlistener:supervisor_stdout] -autostart = %(ENV_SSH_AUTOSTART_SUPERVISOR_STDOUT)s +autostart = %(ENV_ENABLE_SUPERVISOR_STDOUT)s buffer_size = 100 command = /usr/bin/supervisor_stdout events = PROCESS_LOG diff --git a/src/etc/supervisord.d/10-system-timezone-wrapper.conf b/src/etc/supervisord.d/10-system-timezone-wrapper.conf new file mode 100644 index 0000000..0280685 --- /dev/null +++ b/src/etc/supervisord.d/10-system-timezone-wrapper.conf @@ -0,0 +1,11 @@ +[program:system-timezone-wrapper] +autorestart = false +autostart = true +command = /usr/sbin/system-timezone-wrapper --verbose +priority = 10 +startretries = 0 +startsecs = 0 +stderr_logfile = /dev/stderr +stderr_logfile_maxbytes = 0 +stdout_logfile = /dev/stdout +stdout_logfile_maxbytes = 0 diff --git a/src/etc/supervisord.d/sshd-bootstrap.conf b/src/etc/supervisord.d/20-sshd-bootstrap.conf similarity index 61% rename from src/etc/supervisord.d/sshd-bootstrap.conf rename to src/etc/supervisord.d/20-sshd-bootstrap.conf index b254f0a..fe79ae6 100644 --- a/src/etc/supervisord.d/sshd-bootstrap.conf +++ b/src/etc/supervisord.d/20-sshd-bootstrap.conf @@ -1,10 +1,11 @@ [program:sshd-bootstrap] autorestart = false -autostart = %(ENV_SSH_AUTOSTART_SSHD_BOOTSTRAP)s +autostart = %(ENV_ENABLE_SSHD_BOOTSTRAP)s command = /usr/sbin/sshd-bootstrap --verbose -priority = 5 -redirect_stderr = true +priority = 20 startretries = 0 startsecs = 0 +stderr_logfile = /dev/stderr +stderr_logfile_maxbytes = 0 stdout_logfile = /dev/stdout stdout_logfile_maxbytes = 0 diff --git a/src/etc/supervisord.d/50-sshd-wrapper.conf b/src/etc/supervisord.d/50-sshd-wrapper.conf new file mode 100644 index 0000000..9375cb2 --- /dev/null +++ b/src/etc/supervisord.d/50-sshd-wrapper.conf @@ -0,0 +1,11 @@ +[program:sshd-wrapper] +autorestart = true +autostart = %(ENV_ENABLE_SSHD_WRAPPER)s +command = /usr/sbin/sshd-wrapper --verbose +priority = 50 +startretries = 0 +startsecs = 5 +stderr_logfile = /dev/stderr +stderr_logfile_maxbytes = 0 +stdout_logfile = /dev/stdout +stdout_logfile_maxbytes = 0 diff --git a/src/etc/supervisord.d/sshd-wrapper.conf b/src/etc/supervisord.d/sshd-wrapper.conf deleted file mode 100644 index b71a51e..0000000 --- a/src/etc/supervisord.d/sshd-wrapper.conf +++ /dev/null @@ -1,9 +0,0 @@ -[program:sshd-wrapper] -autorestart = true -autostart = %(ENV_SSH_AUTOSTART_SSHD)s -command = /usr/sbin/sshd-wrapper -priority = 10 -redirect_stderr = true -startsecs = 0 -stdout_logfile = /dev/stdout -stdout_logfile_maxbytes = 0 diff --git a/src/etc/systemd/system/centos-ssh.register@.service b/src/etc/systemd/system/centos-ssh.register@.service index 28fe6ec..997c9e0 100644 --- a/src/etc/systemd/system/centos-ssh.register@.service +++ b/src/etc/systemd/system/centos-ssh.register@.service @@ -35,6 +35,7 @@ # # To uninstall: # sudo systemctl disable -f {service-unit-instance-name} +# sudo systemctl daemon-reload # sudo rm /etc/systemd/system/{service-unit-template-name} # sudo systemctl daemon-reload # ------------------------------------------------------------------------------ @@ -86,7 +87,7 @@ ExecStart=/bin/bash -c \ \"$(/usr/bin/docker port \ {{SERVICE_UNIT_NAME}}.%i \ 22 \ - | /usr/bin/sed 's~^[0-9.]*:~~' \ + | /bin/sed 's~^[0-9.]*:~~' \ )\" \ --ttl ${REGISTER_TTL} 2> /dev/null; \ fi; \ @@ -111,15 +112,15 @@ ExecStart=/bin/bash -c \ ${REGISTER_KEY_ROOT}/ports/tcp/22 \ &> /dev/null; \ then \ - echo set; \ + printf -- 'set\n'; \ else \ - echo update; \ + printf -- 'update\n'; \ fi) \ ${REGISTER_KEY_ROOT}/ports/tcp/22 \ \"$(/usr/bin/docker port \ {{SERVICE_UNIT_NAME}}.%i \ 22 \ - | /usr/bin/sed 's~^[0-9.]*:~~' \ + | /bin/sed 's~^[0-9.]*:~~' \ )\" \ --ttl ${REGISTER_TTL}; \ fi; \ @@ -131,9 +132,9 @@ ExecStart=/bin/bash -c \ ${REGISTER_KEY_ROOT}/hostname \ &> /dev/null; \ then \ - echo set; \ + printf -- 'set\n'; \ else \ - echo update; \ + printf -- 'update\n'; \ fi) \ ${REGISTER_KEY_ROOT}/hostname \ %H \ diff --git a/src/etc/systemd/system/centos-ssh@.service b/src/etc/systemd/system/centos-ssh@.service index e9eab5a..9787182 100644 --- a/src/etc/systemd/system/centos-ssh@.service +++ b/src/etc/systemd/system/centos-ssh@.service @@ -56,15 +56,14 @@ Environment="DOCKER_IMAGE_PACKAGE_PATH=/var/opt/scmi/packages" Environment="DOCKER_IMAGE_TAG={{RELEASE_VERSION}}" Environment="DOCKER_PORT_MAP_TCP_22=2020" Environment="DOCKER_USER=jdeathe" +Environment="ENABLE_SSHD_BOOTSTRAP=true" +Environment="ENABLE_SSHD_WRAPPER=true" +Environment="ENABLE_SUPERVISOR_STDOUT=false" Environment="SSH_AUTHORIZED_KEYS=" -Environment="SSH_AUTOSTART_SSHD=true" -Environment="SSH_AUTOSTART_SSHD_BOOTSTRAP=true" -Environment="SSH_AUTOSTART_SUPERVISOR_STDOUT=true" Environment="SSH_CHROOT_DIRECTORY=%%h" Environment="SSH_INHERIT_ENVIRONMENT=false" Environment="SSH_PASSWORD_AUTHENTICATION=false" Environment="SSH_SUDO=ALL=(ALL) ALL" -Environment="SSH_TIMEZONE=UTC" Environment="SSH_USER=app-admin" Environment="SSH_USER_FORCE_SFTP=false" Environment="SSH_USER_HOME=/home/%%u" @@ -73,6 +72,7 @@ Environment="SSH_USER_PASSWORD=" Environment="SSH_USER_PASSWORD_HASHED=false" Environment="SSH_USER_PRIVATE_KEY=" Environment="SSH_USER_SHELL=/bin/bash" +Environment="SYSTEM_TIMEZONE=UTC" # Initialisation: Load image from local storage if available, otherwise pull. ExecStartPre=/bin/bash -c \ @@ -129,15 +129,15 @@ ExecStartPre=-/bin/bash -c \ ExecStart=/bin/bash -c \ "exec /usr/bin/docker run \ --name %p.%i \ + --env \"ENABLE_SSHD_BOOTSTRAP=${ENABLE_SSHD_BOOTSTRAP}\" \ + --env \"ENABLE_SSHD_WRAPPER=${ENABLE_SSHD_WRAPPER}\" \ + --env \"ENABLE_SUPERVISOR_STDOUT=${ENABLE_SUPERVISOR_STDOUT}\" \ --env \"SSH_AUTHORIZED_KEYS=${SSH_AUTHORIZED_KEYS}\" \ - --env \"SSH_AUTOSTART_SSHD=${SSH_AUTOSTART_SSHD}\" \ - --env \"SSH_AUTOSTART_SSHD_BOOTSTRAP=${SSH_AUTOSTART_SSHD_BOOTSTRAP}\" \ - --env \"SSH_AUTOSTART_SUPERVISOR_STDOUT=${SSH_AUTOSTART_SUPERVISOR_STDOUT}\" \ --env \"SSH_CHROOT_DIRECTORY=${SSH_CHROOT_DIRECTORY}\" \ --env \"SSH_INHERIT_ENVIRONMENT=${SSH_INHERIT_ENVIRONMENT}\" \ --env \"SSH_PASSWORD_AUTHENTICATION=${SSH_PASSWORD_AUTHENTICATION}\" \ --env \"SSH_SUDO=${SSH_SUDO}\" \ - --env \"SSH_TIMEZONE=${SSH_TIMEZONE}\" \ + --env \"SYSTEM_TIMEZONE=${SYSTEM_TIMEZONE}\" \ --env \"SSH_USER=${SSH_USER}\" \ --env \"SSH_USER_FORCE_SFTP=${SSH_USER_FORCE_SFTP}\" \ --env \"SSH_USER_HOME=${SSH_USER_HOME}\" \ @@ -148,27 +148,27 @@ ExecStart=/bin/bash -c \ --env \"SSH_USER_SHELL=${SSH_USER_SHELL}\" \ $(if [[ ${DOCKER_PORT_MAP_TCP_22} != NULL ]]; \ then \ - if /usr/bin/grep -qE \ + if /bin/grep -qE \ '^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:)?[1-9][0-9]*$' \ <<< \"${DOCKER_PORT_MAP_TCP_22}\" \ - && /usr/bin/grep -qE \ + && /bin/grep -qE \ '^.+\.[0-9]+(\.[0-9]+)?$' \ - <<< \"${DOCKER_NAME}\"; \ + <<< %p.%i; \ then \ printf -- '--publish %%s%%s:22' \ $(\ - /usr/bin/grep -o \ + /bin/grep -o \ '^[0-9\.]*:' \ <<< \"${DOCKER_PORT_MAP_TCP_22}\" \ ) \ $(( \ $(\ - /usr/bin/grep -oE \ + /bin/grep -oE \ '[0-9]+$' \ <<< \"${DOCKER_PORT_MAP_TCP_22}\" \ ) \ + $(\ - /usr/bin/grep -oE \ + /bin/grep -oE \ '^[0-9]+' \ <<< %i \ ) \ diff --git a/src/opt/scmi/default.sh b/src/opt/scmi/default.sh index 652b1ce..977a152 100644 --- a/src/opt/scmi/default.sh +++ b/src/opt/scmi/default.sh @@ -46,15 +46,14 @@ fi # Common parameters of create and run targets DOCKER_CONTAINER_PARAMETERS="--name ${DOCKER_NAME} \ --restart ${DOCKER_RESTART_POLICY} \ +--env \"ENABLE_SSHD_BOOTSTRAP=${ENABLE_SSHD_BOOTSTRAP}\" \ +--env \"ENABLE_SSHD_WRAPPER=${ENABLE_SSHD_WRAPPER}\" \ +--env \"ENABLE_SUPERVISOR_STDOUT=${ENABLE_SUPERVISOR_STDOUT}\" \ --env \"SSH_AUTHORIZED_KEYS=${SSH_AUTHORIZED_KEYS}\" \ ---env \"SSH_AUTOSTART_SSHD=${SSH_AUTOSTART_SSHD}\" \ ---env \"SSH_AUTOSTART_SSHD_BOOTSTRAP=${SSH_AUTOSTART_SSHD_BOOTSTRAP}\" \ ---env \"SSH_AUTOSTART_SUPERVISOR_STDOUT=${SSH_AUTOSTART_SUPERVISOR_STDOUT}\" \ --env \"SSH_CHROOT_DIRECTORY=${SSH_CHROOT_DIRECTORY}\" \ --env \"SSH_INHERIT_ENVIRONMENT=${SSH_INHERIT_ENVIRONMENT}\" \ --env \"SSH_PASSWORD_AUTHENTICATION=${SSH_PASSWORD_AUTHENTICATION}\" \ --env \"SSH_SUDO=${SSH_SUDO}\" \ ---env \"SSH_TIMEZONE=${SSH_TIMEZONE}\" \ --env \"SSH_USER=${SSH_USER}\" \ --env \"SSH_USER_FORCE_SFTP=${SSH_USER_FORCE_SFTP}\" \ --env \"SSH_USER_HOME=${SSH_USER_HOME}\" \ @@ -63,4 +62,5 @@ DOCKER_CONTAINER_PARAMETERS="--name ${DOCKER_NAME} \ --env \"SSH_USER_PASSWORD_HASHED=${SSH_USER_PASSWORD_HASHED}\" \ --env \"SSH_USER_PRIVATE_KEY=${SSH_USER_PRIVATE_KEY}\" \ --env \"SSH_USER_SHELL=${SSH_USER_SHELL}\" \ +--env \"SYSTEM_TIMEZONE=${SYSTEM_TIMEZONE}\" \ ${DOCKER_PUBLISH}" diff --git a/src/opt/scmi/environment.sh b/src/opt/scmi/environment.sh index ece728d..433c68e 100644 --- a/src/opt/scmi/environment.sh +++ b/src/opt/scmi/environment.sh @@ -1,48 +1,37 @@ # ------------------------------------------------------------------------------ # Constants # ------------------------------------------------------------------------------ -readonly DOCKER_USER=jdeathe readonly DOCKER_IMAGE_NAME=centos-ssh - -# Tag validation patterns -readonly DOCKER_IMAGE_TAG_PATTERN='^(latest|centos-[6-7]|((1|2|centos-(6-1|7-2))\.[0-9]+\.[0-9]+))$' -readonly DOCKER_IMAGE_RELEASE_TAG_PATTERN='^(1|2|centos-(6-1|7-2))\.[0-9]+\.[0-9]+$' +readonly DOCKER_IMAGE_RELEASE_TAG_PATTERN='^[1-2]\.[0-9]+\.[0-9]+$' +readonly DOCKER_IMAGE_TAG_PATTERN='^(latest|[1-2]\.[0-9]+\.[0-9]+)$' +readonly DOCKER_USER=jdeathe # ------------------------------------------------------------------------------ # Variables # ------------------------------------------------------------------------------ - -# Docker image/container settings +DIST_PATH="${DIST_PATH:-./dist}" DOCKER_CONTAINER_OPTS="${DOCKER_CONTAINER_OPTS:-}" DOCKER_IMAGE_TAG="${DOCKER_IMAGE_TAG:-latest}" DOCKER_NAME="${DOCKER_NAME:-ssh.1}" DOCKER_PORT_MAP_TCP_22="${DOCKER_PORT_MAP_TCP_22:-2020}" DOCKER_RESTART_POLICY="${DOCKER_RESTART_POLICY:-always}" - -# Docker build --no-cache parameter NO_CACHE="${NO_CACHE:-false}" - -# Directory path for release packages -DIST_PATH="${DIST_PATH:-./dist}" - -# ETCD register service settings REGISTER_ETCD_PARAMETERS="${REGISTER_ETCD_PARAMETERS:-}" REGISTER_TTL="${REGISTER_TTL:-60}" REGISTER_UPDATE_INTERVAL="${REGISTER_UPDATE_INTERVAL:-55}" - -# Number of seconds expected to complete container startup including bootstrap. STARTUP_TIME="${STARTUP_TIME:-2}" +# ------------------------------------------------------------------------------ # Application container configuration +# ------------------------------------------------------------------------------ +ENABLE_SSHD_BOOTSTRAP="${ENABLE_SSHD_BOOTSTRAP:-true}" +ENABLE_SSHD_WRAPPER="${ENABLE_SSHD_WRAPPER:-true}" +ENABLE_SUPERVISOR_STDOUT="${ENABLE_SUPERVISOR_STDOUT:-false}" SSH_AUTHORIZED_KEYS="${SSH_AUTHORIZED_KEYS:-}" -SSH_AUTOSTART_SSHD="${SSH_AUTOSTART_SSHD:-true}" -SSH_AUTOSTART_SSHD_BOOTSTRAP="${SSH_AUTOSTART_SSHD_BOOTSTRAP:-true}" -SSH_AUTOSTART_SUPERVISOR_STDOUT="${SSH_AUTOSTART_SUPERVISOR_STDOUT:-true}" SSH_CHROOT_DIRECTORY="${SSH_CHROOT_DIRECTORY:-%h}" SSH_INHERIT_ENVIRONMENT="${SSH_INHERIT_ENVIRONMENT:-false}" SSH_PASSWORD_AUTHENTICATION="${SSH_PASSWORD_AUTHENTICATION:-false}" SSH_SUDO="${SSH_SUDO:-ALL=(ALL) ALL}" -SSH_TIMEZONE="${SSH_TIMEZONE:-UTC}" SSH_USER="${SSH_USER:-app-admin}" SSH_USER_FORCE_SFTP="${SSH_USER_FORCE_SFTP:-false}" SSH_USER_HOME="${SSH_USER_HOME:-/home/%u}" @@ -51,3 +40,4 @@ SSH_USER_PASSWORD="${SSH_USER_PASSWORD:-}" SSH_USER_PASSWORD_HASHED="${SSH_USER_PASSWORD_HASHED:-false}" SSH_USER_PRIVATE_KEY="${SSH_USER_PRIVATE_KEY:-}" SSH_USER_SHELL="${SSH_USER_SHELL:-/bin/bash}" +SYSTEM_TIMEZONE="${SYSTEM_TIMEZONE:-UTC}" diff --git a/src/opt/scmi/service-unit.sh b/src/opt/scmi/service-unit.sh index 6e04daf..6c6c131 100644 --- a/src/opt/scmi/service-unit.sh +++ b/src/opt/scmi/service-unit.sh @@ -6,15 +6,14 @@ readonly SERVICE_UNIT_ENVIRONMENT_KEYS=" DOCKER_IMAGE_PACKAGE_PATH DOCKER_IMAGE_TAG DOCKER_PORT_MAP_TCP_22 + ENABLE_SSHD_BOOTSTRAP + ENABLE_SSHD_WRAPPER + ENABLE_SUPERVISOR_STDOUT SSH_AUTHORIZED_KEYS - SSH_AUTOSTART_SSHD - SSH_AUTOSTART_SSHD_BOOTSTRAP - SSH_AUTOSTART_SUPERVISOR_STDOUT SSH_CHROOT_DIRECTORY SSH_INHERIT_ENVIRONMENT SSH_PASSWORD_AUTHENTICATION SSH_SUDO - SSH_TIMEZONE SSH_USER SSH_USER_FORCE_SFTP SSH_USER_HOME @@ -23,6 +22,7 @@ readonly SERVICE_UNIT_ENVIRONMENT_KEYS=" SSH_USER_PASSWORD_HASHED SSH_USER_PRIVATE_KEY SSH_USER_SHELL + SYSTEM_TIMEZONE " readonly SERVICE_UNIT_REGISTER_ENVIRONMENT_KEYS=" REGISTER_ETCD_PARAMETERS diff --git a/src/usr/bin/healthcheck b/src/usr/bin/healthcheck index 99f164f..c2d6189 100755 --- a/src/usr/bin/healthcheck +++ b/src/usr/bin/healthcheck @@ -29,11 +29,49 @@ function __get_ssh_user () printf -- '%s' "${value}" } +function __get_system_timezone () +{ + local -r default_value="${1:-UTC}" + + local value="${SYSTEM_TIMEZONE}" + + if ! __is_valid_system_timezone "${value}" + then + value="${default_value}" + fi + + printf -- '%s' "${value}" +} + +function __is_valid_system_timezone () +{ + __is_valid_zone "${@}" +} + +function __is_valid_zone () +{ + local zone="${1}" + + if [[ -n ${zone} ]] \ + && [[ -f /usr/share/zoneinfo/${zone} ]] + then + return 0 + fi + + return 1 +} + function main () { + local -r system_timezone="$( + __get_system_timezone + )" local -r user="$( __get_ssh_user )" + local -r zone="$( + system-timezone -qq + )" if ! ps axo command | grep -qE '^/usr/bin/python /usr/bin/supervisord' then @@ -43,7 +81,15 @@ function main () exit 1 fi - if [[ ${SSH_AUTOSTART_SSHD_BOOTSTRAP} == true ]] + if [[ ${system_timezone} != "${zone}" ]] + then + >&2 printf -- \ + '%s\n' \ + "system-timezone zone mismatch." + exit 1 + fi + + if [[ ${ENABLE_SSHD_BOOTSTRAP} == true ]] then if [[ -e /var/lock/subsys/sshd-bootstrap ]] then @@ -62,7 +108,7 @@ function main () fi fi - if [[ ${SSH_AUTOSTART_SSHD} == true ]] + if [[ ${ENABLE_SSHD_WRAPPER} == true ]] then if ! ps axo command | grep -qE '^/usr/sbin/sshd -D' \ || [[ ! -s /var/run/sshd.pid ]] diff --git a/src/usr/sbin/sshd-bootstrap b/src/usr/sbin/sshd-bootstrap index 6ee2fbf..6bd30b0 100755 --- a/src/usr/sbin/sshd-bootstrap +++ b/src/usr/sbin/sshd-bootstrap @@ -1,312 +1,436 @@ #!/usr/bin/env bash -function __is_sudo_no_password_all () +set -e + +function __cleanup () { - local -r sudo="${1}" - local -r pattern=' ?NOPASSWD:ALL$' + local -r exit_code="${?}" - if [[ ${sudo} =~ ${pattern} ]] + __delete_lock + + if [[ ${exit_code} -eq 0 ]] then - return 0 + __create_state fi - - return 1 } -function __is_valid_ssh_authorized_keys () +function __create_lock () { - local -r authorized_keys="${1}" - local -r invalid_key_pattern='is not a public key file.$' - - local ssh_key - local IFS="" - - if [[ -z ${authorized_keys} ]] + if [[ -n ${lock_file} ]] then - return 1 + touch "${lock_file}" fi - - while read -r ssh_key || [[ -n ${ssh_key} ]] - do - if [[ -n ${ssh_key} ]] \ - && [[ $( - __get_ssh_key_fingerprint "${ssh_key}" - ) =~ ${invalid_key_pattern} ]] - then - return 1 - fi - done <<< "${authorized_keys}" - - return 0 } -function __is_valid_ssh_chroot_directory () +function __create_state () { - local -r chroot_directory="${1}" - local -r safe_directory='^(%h|\/(?!\/|bin|dev|etc|lib|lib64|lost+found|media|proc|root|sbin|srv|sys|tmp|usr).+)$' - - if [[ -z ${chroot_directory} ]] + if [[ -n ${state_file} ]] then - return 1 + touch "${state_file}" fi +} - if grep -qoP "${safe_directory}" <<< "${chroot_directory}" +function __delete_lock () +{ + if [[ -e ${lock_file} ]] then - return 0 + rm -f "${lock_file}" fi - - return 1 } -function __is_valid_ssh_inherit_environment () +function __generate_ssh_host_key () { - local -r boolean_value='^(true|false)$' - local -r value="${1}" + local -r replace="${1:-false}" + local -r type="${2:-rsa}" - if [[ ${value} =~ ${boolean_value} ]] + local private_key_path + local public_key_path + local response='n\n' + + if [[ ${replace} == true ]] then - return 0 + response='y\n' fi - return 1 + case "${type}" in + rsa1|rsa|dsa|ecdsa|ed25519) + if [[ ${type} != rsa1 ]] + then + private_key_path=/etc/ssh/ssh_host_"${type}"_key + public_key_path=/etc/ssh/ssh_host_"${type}"_key.pub + else + private_key_path=/etc/ssh/ssh_host_key + public_key_path=/etc/ssh/ssh_host_key.pub + fi + + printf -- \ + "${response}" \ + | ssh-keygen \ + -q \ + -C "" \ + -N "" \ + -t "${type}" \ + -f "${private_key_path}" \ + &> /dev/null + + if [[ -x /sbin/restorecon ]] + then + /sbin/restorecon "${public_key_path}" + fi + + ;; + *) + printf -- \ + 'Unknown key type %s - skipping.\n' \ + "${type}" + ;; + esac } -function __is_valid_ssh_key () +function __generate_ssh_host_keys () { - local -r public_key="${1}" - local -r invalid_key_pattern='is not a public key file.$' + local -r replace="${1:-false}" - if [[ -z ${public_key} ]] - then - return 1 - fi + local version="${2}" - if [[ -n ${public_key} ]] \ - && [[ $( - __get_ssh_key_fingerprint "${public_key}" - ) =~ ${invalid_key_pattern} ]] + if [[ -z ${version} ]] \ + && [[ -e /etc/redhat-release ]] then + version="$( + rpm -q \ + --whatprovides redhat-release \ + --queryformat "%{VERSION}" + )" + else + >&2 printf -- \ + 'ERROR: Unknown EL release.\n' return 1 fi - return 0 + case "${version}" in + 6) + __generate_ssh_host_key "${replace}" rsa1 + __generate_ssh_host_key "${replace}" rsa + __generate_ssh_host_key "${replace}" dsa + ;; + 7) + __generate_ssh_host_key "${replace}" rsa1 + __generate_ssh_host_key "${replace}" rsa + __generate_ssh_host_key "${replace}" dsa + __generate_ssh_host_key "${replace}" ecdsa + __generate_ssh_host_key "${replace}" ed25519 + ;; + *) + printf -- \ + 'Unknown EL release %s - skipping.\n' \ + "${version}" + ;; + esac } -function __is_valid_ssh_password_authentication () +function __get_password () { - local -r boolean_value='^(true|false)$' - local -r value="${1}" + local -r password_length="${1:-16}" - if [[ ${value} =~ ${boolean_value} ]] - then - return 0 - fi + local password="$( + head -n 4096 /dev/urandom \ + | tr -cd '[:alnum:]' \ + | cut -c1-"${password_length}" + )" - return 1 + printf -- '%s' "${password}" } -function __is_valid_ssh_sudo () +function __get_ssh_authorized_keys () { - local -r temp_path="$( - mktemp -u + local -r default_value="ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key" + local -r pattern_base64='^[A-Za-z0-9/+=]*$' + local -r password_authentication="$( + __get_ssh_password_authentication )" - local -r sudo_cmd="${1}" - if [[ -z ${sudo_cmd} ]] + local value="${SSH_AUTHORIZED_KEYS}" + + if [[ -n ${value} ]] \ + && [[ ${value} =~ ${pattern_base64} ]] then - return 1 + value="$( + base64 -d -i <<< "${value}" + )" + elif [[ -n ${value} ]] \ + && [[ -f ${value} ]] + then + value="$(< "${value}")" fi - trap \ - "rm -f \"${temp_path}\"" \ - EXIT INT RETURN TERM - - printf -- \ - '%%wheel %s\n' \ - "${sudo_cmd}" \ - > "${temp_path}" - - if visudo \ - -c \ - -q \ - -f "${temp_path}" \ - &> /dev/null + # Force a default key if PasswordAuthentication is disabled, maintaining + # the original behaviour. + if [[ ${password_authentication} == false ]] \ + && [[ -z ${value} ]] then - return 0 + value="${default_value}" fi - return 1 + printf -- '%s' "${value}" } -function __is_valid_ssh_timezone () +function __get_ssh_authorized_key_fingerprints () { - local -r zone="${1}" + local -r authorized_keys="${1:-"$( + __get_ssh_authorized_keys + )"}" - if [[ -z ${zone} ]] - then - return 1 - fi + local ssh_key + local value + local IFS="" - if [[ -f /usr/share/zoneinfo/${zone} ]] + if [[ -n ${authorized_keys} ]] then - return 0 + while read -r ssh_key || [[ -n ${ssh_key} ]] + do + if __is_valid_ssh_key "${ssh_key}" + then + printf -v value \ + -- \ + '%s%s\n' \ + "${value}" \ + "$( + __get_ssh_key_fingerprint_hash_output "${ssh_key}" + )" + fi + done <<< "${authorized_keys}" fi - return 1 + printf -- '%s' "${value}" } -function __is_valid_ssh_user () +function __get_ssh_chroot_directory () { - local -r safe_user='^[a-z_][a-z0-9_-]{0,29}[$a-z0-9_]?$' - local -r user="${1}" + local -r default_value="${1:-%h}" - if [[ ${user} =~ ${safe_user} ]] + local value="${SSH_CHROOT_DIRECTORY}" + + if ! __is_valid_ssh_chroot_directory "${value}" then - return 0 + value="${default_value}" fi - return 1 + printf -- '%s' "${value}" } -function __is_valid_ssh_user_force_sftp () +function __get_ssh_chroot_directory_path () { - local -r boolean_value='^(true|false)$' - local -r value="${1}" + local -r default_chroot_directory="%h" + local -r user="$( + __get_ssh_user + )" + local -r home="$( + __get_ssh_user_home + )" - if [[ ${value} =~ ${boolean_value} ]] + local chroot_directory="${1:-"$( + __get_ssh_chroot_directory + )"}" + local value + + if ! __is_valid_ssh_chroot_directory "${chroot_directory}" then - return 0 + chroot_directory="${default_chroot_directory}" fi - return 1 + # Replace %h with SSH_USER_HOME + value="${chroot_directory//'%h'/${home}}" + + # Replace %u with SSH_USER + value="${value//'%u'/${user}}" + + printf -- '%s' "${value}" } -function __is_valid_ssh_user_home () +function __get_ssh_inherit_environment () { - local -r home_directory="${1}" - local -r user_directory='^\/(?!\/|bin|dev|etc|lib|lib64|lost+found|media|proc|root|sbin|srv|sys|tmp|usr).+$' - local -r root_directory='^/root$' - local -r user="${2:-"$( - __get_ssh_user - )"}" + local -r default_value="${1:-false}" - local safe_directory="${user_directory}" + local value="${SSH_INHERIT_ENVIRONMENT}" - if [[ -z ${home_directory} ]] + if ! __is_valid_ssh_inherit_environment "${value}" then - return 1 + value="${default_value}" fi - if [[ ${user} == root ]] - then - safe_directory="${root_directory}" - fi + printf -- '%s' "${value}" +} - if grep -qoP "${safe_directory}" <<< "${home_directory}" - then - return 0 - fi +function __get_ssh_host_key_fingerprint () +{ + local -r type="${1:-rsa}" - return 1 + local fingerprint + local public_key_path + local ssh_key + + case "${type}" in + rsa1|rsa|dsa|ecdsa|ed25519) + public_key_path=/etc/ssh/ssh_host_"${type}"_key.pub + ssh_key="$(< "${public_key_path}")" + + if __is_valid_ssh_key "${ssh_key}" + then + __get_ssh_key_fingerprint_hash_output "${ssh_key}" + else + >&2 printf -- \ + 'ERROR: invalid host key\n' + return 1 + fi + ;; + *) + >&2 printf -- \ + 'ERROR: invalid key type\n' + return 1 + ;; + esac } -function __is_valid_ssh_user_id () +function __get_ssh_key_fingerprint () { - local -r id="${1}" - local -r user_id_pattern='^[1-9][0-9]*:[1-9][0-9]*$' - local -r root_id_pattern='^0:0$' - local -r user="${2:-"$( - __get_ssh_user - )"}" + local -r ssh_key="${1}" + local -r ssh_key_file="$( + mktemp + )" - local id_pattern="${user_id_pattern}" + local fingerprint + local public_key - if [[ ${user} == root ]] + printf -- \ + '%s\n' \ + "${ssh_key}" \ + > "${ssh_key_file}" + + # If an RSA private key convert to a public key + if openssl rsa -check -noout -in \ + "${ssh_key_file}" \ + &> /dev/null then - id_pattern="${root_id_pattern}" + public_key="$( + ssh-keygen \ + -y \ + -f "${ssh_key_file}" + )" + + printf -- \ + '%s\n' \ + "${public_key}" \ + > "${ssh_key_file}" fi - if [[ ${id} =~ ${id_pattern} ]] + fingerprint="$( + ssh-keygen \ + -l \ + -E md5 \ + -f "${ssh_key_file}" \ + | sed 's~MD5:~~' + )" + + rm -f \ + "${ssh_key_file}" + + printf -- '%s' "${fingerprint}" +} + +function __get_ssh_key_fingerprint_hash_output () +{ + local -r ssh_key="${1}" + local -r insecure_fingerprint='dd:3b:b8:2e:85:04:06:e9:ab:ff:a8:0a:c0:04:6e:d6' + + local value + + if __is_valid_ssh_key "${ssh_key}" then - return 0 + printf -v value \ + -- \ + '%s' \ + "$( + __get_ssh_key_fingerprint "${ssh_key}" \ + | awk '{ print $2; }' + )" + + # Indicate use of insecure public key + if [[ ${value} == ${insecure_fingerprint} ]] + then + value+=" (insecure key)" + fi + + printf -v value \ + -- \ + '%s\n' \ + "${value}" fi - return 1 + printf -- '%s' "${value}" } -function __is_valid_ssh_user_password_hash () +function __get_ssh_password_authentication () { - local -r password_hash="${1}" - local -r sha_512_pattern='^\$6\$[a-zA-Z0-9./]{0,16}\$[a-zA-Z0-9./]{86}$' + local -r default_value="${1:-false}" - if [[ ${password_hash} =~ ${sha_512_pattern} ]] + local value="${SSH_PASSWORD_AUTHENTICATION}" + + if ! __is_valid_ssh_password_authentication "${value}" then - return 0 + value="${default_value}" fi - return 1 + printf -- '%s' "${value}" } -function __is_valid_ssh_user_password_hashed () +function __get_ssh_sudo () { - local -r boolean_value='^(true|false)$' - local -r value="${1}" + local -r default_value="${1:-"ALL=(ALL) ALL"}" - if [[ ${value} =~ ${boolean_value} ]] + local value="${SSH_SUDO}" + + if ! __is_valid_ssh_sudo "${value}" then - return 0 + value="${default_value}" fi - return 1 + printf -- '%s' "${value}" } -function __is_valid_ssh_user_shell () +function __get_ssh_user () { - local -r shell="${1}" - local -r valid_shells="$( - chsh --list-shells - )" + local -r default_value="${1:-app-admin}" - local valid_shell + local value="${SSH_USER}" - if [[ -z ${shell} ]] + if ! __is_valid_ssh_user "${value}" then - return 1 + value="${default_value}" fi - for valid_shell in ${valid_shells} - do - if [[ ${valid_shell} == "${shell}" ]] - then - return 0 - fi - done - - return 1 + printf -- '%s' "${value}" } -function __get_password () +function __get_ssh_user_password_hashed () { - local -r password_length="${1:-16}" + local -r default_value="${1:-false}" - local password="$( - head -n 4096 /dev/urandom \ - | tr -cd '[:alnum:]' \ - | cut -c1-"${password_length}" - )" + local value="${SSH_USER_PASSWORD_HASHED}" - printf -- '%s' "${password}" + if ! __is_valid_ssh_user_password_hashed "${value}" + then + value="${default_value}" + fi + + printf -- '%s' "${value}" } -function __get_ssh_authorized_keys () +function __get_ssh_user_private_key () { - local -r default_value="ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key" + local -r default_value="" local -r pattern_base64='^[A-Za-z0-9/+=]*$' - local -r password_authentication="$( - __get_ssh_password_authentication - )" - local value="${SSH_AUTHORIZED_KEYS}" + local value="${SSH_USER_PRIVATE_KEY}" if [[ -n ${value} ]] \ && [[ ${value} =~ ${pattern_base64} ]] @@ -320,10 +444,16 @@ function __get_ssh_authorized_keys () value="$(< "${value}")" fi - # Force a default key if PasswordAuthentication is disabled, maintaining - # the original behaviour. - if [[ ${password_authentication} == false ]] \ - && [[ -z ${value} ]] + printf -- '%s' "${value}" +} + +function __get_ssh_user_force_sftp () +{ + local -r default_value="${1:-false}" + + local value="${SSH_USER_FORCE_SFTP}" + + if ! __is_valid_ssh_user_force_sftp "${value}" then value="${default_value}" fi @@ -435,20 +565,14 @@ function __get_ssh_host_key_fingerprint () then __get_ssh_key_fingerprint_hash_output "${ssh_key}" else - printf -- \ - 'ERROR: invalid host key\n' \ - >&2 - sleep 0.1 - + >&2 printf -- \ + 'ERROR: invalid host key\n' return 1 fi ;; *) - printf -- \ - 'ERROR: invalid key type\n' \ - >&2 - sleep 0.1 - + >&2 printf -- \ + 'ERROR: invalid key type\n' return 1 ;; esac @@ -569,301 +693,472 @@ function __get_ssh_timezone () value="${default_value}" fi - printf -- '%s' "${value}" + printf -- '%s' "${value}" +} + +function __get_ssh_user () +{ + local -r default_value="${1:-app-admin}" + + local value="${SSH_USER}" + + if ! __is_valid_ssh_user "${value}" + then + value="${default_value}" + fi + + printf -- '%s' "${value}" +} + +function __get_ssh_user_password_hashed () +{ + local -r default_value="${1:-false}" + + local value="${SSH_USER_PASSWORD_HASHED}" + + if ! __is_valid_ssh_user_password_hashed "${value}" + then + value="${default_value}" + fi + + printf -- '%s' "${value}" +} + +function __get_ssh_user_private_key () +{ + local -r default_value="" + local -r pattern_base64='^[A-Za-z0-9/+=]*$' + + local value="${SSH_USER_PRIVATE_KEY}" + + if [[ -n ${value} ]] \ + && [[ ${value} =~ ${pattern_base64} ]] + then + value="$( + base64 -d -i <<< "${value}" + )" + elif [[ -n ${value} ]] \ + && [[ -f ${value} ]] + then + value="$(< "${value}")" + fi + + printf -- '%s' "${value}" +} + +function __get_ssh_user_force_sftp () +{ + local -r default_value="${1:-false}" + + local value="${SSH_USER_FORCE_SFTP}" + + if ! __is_valid_ssh_user_force_sftp "${value}" + then + value="${default_value}" + fi + + printf -- '%s' "${value}" +} + +function __get_ssh_user_gid () +{ + local -r default_value="${1:-500}" + local -r id="$( + __get_ssh_user_id + )" + local -r id_pattern='^([0-9]{1,}):([0-9]{1,})$' + + local value="${default_value}" + + if [[ ${id} =~ ${id_pattern} ]] + then + value="${BASH_REMATCH[2]}" + fi + + printf -- '%d' "${value}" +} + +function __get_ssh_user_home () +{ + local -r default_value="${1:-/home/%u}" + local -r root_value="/root" + local -r user="${2:-"$( + __get_ssh_user + )"}" + + local value="${SSH_USER_HOME}" + + if ! __is_valid_ssh_user_home "${value}" + then + if [[ ${user} == root ]] + then + value="${root_value}" + else + value="${default_value}" + fi + fi + + # Replace %u with SSH_USER + value="${value//'%u'/${user}}" + + printf -- '%s' "${value}" +} + +function __get_ssh_user_id () +{ + local -r default_value="${1:-500:500}" + local -r root_value="0:0" + local -r user="${2:-"$( + __get_ssh_user + )"}" + + local value="${SSH_USER_ID}" + + if ! __is_valid_ssh_user_id "${value}" + then + if [[ ${user} == root ]] + then + value="${root_value}" + else + value="${default_value}" + fi + fi + + printf -- '%s' "${value}" +} + +function __get_ssh_user_shell () +{ + local -r default_value="${1:-/bin/bash}" + local -r force_sftp="$( + __get_ssh_user_force_sftp + )" + + local value="${SSH_USER_SHELL}" + + if ! __is_valid_ssh_user_shell "${value}" + then + value="${default_value}" + fi + + # SFTP users cannot have shell access + if [[ ${force_sftp} == true ]] + then + value="/sbin/nologin" + fi + + printf -- '%s' "${value}" +} + +function __get_ssh_user_uid () +{ + local -r default_value="${1:-500}" + local -r id="$( + __get_ssh_user_id + )" + local -r id_pattern='^([0-9]{1,}):([0-9]{1,})$' + + local value="${default_value}" + + if [[ ${id} =~ ${id_pattern} ]] + then + value="${BASH_REMATCH[1]}" + fi + + printf -- '%d' "${value}" +} + +function __get_timer_total () +{ + local -r timer_end="$( + date -u +%s.%N + )" + local -r timer_start="${1}" + + if [[ -z ${timer_start} ]] \ + || [[ ${timer_start//.} -gt ${timer_end//.} ]] + then + >&2 printf -- \ + 'ERROR: invalid timer start: %s\n' \ + "${timer_start}" + printf -- \ + '0.000000' + else + awk \ + -v timer_end="${timer_end}" \ + -v timer_start="${timer_start}" \ + 'BEGIN { print \ + timer_end - timer_start; + }' + fi +} + +function __is_sudo_no_password_all () +{ + local -r sudo="${1}" + local -r pattern=' ?NOPASSWD:ALL$' + + if [[ ${sudo} =~ ${pattern} ]] + then + return 0 + fi + + return 1 +} + +function __is_valid_ssh_authorized_keys () +{ + local -r authorized_keys="${1}" + local -r invalid_key_pattern='is not a public key file.$' + + local ssh_key + local IFS="" + + if [[ -z ${authorized_keys} ]] + then + return 1 + fi + + while read -r ssh_key || [[ -n ${ssh_key} ]] + do + if [[ -n ${ssh_key} ]] \ + && [[ $( + __get_ssh_key_fingerprint "${ssh_key}" + ) =~ ${invalid_key_pattern} ]] + then + return 1 + fi + done <<< "${authorized_keys}" + + return 0 +} + +function __is_valid_ssh_chroot_directory () +{ + local -r chroot_directory="${1}" + local -r safe_directory='^(%h|\/(?!\/|bin|dev|etc|lib|lib64|lost+found|media|proc|root|sbin|srv|sys|tmp|usr).+)$' + + if [[ -z ${chroot_directory} ]] + then + return 1 + fi + + if grep -qoP "${safe_directory}" <<< "${chroot_directory}" + then + return 0 + fi + + return 1 } -function __get_ssh_user () +function __is_valid_ssh_inherit_environment () { - local -r default_value="${1:-app-admin}" - - local value="${SSH_USER}" + local -r boolean_value='^(true|false)$' + local -r value="${1}" - if ! __is_valid_ssh_user "${value}" + if [[ ${value} =~ ${boolean_value} ]] then - value="${default_value}" + return 0 fi - printf -- '%s' "${value}" + return 1 } -function __get_ssh_user_password_hashed () +function __is_valid_ssh_key () { - local -r default_value="${1:-false}" + local -r public_key="${1}" + local -r invalid_key_pattern='is not a public key file.$' - local value="${SSH_USER_PASSWORD_HASHED}" + if [[ -z ${public_key} ]] + then + return 1 + fi - if ! __is_valid_ssh_user_password_hashed "${value}" + if [[ -n ${public_key} ]] \ + && [[ $( + __get_ssh_key_fingerprint "${public_key}" + ) =~ ${invalid_key_pattern} ]] then - value="${default_value}" + return 1 fi - printf -- '%s' "${value}" + return 0 } -function __get_ssh_user_private_key () +function __is_valid_ssh_password_authentication () { - local -r default_value="" - local -r pattern_base64='^[A-Za-z0-9/+=]*$' - - local value="${SSH_USER_PRIVATE_KEY}" + local -r boolean_value='^(true|false)$' + local -r value="${1}" - if [[ -n ${value} ]] \ - && [[ ${value} =~ ${pattern_base64} ]] - then - value="$( - base64 -d -i <<< "${value}" - )" - elif [[ -n ${value} ]] \ - && [[ -f ${value} ]] + if [[ ${value} =~ ${boolean_value} ]] then - value="$(< "${value}")" + return 0 fi - printf -- '%s' "${value}" + return 1 } -function __get_ssh_user_force_sftp () +function __is_valid_ssh_sudo () { - local -r default_value="${1:-false}" + local -r temp_path="$( + mktemp -u + )" + local -r sudo_cmd="${1}" - local value="${SSH_USER_FORCE_SFTP}" + if [[ -z ${sudo_cmd} ]] + then + return 1 + fi - if ! __is_valid_ssh_user_force_sftp "${value}" + trap \ + "rm -f \"${temp_path}\"" \ + RETURN + + printf -- \ + '%%wheel %s\n' \ + "${sudo_cmd}" \ + > "${temp_path}" + + if visudo \ + -c \ + -q \ + -f "${temp_path}" \ + &> /dev/null then - value="${default_value}" + return 0 fi - printf -- '%s' "${value}" + return 1 } -function __get_ssh_user_gid () +function __is_valid_ssh_user () { - local -r default_value="${1:-500}" - local -r id="$( - __get_ssh_user_id - )" - local -r id_pattern='^([0-9]{1,}):([0-9]{1,})$' - - local value="${default_value}" + local -r safe_user='^[a-z_][a-z0-9_-]{0,29}[$a-z0-9_]?$' + local -r user="${1}" - if [[ ${id} =~ ${id_pattern} ]] + if [[ ${user} =~ ${safe_user} ]] then - value="${BASH_REMATCH[2]}" + return 0 fi - printf -- '%d' "${value}" + return 1 } -function __get_ssh_user_home () +function __is_valid_ssh_user_force_sftp () { - local -r default_value="${1:-/home/%u}" - local -r root_value="/root" - local -r user="${2:-"$( - __get_ssh_user - )"}" - - local value="${SSH_USER_HOME}" + local -r boolean_value='^(true|false)$' + local -r value="${1}" - if ! __is_valid_ssh_user_home "${value}" + if [[ ${value} =~ ${boolean_value} ]] then - if [[ ${user} == root ]] - then - value="${root_value}" - else - value="${default_value}" - fi + return 0 fi - # Replace %u with SSH_USER - value="${value//'%u'/${user}}" - - printf -- '%s' "${value}" + return 1 } -function __get_ssh_user_id () +function __is_valid_ssh_user_home () { - local -r default_value="${1:-500:500}" - local -r root_value="0:0" + local -r home_directory="${1}" + local -r user_directory='^\/(?!\/|bin|dev|etc|lib|lib64|lost+found|media|proc|root|sbin|srv|sys|tmp|usr).+$' + local -r root_directory='^/root$' local -r user="${2:-"$( __get_ssh_user )"}" - local value="${SSH_USER_ID}" + local safe_directory="${user_directory}" - if ! __is_valid_ssh_user_id "${value}" + if [[ -z ${home_directory} ]] then - if [[ ${user} == root ]] - then - value="${root_value}" - else - value="${default_value}" - fi + return 1 fi - printf -- '%s' "${value}" -} - -function __get_ssh_user_shell () -{ - local -r default_value="${1:-/bin/bash}" - local -r force_sftp="$( - __get_ssh_user_force_sftp - )" - - local value="${SSH_USER_SHELL}" - - if ! __is_valid_ssh_user_shell "${value}" + if [[ ${user} == root ]] then - value="${default_value}" + safe_directory="${root_directory}" fi - # SFTP users cannot have shell access - if [[ ${force_sftp} == true ]] + if grep -qoP "${safe_directory}" <<< "${home_directory}" then - value="/sbin/nologin" + return 0 fi - printf -- '%s' "${value}" + return 1 } -function __get_ssh_user_uid () +function __is_valid_ssh_user_id () { - local -r default_value="${1:-500}" - local -r id="$( - __get_ssh_user_id - )" - local -r id_pattern='^([0-9]{1,}):([0-9]{1,})$' + local -r id="${1}" + local -r user_id_pattern='^[1-9][0-9]*:[1-9][0-9]*$' + local -r root_id_pattern='^0:0$' + local -r user="${2:-"$( + __get_ssh_user + )"}" - local value="${default_value}" + local id_pattern="${user_id_pattern}" + + if [[ ${user} == root ]] + then + id_pattern="${root_id_pattern}" + fi if [[ ${id} =~ ${id_pattern} ]] then - value="${BASH_REMATCH[1]}" + return 0 fi - printf -- '%d' "${value}" + return 1 } -function __generate_ssh_host_key () +function __is_valid_ssh_user_password_hash () { - local -r replace="${1:-false}" - local -r type="${2:-rsa}" - - local private_key_path - local public_key_path - local response='n\n' + local -r password_hash="${1}" + local -r sha_512_pattern='^\$6\$[a-zA-Z0-9./]{0,16}\$[a-zA-Z0-9./]{86}$' - if [[ ${replace} == true ]] + if [[ ${password_hash} =~ ${sha_512_pattern} ]] then - response='y\n' + return 0 fi - case "${type}" in - rsa1|rsa|dsa|ecdsa|ed25519) - if [[ ${type} != rsa1 ]] - then - private_key_path=/etc/ssh/ssh_host_"${type}"_key - public_key_path=/etc/ssh/ssh_host_"${type}"_key.pub - else - private_key_path=/etc/ssh/ssh_host_key - public_key_path=/etc/ssh/ssh_host_key.pub - fi - - printf -- \ - "${response}" \ - | ssh-keygen \ - -q \ - -C "" \ - -N "" \ - -t "${type}" \ - -f "${private_key_path}" \ - &> /dev/null - - if [[ -x /sbin/restorecon ]] - then - /sbin/restorecon "${public_key_path}" - fi - - ;; - *) - printf -- \ - 'Unknown key type %s - skipping.\n' \ - "${type}" - ;; - esac + return 1 } -function __generate_ssh_host_keys () +function __is_valid_ssh_user_password_hashed () { - local -r replace="${1:-false}" - - local version="${2}" - - if [[ -z ${version} ]] \ - && [[ -e /etc/redhat-release ]] - then - version="$( - rpm -q \ - --whatprovides redhat-release \ - --queryformat "%{VERSION}" - )" - else - printf -- \ - 'ERROR: Unknown EL release.\n' \ - >&2 - sleep 0.1 + local -r boolean_value='^(true|false)$' + local -r value="${1}" - return 1 + if [[ ${value} =~ ${boolean_value} ]] + then + return 0 fi - case "${version}" in - 6) - __generate_ssh_host_key "${replace}" rsa1 - __generate_ssh_host_key "${replace}" rsa - __generate_ssh_host_key "${replace}" dsa - ;; - 7) - __generate_ssh_host_key "${replace}" rsa1 - __generate_ssh_host_key "${replace}" rsa - __generate_ssh_host_key "${replace}" dsa - __generate_ssh_host_key "${replace}" ecdsa - __generate_ssh_host_key "${replace}" ed25519 - ;; - *) - printf -- \ - 'Unknown EL release %s - skipping.\n' \ - "${version}" - ;; - esac + return 1 } -function __set_ssh_timezone () +function __is_valid_ssh_user_shell () { - local -r zone="${1:-UTC}" + local -r shell="${1}" + local -r valid_shells="$( + chsh --list-shells + )" - if ! __is_valid_ssh_timezone "${zone}" - then - printf -- \ - 'ERROR: Unknown time zone: %s\n' \ - "${zone}" \ - >&2 - sleep 0.1 + local valid_shell + if [[ -z ${shell} ]] + then return 1 - else - if [[ -f /etc/sysconfig/clock ]] + fi + + for valid_shell in ${valid_shells} + do + if [[ ${valid_shell} == "${shell}" ]] then - sed -i \ - -e "s~^\(ZONE=\).*$~\1${zone}~" \ - /etc/sysconfig/clock + return 0 fi + done - ln -sf \ - /usr/share/zoneinfo/"${zone}" \ - /etc/localtime - - return "${?}" - fi + return 1 } function __set_ssh_user_password () @@ -886,22 +1181,16 @@ function __set_ssh_user_password () if ! __is_valid_ssh_user "${user}" then - printf -- \ - 'ERROR: Invalid user\n' \ - >&2 - sleep 0.1 - + >&2 printf -- \ + 'ERROR: Invalid user\n' return 1 else if [[ ${password_hashed} == true ]] then if ! __is_valid_ssh_user_password_hash "${password}" then - printf -- \ - 'ERROR: Invalid password - requires SHA-512 hash\n' \ - >&2 - sleep 0.1 - + >&2 printf -- \ + 'ERROR: Invalid password - requires SHA-512 hash\n' return 1 else printf -- \ @@ -915,11 +1204,8 @@ function __set_ssh_user_password () else if [[ -z ${password} ]] then - printf -- \ - 'ERROR: Invalid password - empty\n' \ - >&2 - sleep 0.1 - + >&2 printf -- \ + 'ERROR: Invalid password - empty\n' return 1 else printf -- \ @@ -938,10 +1224,11 @@ function main () { local -r env_exclude_pattern="^([.]*SSH_USER_PASSWORD|_|HOME|HOSTNAME|PATH|PWD|SHLVL|SUPERVISOR_ENABLED|SUPERVISOR_GROUP_NAME|SUPERVISOR_PROCESS_NAME|TERM)=" local -r lock_file="/var/lock/subsys/sshd-bootstrap" - local -r password_length=16 + local -r password_length="16" local -r redacted_value="********" + local -r state_file="/var/lib/misc/sshd-bootstrap" local -r timer_start="$( - date +%s.%N + date -u +%s.%N )" local env @@ -949,7 +1236,6 @@ function main () [SSH_CHROOT_DIRECTORY]=__is_valid_ssh_chroot_directory [SSH_PASSWORD_AUTHENTICATION]=__is_valid_ssh_password_authentication [SSH_SUDO]=__is_valid_ssh_sudo - [SSH_TIMEZONE]=__is_valid_ssh_timezone [SSH_USER]=__is_valid_ssh_user [SSH_USER_FORCE_SFTP]=__is_valid_ssh_user_force_sftp [SSH_USER_HOME]=__is_valid_ssh_user_home @@ -968,7 +1254,6 @@ function main () local ssh_key_fingerprints local ssh_password_authentication local ssh_sudo - local ssh_timezone local ssh_user local ssh_user_force_sftp local ssh_user_groups="users,wheel" @@ -983,444 +1268,434 @@ function main () local sshd_command="SSH" local timer_total - # Create lock - touch \ - "${lock_file}" - - # Parse options while [[ "${#}" -gt 0 ]] do case "${1}" in -v|--verbose) - verbose=true + verbose="true" shift 1 ;; esac done - ssh_user_home="$( - __get_ssh_user_home - )" - - if [[ ! -d ${ssh_user_home}/.ssh ]] + if [[ -e ${state_file} ]] then - ssh_authorized_keys="$( - __get_ssh_authorized_keys - )" - ssh_inherit_environment="$( - __get_ssh_inherit_environment - )" - ssh_password_authentication="$( - __get_ssh_password_authentication - )" - ssh_sudo="$( - __get_ssh_sudo - )" - ssh_timezone="$( - __get_ssh_timezone - )" - ssh_user="$( - __get_ssh_user - )" - ssh_user_force_sftp="$( - __get_ssh_user_force_sftp - )" - ssh_user_password_hashed="$( - __get_ssh_user_password_hashed - )" - ssh_user_password="${SSH_USER_PASSWORD:-"$( - __get_password "${password_length}" - )"}" - ssh_user_private_key="$( - __get_ssh_user_private_key - )" - ssh_user_shell="$( - __get_ssh_user_shell - )" - ssh_user_uid="$( - __get_ssh_user_uid - )" - ssh_user_gid="$( - __get_ssh_user_gid - )" - - if [[ ${ssh_inherit_environment} == true ]] + if [[ ${verbose} == true ]] then - env \ - | grep -Ev "${env_exclude_pattern}" \ - > /etc/environment + printf -- \ + 'INFO: %s finished - skipping.\n' \ + "${0##*/}" fi + exit 0 + fi - if [[ ${ssh_password_authentication} == true ]] - then - password_authentication="yes" - - if [[ ${ssh_user} == root ]] - then - sed -i \ - -e 's~^\(PasswordAuthentication \)no$~\1yes~g' \ - -e 's~^\(PermitRootLogin \)no$~\1yes~g' \ - /etc/ssh/sshd_config - else - sed -i \ - -e 's~^\(PasswordAuthentication \)no$~\1yes~g' \ - /etc/ssh/sshd_config - fi - elif [[ ${ssh_user} == root ]] - then - sed -i \ - -e 's~^\(PermitRootLogin \)no$~\1without-password~g' \ - /etc/ssh/sshd_config - fi + if [[ -e ${lock_file} ]] + then + >&2 printf -- \ + 'ERROR: %s lock detected - aborting.\n' \ + "${0##*/}" + exit 1 + fi - # Warn operator if any supplied environment variable values failed - # validation and have been set to a safe default. - if [[ ${verbose} == true ]] - then - for env in "${!env_validation_with_defaults[@]}" - do - if ! ${env_validation_with_defaults[${env}]} "${!env}" - then - printf -- \ - 'WARNING: Validation failed on %s - setting to default.\n' \ - "${env}" - fi - done - fi + trap "__cleanup" \ + EXIT INT TERM + __create_lock - if ! __set_ssh_timezone "${ssh_timezone}" - then - printf -- \ - 'ERROR: Could not set timezone - aborting.\n' \ - >&2 - sleep 0.1 + ssh_authorized_keys="$( + __get_ssh_authorized_keys + )" + ssh_inherit_environment="$( + __get_ssh_inherit_environment + )" + ssh_password_authentication="$( + __get_ssh_password_authentication + )" + ssh_sudo="$( + __get_ssh_sudo + )" + ssh_user="$( + __get_ssh_user + )" + ssh_user_force_sftp="$( + __get_ssh_user_force_sftp + )" + ssh_user_home="$( + __get_ssh_user_home + )" + ssh_user_password_hashed="$( + __get_ssh_user_password_hashed + )" + ssh_user_password="${SSH_USER_PASSWORD:-"$( + __get_password "${password_length}" + )"}" + ssh_user_private_key="$( + __get_ssh_user_private_key + )" + ssh_user_shell="$( + __get_ssh_user_shell + )" + ssh_user_uid="$( + __get_ssh_user_uid + )" + ssh_user_gid="$( + __get_ssh_user_gid + )" - exit 1 - fi + if [[ ${ssh_inherit_environment} == true ]] + then + env \ + | grep -Ev "${env_exclude_pattern}" \ + > /etc/environment + fi - $( - __generate_ssh_host_keys - ) & - pids[0]="${!}" + if [[ ${ssh_password_authentication} == true ]] + then + password_authentication="yes" if [[ ${ssh_user} == root ]] then - chsh \ - -s "${ssh_user_shell}" \ - "${ssh_user}" \ - &> /dev/null + sed -i \ + -e 's~^\(PasswordAuthentication \)no$~\1yes~g' \ + -e 's~^\(PermitRootLogin \)no$~\1yes~g' \ + /etc/ssh/sshd_config else - # Create base directory for home - if [[ -n ${ssh_user_home%/*} ]] \ - && [[ ! -d ${ssh_user_home%/*} ]] + sed -i \ + -e 's~^\(PasswordAuthentication \)no$~\1yes~g' \ + /etc/ssh/sshd_config + fi + elif [[ ${ssh_user} == root ]] + then + sed -i \ + -e 's~^\(PermitRootLogin \)no$~\1without-password~g' \ + /etc/ssh/sshd_config + fi + + # Warn operator if any supplied environment variable values failed + # validation and have been set to a safe default. + if [[ ${verbose} == true ]] + then + for env in "${!env_validation_with_defaults[@]}" + do + if ! ${env_validation_with_defaults[${env}]} "${!env}" then - mkdir -pm 755 \ - "${ssh_user_home%/*}" + printf -- \ + 'WARNING: Validation failed on %s - setting to default.\n' \ + "${env}" fi + done + fi - groupadd \ - -f \ - -g "${ssh_user_gid}" \ - "${ssh_user}" - - useradd \ - -u "${ssh_user_uid}" \ - -g "${ssh_user_gid}" \ - -m \ - -G "${ssh_user_groups}" \ - -d "${ssh_user_home}" \ - -s "${ssh_user_shell}" \ - "${ssh_user}" - - # Set root user password - $( - printf -- \ - '%s:%s\n' \ - "root" \ - "$( - __get_password "${password_length}" - )" \ - | chpasswd - ) & - pids[1]="${!}" - fi + $( + __generate_ssh_host_keys + ) & + pids[0]="${!}" - if ! __set_ssh_user_password "${ssh_user_password}" + if [[ ${ssh_user} == root ]] + then + chsh \ + -s "${ssh_user_shell}" \ + "${ssh_user}" \ + &> /dev/null + else + # Create base directory for home + if [[ -n ${ssh_user_home%/*} ]] \ + && [[ ! -d ${ssh_user_home%/*} ]] then - printf -- \ - 'ERROR: Could not set password - aborting.\n' \ - >&2 - sleep 0.1 - - exit 1 + mkdir -pm 755 \ + "${ssh_user_home%/*}" fi - if [[ ${ssh_user_force_sftp} == true ]] - then - sshd_command="SFTP" - ssh_sudo="N/A" - ssh_user_groups="users" - ssh_chroot_directory="$( - __get_ssh_chroot_directory - )" - ssh_chroot_directory_path="$( - __get_ssh_chroot_directory_path "${ssh_chroot_directory}" - )" - - if [[ ! -d ${ssh_chroot_directory_path} ]] \ - || [[ ${ssh_chroot_directory_path} != "${ssh_user_home}" ]] - then - # ChrootDirectory like /chroot/%u or /home/chroot/%u - printf -v ssh_chroot_home_directory_path \ - -- \ - '%s%s' \ - "${ssh_chroot_directory_path}" \ - "${ssh_user_home}" - mkdir -pm 711 \ - "${ssh_chroot_directory_path}" - mkdir -pm 755 \ - "${ssh_chroot_home_directory_path}" - else - # ChrootDirectory %h - ssh_chroot_home_directory_path="${ssh_user_home}" - chmod 750 \ - "${ssh_chroot_home_directory_path}" - fi + groupadd \ + -f \ + -g "${ssh_user_gid}" \ + "${ssh_user}" + + useradd \ + -u "${ssh_user_uid}" \ + -g "${ssh_user_gid}" \ + -m \ + -G "${ssh_user_groups}" \ + -d "${ssh_user_home}" \ + -s "${ssh_user_shell}" \ + "${ssh_user}" + + # Set root user password + $( + printf -- \ + '%s:%s\n' \ + "root" \ + "$( + __get_password "${password_length}" + )" \ + | chpasswd + ) & + pids[1]="${!}" + fi - # Create a user writeable data directory if no other directories - # are mounted. - if ! grep -q '^d' <<< "$( - ls -l "${ssh_chroot_home_directory_path}"/ - )" - then - # Make and set user permissions on new _data directory - mkdir -m 700 \ - "${ssh_chroot_home_directory_path}"/_data - chown -R \ - "${ssh_user}":"${ssh_user}" \ - "${ssh_chroot_home_directory_path}"/_data - elif [[ -d ${ssh_chroot_home_directory_path}/_data ]] - then - # Set user permissions on _data directory where it exists - chmod 700 \ - "${ssh_chroot_home_directory_path}"/_data - chown -R \ - "${ssh_user}":"${ssh_user}" \ - "${ssh_chroot_home_directory_path}"/_data - fi + if ! __set_ssh_user_password "${ssh_user_password}" + then + >&2 printf -- \ + 'ERROR: Could not set password - aborting.\n' + exit 1 + fi - # ChrootDirectory must be owned by root user - if [[ ${ssh_chroot_directory_path} != "${ssh_user_home}" ]] - then - chown \ - root:root \ - "${ssh_chroot_directory_path}" - chmod 711 \ - "${ssh_chroot_directory_path}" - else - chown \ - root:"${ssh_user}" \ - "${ssh_chroot_directory_path}" - fi + if [[ ${ssh_user_force_sftp} == true ]] + then + sshd_command="SFTP" + ssh_sudo="N/A" + ssh_user_groups="users" + ssh_chroot_directory="$( + __get_ssh_chroot_directory + )" + ssh_chroot_directory_path="$( + __get_ssh_chroot_directory_path "${ssh_chroot_directory}" + )" - # Add user specific sshd configuration - tee -a /etc/ssh/sshd_config > /dev/null <<-EOT - # Force SFTP - Match User ${ssh_user} - AllowTcpForwarding no - X11Forwarding no - ChrootDirectory ${ssh_chroot_directory} - ForceCommand internal-sftp - EOT + if [[ ! -d ${ssh_chroot_directory_path} ]] \ + || [[ ${ssh_chroot_directory_path} != "${ssh_user_home}" ]] + then + # ChrootDirectory like /chroot/%u or /home/chroot/%u + printf -v ssh_chroot_home_directory_path \ + -- \ + '%s%s' \ + "${ssh_chroot_directory_path}" \ + "${ssh_user_home}" + mkdir -pm 711 \ + "${ssh_chroot_directory_path}" + mkdir -pm 755 \ + "${ssh_chroot_home_directory_path}" else - sed -i \ - -e '/# Force SFTP/,/ForceCommand internal-sftp/ { d; }' \ - /etc/ssh/sshd_config + # ChrootDirectory %h + ssh_chroot_home_directory_path="${ssh_user_home}" + chmod 750 \ + "${ssh_chroot_home_directory_path}" fi - # SSH require files - mkdir -m 700 \ - "${ssh_user_home}"/.ssh - touch \ - "${ssh_user_home}"/.ssh/authorized_keys - chown -R \ - "${ssh_user}":"${ssh_user}" \ - "${ssh_user_home}"/.ssh - chmod 600 \ - "${ssh_user_home}"/.ssh/authorized_keys - - # Details output for SSH public key fingerprints - if [[ ${ssh_password_authentication} == true ]] \ - && [[ -z ${SSH_AUTHORIZED_KEYS} ]] + # Create a user writeable data directory if no other directories + # are mounted. + if ! grep -q '^d' <<< "$( + ls -l "${ssh_chroot_home_directory_path}"/ + )" then - if [[ ${verbose} == true ]] - then - ssh_key_fingerprints="N/A" - fi - elif ! __is_valid_ssh_authorized_keys "${ssh_authorized_keys}" + # Make and set user permissions on new _data directory + mkdir -m 700 \ + "${ssh_chroot_home_directory_path}"/_data + chown -R \ + "${ssh_user}":"${ssh_user}" \ + "${ssh_chroot_home_directory_path}"/_data + elif [[ -d ${ssh_chroot_home_directory_path}/_data ]] then - if [[ ${verbose} == true ]] - then - printf -v ssh_key_fingerprints \ - -- \ - '%s\nUnable to populate %s/.ssh/authorized_key' \ - "ERROR: Public key validation failed." \ - "${ssh_user_home}" - fi - else - printf -- \ - '%s\n' \ - "${ssh_authorized_keys}" \ - > "${ssh_user_home}"/.ssh/authorized_keys - - if [[ ${verbose} == true ]] - then - ssh_key_fingerprints="$( - __get_ssh_authorized_key_fingerprints - )" - fi + # Set user permissions on _data directory where it exists + chmod 700 \ + "${ssh_chroot_home_directory_path}"/_data + chown -R \ + "${ssh_user}":"${ssh_user}" \ + "${ssh_chroot_home_directory_path}"/_data fi - # Details output for SSH private key fingerprint - if [[ -z ${ssh_user_private_key} ]] \ - || [[ ${ssh_user_force_sftp} == true ]] - then - if [[ ${verbose} == true ]] - then - ssh_user_private_key_fingerprint="N/A" - fi - elif ! __is_valid_ssh_key "${ssh_user_private_key}" + # ChrootDirectory must be owned by root user + if [[ ${ssh_chroot_directory_path} != "${ssh_user_home}" ]] then - if [[ ${verbose} == true ]] - then - printf -v ssh_user_private_key_fingerprint \ - -- \ - '%s\nUnable to populate %s/.ssh/id_rsa' \ - "ERROR: Private key validation failed." \ - "${ssh_user_home}" - fi + chown \ + root:root \ + "${ssh_chroot_directory_path}" + chmod 711 \ + "${ssh_chroot_directory_path}" else - printf -- \ - '%s\n' \ - "${ssh_user_private_key}" \ - > "${ssh_user_home}"/.ssh/id_rsa chown \ - "${ssh_user}":"${ssh_user}" \ - "${ssh_user_home}"/.ssh/id_rsa - chmod 600 \ - "${ssh_user_home}"/.ssh/id_rsa - - if [[ ${verbose} == true ]] - then - ssh_user_private_key_fingerprint="$( - __get_ssh_key_fingerprint_hash_output \ - "${ssh_user_private_key}" - )" - fi + root:"${ssh_user}" \ + "${ssh_chroot_directory_path}" fi - # Set sudo access for the wheel group - sed -i \ - -e "s~^%wheel\\t.*$~%wheel\\t${ssh_sudo}~g" \ - /etc/sudoers - - tee -a /etc/sudoers > /dev/null <<-EOT - - # ${ssh_user} - Defaults:root secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin + # Add user specific sshd configuration + tee -a /etc/ssh/sshd_config > /dev/null <<-EOT + # Force SFTP + Match User ${ssh_user} + AllowTcpForwarding no + X11Forwarding no + ChrootDirectory ${ssh_chroot_directory} + ForceCommand internal-sftp EOT + else + sed -i \ + -e '/# Force SFTP/,/ForceCommand internal-sftp/ { d; }' \ + /etc/ssh/sshd_config + fi - # Wait for background processes - Host key generation - wait ${pids[0]} - + # SSH require files + mkdir -m 700 \ + "${ssh_user_home}"/.ssh + touch \ + "${ssh_user_home}"/.ssh/authorized_keys + chown -R \ + "${ssh_user}":"${ssh_user}" \ + "${ssh_user_home}"/.ssh + chmod 600 \ + "${ssh_user_home}"/.ssh/authorized_keys + + # Details output for SSH public key fingerprints + if [[ ${ssh_password_authentication} == true ]] \ + && [[ -z ${SSH_AUTHORIZED_KEYS} ]] + then if [[ ${verbose} == true ]] then - ssh_host_key_fingerprint_rsa="$( - __get_ssh_host_key_fingerprint rsa - )" + ssh_key_fingerprints="N/A" fi - - # Wait for background processes - Set password for root user - if [[ -n ${pids[1]+isset} ]] + elif ! __is_valid_ssh_authorized_keys "${ssh_authorized_keys}" + then + if [[ ${verbose} == true ]] then - wait ${pids[1]} + printf -v ssh_key_fingerprints \ + -- \ + '%s\nUnable to populate %s/.ssh/authorized_key' \ + "ERROR: Public key validation failed." \ + "${ssh_user_home}" fi + else + printf -- \ + '%s\n' \ + "${ssh_authorized_keys}" \ + > "${ssh_user_home}"/.ssh/authorized_keys - # Only show user password if auto-generated and password is required - # for password authentication or sudo. - if [[ -n ${SSH_USER_PASSWORD} ]] - then - ssh_user_password="${redacted_value}" - elif [[ ${ssh_password_authentication} == false ]] \ - && [[ ${ssh_user_force_sftp} == true ]] + if [[ ${verbose} == true ]] then - ssh_user_password="${redacted_value}" - elif [[ ${ssh_password_authentication} == false ]] \ - && [[ ${ssh_user} != root ]] \ - && __is_sudo_no_password_all "${ssh_sudo}" + ssh_key_fingerprints="$( + __get_ssh_authorized_key_fingerprints + )" + fi + fi + + # Details output for SSH private key fingerprint + if [[ -z ${ssh_user_private_key} ]] \ + || [[ ${ssh_user_force_sftp} == true ]] + then + if [[ ${verbose} == true ]] then - ssh_user_password="${redacted_value}" - elif [[ ${ssh_password_authentication} == false ]] \ - && [[ ${ssh_user} == root ]] + ssh_user_private_key_fingerprint="N/A" + fi + elif ! __is_valid_ssh_key "${ssh_user_private_key}" + then + if [[ ${verbose} == true ]] then - ssh_user_password="${redacted_value}" + printf -v ssh_user_private_key_fingerprint \ + -- \ + '%s\nUnable to populate %s/.ssh/id_rsa' \ + "ERROR: Private key validation failed." \ + "${ssh_user_home}" fi + else + printf -- \ + '%s\n' \ + "${ssh_user_private_key}" \ + > "${ssh_user_home}"/.ssh/id_rsa + chown \ + "${ssh_user}":"${ssh_user}" \ + "${ssh_user_home}"/.ssh/id_rsa + chmod 600 \ + "${ssh_user_home}"/.ssh/id_rsa if [[ ${verbose} == true ]] then - timer_total="$( - awk \ - -v timer_end="$( - date +%s.%N - )" \ - -v timer_start="${timer_start}" \ - 'BEGIN { print \ - timer_end - timer_start; - }' + ssh_user_private_key_fingerprint="$( + __get_ssh_key_fingerprint_hash_output \ + "${ssh_user_private_key}" )" + fi + fi - cat <<-EOT - - ================================================================================ - ${sshd_command} Details - -------------------------------------------------------------------------------- - user : ${ssh_user} - password : ${ssh_user_password} - password authentication : ${password_authentication} - id : ${ssh_user_uid}:${ssh_user_gid} - home : ${ssh_user_home} - chroot path : ${ssh_chroot_directory_path} - shell : ${ssh_user_shell} - sudo : ${ssh_sudo} - key fingerprints : - ${ssh_key_fingerprints} - rsa private key fingerprint : - ${ssh_user_private_key_fingerprint} - rsa host key fingerprint : - ${ssh_host_key_fingerprint_rsa} - timezone : ${ssh_timezone} - -------------------------------------------------------------------------------- - ${timer_total} - - EOT - elif [[ ${ssh_user_password} != "${redacted_value}" ]] - then - # Mininal ouput when required - cat <<-EOT + # Set sudo access for the wheel group + sed -i \ + -e "s~^%wheel\\t.*$~%wheel\\t${ssh_sudo}~g" \ + /etc/sudoers - ================================================================================ - ${sshd_command} Details - -------------------------------------------------------------------------------- - password : ${ssh_user_password} - -------------------------------------------------------------------------------- + tee -a /etc/sudoers > /dev/null <<-EOT - EOT - fi + # ${ssh_user} + Defaults:root secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin + EOT + + # Wait for background processes - Host key generation + wait ${pids[0]} + + if [[ ${verbose} == true ]] + then + ssh_host_key_fingerprint_rsa="$( + __get_ssh_host_key_fingerprint rsa + )" fi - # Release lock - rm -f \ - "${lock_file}" + # Wait for background processes - Set password for root user + if [[ -n ${pids[1]+isset} ]] + then + wait ${pids[1]} + fi + + # Only show user password if auto-generated and password is required + # for password authentication or sudo. + if [[ -n ${SSH_USER_PASSWORD} ]] + then + ssh_user_password="${redacted_value}" + elif [[ ${ssh_password_authentication} == false ]] \ + && [[ ${ssh_user_force_sftp} == true ]] + then + ssh_user_password="${redacted_value}" + elif [[ ${ssh_password_authentication} == false ]] \ + && [[ ${ssh_user} != root ]] \ + && __is_sudo_no_password_all "${ssh_sudo}" + then + ssh_user_password="${redacted_value}" + elif [[ ${ssh_password_authentication} == false ]] \ + && [[ ${ssh_user} == root ]] + then + ssh_user_password="${redacted_value}" + fi + + if [[ ${verbose} == true ]] + then + timer_total="$( + __get_timer_total \ + "${timer_start}" + )" + + cat <<-EOT + + ================================================================================ + ${sshd_command} Details + -------------------------------------------------------------------------------- + chroot path : ${ssh_chroot_directory_path} + home : ${ssh_user_home} + id : ${ssh_user_uid}:${ssh_user_gid} + key fingerprints : + ${ssh_key_fingerprints} + password : ${ssh_user_password} + password authentication : ${password_authentication} + rsa private key fingerprint : + ${ssh_user_private_key_fingerprint} + rsa host key fingerprint : + ${ssh_host_key_fingerprint_rsa} + shell : ${ssh_user_shell} + sudo : ${ssh_sudo} + user : ${ssh_user} + -------------------------------------------------------------------------------- + ${timer_total} + + EOT + elif [[ ${ssh_user_password} != "${redacted_value}" ]] + then + # Mininal ouput when required + cat <<-EOT + + ================================================================================ + ${sshd_command} Details + -------------------------------------------------------------------------------- + password : ${ssh_user_password} + -------------------------------------------------------------------------------- + + EOT + fi + + # Trigger cleanup trap. + exit 0 } main "${@}" diff --git a/src/usr/sbin/sshd-wrapper b/src/usr/sbin/sshd-wrapper index f128f8f..6f6c815 100755 --- a/src/usr/sbin/sshd-wrapper +++ b/src/usr/sbin/sshd-wrapper @@ -2,58 +2,116 @@ set -e -function __is_valid_ssh_autostart_sshd_bootstrap () +function __cleanup () { - local -r boolean_value='^(true|false)$' - local -r value="${1}" + __delete_lock +} - if [[ ${value} =~ ${boolean_value} ]] +function __create_lock () +{ + if [[ -n ${lock_file} ]] then - return 0 + touch "${lock_file}" fi - - return 1 } -function __get_ssh_autostart_sshd_bootstrap () +function __delete_lock () { - local -r default_value="${1:-true}" - - local value="${SSH_AUTOSTART_SSHD_BOOTSTRAP}" - - if ! __is_valid_ssh_autostart_sshd_bootstrap "${value}" + if [[ -e ${lock_file} ]] then - value="${default_value}" + rm -f "${lock_file}" fi +} - printf -- '%s' "${value}" +function __get_options () +{ + local -r options="${1}" + + printf -- \ + '-D -e -u 0%s%s' \ + "${options:+" "}" \ + "${options}" } function main () { - local -r autostart_bootstrap="$( - __get_ssh_autostart_sshd_bootstrap - )" local -r bin="/usr/sbin/sshd" - local -r lock_file="/var/lock/subsys/sshd-bootstrap" + local -r bootstrap_state_file="/var/lib/misc/sshd-bootstrap" + local -r bootstrap_timeout="4" + local -r lock_file="/var/lock/subsys/sshd-wrapper" local -r nice="/bin/nice" local -r niceness="10" - local -r options="-D -e -u 0" - if [[ ${autostart_bootstrap} == false ]] + local options + local verbose="false" + + while [[ "${#}" -gt 0 ]] + do + case "${1}" in + -v|--verbose) + verbose="true" + shift 1 + ;; + esac + done + + if [[ -e ${lock_file} ]] then - # block. - sleep infinity + >&2 printf -- \ + 'ERROR: %s lock detected - aborting\n' \ + "${0##*/}" + exit 1 fi - while true + trap __cleanup \ + EXIT INT TERM + __create_lock + + options="$( + __get_options + )" + + if [[ ${verbose} == true ]] + then + printf -- \ + 'INFO: %s waiting on %s\n' \ + "${0##*/}" \ + "${bootstrap_state_file##*/}" + fi + + set +e + until [[ -e ${bootstrap_state_file} ]] do - sleep 0.1 - if [[ ! -e ${lock_file} ]] + if ! inotifywait -qq \ + -e "create" \ + -t "${bootstrap_timeout}" \ + "${bootstrap_state_file%/*}" then break fi done + set -e + + if ! [[ -e ${bootstrap_state_file} ]] + then + >&2 printf -- \ + 'ERROR: %s timed out waiting on %s\n' \ + "${0##*/}" \ + "${bootstrap_state_file##*/}" + exit 1 + fi + + if [[ ${verbose} == true ]] + then + printf -- \ + 'INFO: %s starting %s\n' \ + "${0##*/}" \ + "${bin##*/}" + fi + + __cleanup + trap - \ + EXIT INT TERM exec ${nice} \ -n ${niceness} \ diff --git a/src/usr/sbin/system-timezone b/src/usr/sbin/system-timezone new file mode 100644 index 0000000..2cd686f --- /dev/null +++ b/src/usr/sbin/system-timezone @@ -0,0 +1,268 @@ +#!/usr/bin/env bash + +set -e + +function __cleanup () +{ + local -r exit_code="${?}" + + __delete_lock +} + +function __create_lock () +{ + if [[ -n ${lock_file} ]] + then + touch "${lock_file}" + fi +} + +function __delete_lock () +{ + if [[ -f ${lock_file} ]] + then + rm -f "${lock_file}" + fi +} + +function __get_timezone () +{ + local -r link_name="/etc/localtime" + + local localtime="$( + readlink -f "${link_name}" + )" + local zone="${localtime/"/usr/share/zoneinfo/"/}" + + if [[ ! -h ${link_name} ]] \ + || ! __is_valid_zone "${zone}" + then + __print_message \ + "error" \ + "corrupt/invalid symbolic link: ${link_name}" + exit 1 + fi + + printf -- \ + '%s\n' \ + "${zone}" +} + +function __is_valid_zone () +{ + local zone="${1}" + + if [[ -n ${zone} ]] \ + && [[ -f /usr/share/zoneinfo/${zone} ]] + then + return 0 + fi + + return 1 +} + +function __print_message () +{ + local -r type="${1}" + + local message="${2}" + local prefix="" + local quiet="${quiet:-false}" + local silent="${silent:-false}" + + case "${type}" in + error) + prefix="ERROR: " + ;; + info) + prefix="INFO: " + ;; + *) + message="${type}" + ;; + esac + + if [[ ${quiet} == true ]] \ + || [[ ${silent} == true ]] \ + && [[ ${type} != error ]] + then + return 0 + elif [[ ${silent} == true ]] \ + && [[ ${type} == error ]] + then + return 1 + elif [[ ${type} == error ]] + then + >&2 printf -- \ + '%s%s\n' \ + "${prefix}" \ + "${message}" + else + printf -- \ + '%s%s\n' \ + "${prefix}" \ + "${message}" + fi +} + +function __set_timezone () +{ + local zone="${1}" + + if ! __is_valid_zone "${zone}" + then + return 1 + fi + + if [[ -f /etc/sysconfig/clock ]] + then + sed -r -i \ + -e "s~^(ZONE=).*$~\1${zone}~" \ + /etc/sysconfig/clock + fi + + ln -sf \ + /usr/share/zoneinfo/"${zone}" \ + /etc/localtime +} + +function __usage () +{ + local help="${help:-false}" + local quiet="${quiet:-false}" + local silent="${silent:-false}" + + if [[ ${silent} != true ]] \ + || [[ ${help} == true ]] + then + cat <<-USAGE + + Usage: ${0##*/} [OPTIONS] + ${0##*/} [-h|--help] + + ${0##*/} is a utility to manage the system time zone. + It will output the current system time zone if run without options. + + Options: + -h, --help Show this help and exit. + -q, --quiet Do not print information message output. + -qq, --silent Do not print error message output. + -z, --zone ZONE Set the system time zone. e.g: + 'UTC', 'Europe/London' etc. + + USAGE + fi + + if [[ ${help} != true ]] + then + exit 1 + fi + + exit 0 +} + +function main () +{ + local -r lock_file="/var/lock/subsys/system-timezone" + + local help + local quiet="false" + local silent="false" + local system_zone + local zone + + if [[ ${EUID} -ne 0 ]] + then + __print_message \ + "error" \ + "${0##*/} must be run as root." + exit 1 + fi + + while [[ "${#}" -gt 0 ]] + do + case "${1}" in + -h|--help) + help="true" + __usage + break + ;; + -z|--zone) + zone="${2}" + if [[ -z ${zone} ]] + then + __usage + fi + shift 2 || break + ;; + --zone=*) + zone="${1#*=}" + if [[ -z ${zone} ]] + then + __usage + fi + shift 1 + ;; + -q|--quiet) + quiet="true" + shift 1 + ;; + -qq|--silent) + quiet="true" + silent="true" + shift 1 + ;; + *) + __usage + ;; + esac + done + + if [[ -f ${lock_file} ]] + then + __print_message \ + "error" \ + "${0##*/} lock detected - aborting." + exit 1 + fi + + trap "__cleanup" \ + EXIT INT TERM + __create_lock + + if [[ -z ${zone} ]] + then + __get_timezone + exit 0 + fi + + if ! __is_valid_zone "${zone}" + then + __print_message \ + "error" \ + "${0##*/} invalid zone: ${zone}" + exit 1 + else + __print_message \ + "info" \ + "${0##*/} setting zone: ${zone}" + + __set_timezone "${zone}" + + system_zone="$( + __get_timezone + )" + + if [[ ${system_zone} != "${zone}" ]] + then + __print_message \ + "error" \ + "${0##*/} failed setting zone: ${zone}" + exit 1 + fi + fi + + exit 0 +} + +main "${@}" diff --git a/src/usr/sbin/system-timezone-wrapper b/src/usr/sbin/system-timezone-wrapper new file mode 100644 index 0000000..02bc694 --- /dev/null +++ b/src/usr/sbin/system-timezone-wrapper @@ -0,0 +1,205 @@ +#!/usr/bin/env bash + +set -e + +function __cleanup () +{ + local -r exit_code="${?}" + + __delete_lock + + if [[ ${exit_code} -eq 0 ]] + then + __create_state + fi +} + +function __create_lock () +{ + if [[ -n ${lock_file} ]] + then + touch "${lock_file}" + fi +} + +function __create_state () +{ + if [[ -n ${state_file} ]] + then + touch "${state_file}" + fi +} + +function __delete_lock () +{ + if [[ -f ${lock_file} ]] + then + rm -f "${lock_file}" + fi +} + +function __get_system_timezone () +{ + local -r default_value="${1:-UTC}" + + local value="${SYSTEM_TIMEZONE}" + + if ! __is_valid_system_timezone "${value}" + then + value="${default_value}" + fi + + printf -- '%s' "${value}" +} + +function __get_options () +{ + local -r zone="${1:-"$( + __get_system_timezone + )"}" + + printf -- \ + '--silent --zone %s' \ + "${zone}" +} + +function __get_timer_total () +{ + local -r timer_end="$( + date -u +%s.%N + )" + local -r timer_start="${1}" + + if [[ -z ${timer_start} ]] \ + || [[ ${timer_start//.} -gt ${timer_end//.} ]] + then + >&2 printf -- \ + 'ERROR: invalid timer start: %s\n' \ + "${timer_start}" + printf -- \ + '0.000000' + else + awk \ + -v timer_end="${timer_end}" \ + -v timer_start="${timer_start}" \ + 'BEGIN { print \ + timer_end - timer_start; + }' + fi +} + +function __is_valid_system_timezone () +{ + __is_valid_zone "${@}" +} + +function __is_valid_zone () +{ + local zone="${1}" + + if [[ -n ${zone} ]] \ + && [[ -f /usr/share/zoneinfo/${zone} ]] + then + return 0 + fi + + return 1 +} + +function main () +{ + local -r bin="/usr/sbin/system-timezone" + local -r lock_file="/var/lock/subsys/system-timezone-wrapper" + local -r state_file="/var/lib/misc/system-timezone-wrapper" + local -r timer_start="$( + date -u +%s.%N + )" + + local system_timezone + local options + local timer_total + local verbose="false" + + while [[ "${#}" -gt 0 ]] + do + case "${1}" in + -v|--verbose) + verbose="true" + shift 1 + ;; + esac + done + + if [[ -f ${state_file} ]] + then + if [[ ${verbose} == true ]] + then + printf -- \ + 'INFO: %s finished - skipping.\n' \ + "${0##*/}" + fi + exit 0 + fi + + if [[ -f ${lock_file} ]] + then + >&2 printf -- \ + 'ERROR: %s lock detected - aborting.\n' \ + "${0##*/}" + exit 1 + fi + + trap "__cleanup" \ + EXIT INT TERM + __create_lock + + if [[ ${verbose} == true ]] + then + system_timezone="$( + __get_system_timezone + )" + + options="$( + __get_options \ + "${system_timezone}" + )" + + timer_total="$( + __get_timer_total \ + "${timer_start}" + )" + else + options="$( + __get_options + )" + fi + + if ! ${bin} \ + ${options} + then + >&2 printf -- \ + 'ERROR: %s failed setting zone: %s\n' \ + "${bin##*/}" \ + "${system_timezone}" + exit 1 + fi + + if [[ ${verbose} == true ]] + then + cat \ + <<-EOT + + ================================================================================ + System Time Zone Details + -------------------------------------------------------------------------------- + timezone : ${system_timezone} + -------------------------------------------------------------------------------- + ${timer_total} + + EOT + fi + + exit 0 +} + +main "${@}" diff --git a/test/shpec/operation_shpec.sh b/test/shpec/operation_shpec.sh index 56ecf5b..586324c 100644 --- a/test/shpec/operation_shpec.sh +++ b/test/shpec/operation_shpec.sh @@ -177,7 +177,7 @@ function test_basic_ssh_operations () describe "SSH user's password" password="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^password :.*$/ { print $3; }' )" @@ -430,7 +430,7 @@ function test_custom_ssh_configuration () it "Can connect using password authentication." password="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^password :.*$/ { print $3; }' )" @@ -469,7 +469,7 @@ function test_custom_ssh_configuration () it "Logs the setting value." password_authentication="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^password authentication :.*$/ { print $0; }' )" @@ -574,7 +574,7 @@ function test_custom_ssh_configuration () it "Logs the setting value." user_sudo="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^sudo :.*$/ { print $0; }' )" @@ -634,7 +634,7 @@ function test_custom_ssh_configuration () it "Logs the setting value." user="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^user :.*$/ { print $0; }' )" @@ -694,7 +694,7 @@ function test_custom_ssh_configuration () it "Logs the key signature." user_key_signature="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^45:46:b0:ef:a5:e3:c9:6f:1e:66:94:ba:e1:fd:df:65$/ { print $1; }' )" @@ -784,7 +784,7 @@ function test_custom_ssh_configuration () it "Logs the key signatures." user_key_signature="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^45:46:b0:ef:a5:e3:c9:6f:1e:66:94:ba:e1:fd:df:65$/ { print $1; }' )" @@ -792,7 +792,7 @@ function test_custom_ssh_configuration () user_key_signature+=" " user_key_signature+="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^b3:2e:5d:8c:76:d3:c7:24:13:a3:4f:6f:4d:a2:31:9c$/ { print $1; }' )" @@ -867,7 +867,7 @@ function test_custom_ssh_configuration () it "Logs the key signatures." user_key_signature="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^45:46:b0:ef:a5:e3:c9:6f:1e:66:94:ba:e1:fd:df:65$/ { print $1; }' )" @@ -875,7 +875,7 @@ function test_custom_ssh_configuration () user_key_signature+=" " user_key_signature+="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^b3:2e:5d:8c:76:d3:c7:24:13:a3:4f:6f:4d:a2:31:9c$/ { print $1; }' )" @@ -932,7 +932,7 @@ function test_custom_ssh_configuration () it "Logs the key signature." user_key_signature="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | sed -n -e '/^rsa private key fingerprint :$/{ n; p; }' \ | awk '{ print $1; }' @@ -989,7 +989,7 @@ function test_custom_ssh_configuration () it "Logs the key signature." user_key_signature="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | sed -n -e '/^rsa private key fingerprint :$/{ n; p; }' \ | awk '{ print $1; }' @@ -1052,7 +1052,7 @@ function test_custom_ssh_configuration () it "Logs the setting value." user_home="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^home :.*$/ { print $0; }' )" @@ -1113,7 +1113,7 @@ function test_custom_ssh_configuration () it "Logs the setting value." user_id="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^id :.*$/ { print $0; }' )" @@ -1174,7 +1174,7 @@ function test_custom_ssh_configuration () it "Logs the setting value." user_id="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^id :.*$/ { print $0; }' )" @@ -1236,7 +1236,7 @@ function test_custom_ssh_configuration () it "Logs the setting value." user_shell="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^shell :.*$/ { print $0; }' )" @@ -1349,7 +1349,7 @@ function test_custom_ssh_configuration () it "Logs a redacted value." user_password="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^password :.*$/ { print $0; }' )" @@ -1419,7 +1419,7 @@ function test_custom_ssh_configuration () it "Logs a redacted value." user_password="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^password :.*$/ { print $0; }' )" @@ -1489,7 +1489,7 @@ function test_custom_ssh_configuration () it "Logs a redacted value." user_password="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^password :.*$/ { print $0; }' )" @@ -1556,7 +1556,7 @@ function test_custom_ssh_configuration () it "Logs a redacted value." user_password="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^password :.*$/ { print $0; }' )" @@ -1613,7 +1613,7 @@ function test_custom_ssh_configuration () it "Logs the setting value." user="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^user :.*$/ { print $0; }' )" @@ -1644,7 +1644,7 @@ function test_custom_ssh_configuration () it "Logs the setting value." user_id="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^id :.*$/ { print $0; }' )" @@ -1674,7 +1674,7 @@ function test_custom_ssh_configuration () it "Logs the setting value." user_home="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^home :.*$/ { print $0; }' )" @@ -1693,7 +1693,7 @@ function test_custom_ssh_configuration () docker run \ --detach \ --name ssh.1 \ - --env "SSH_TIMEZONE=Europe/London" \ + --env "SYSTEM_TIMEZONE=Europe/London" \ --publish ${DOCKER_PORT_MAP_TCP_22}:22 \ jdeathe/centos-ssh:latest \ &> /dev/null @@ -1722,7 +1722,7 @@ function test_custom_ssh_configuration () it "Logs the setting value." timezone="$( - docker logs \ + 2>&1 docker logs \ ssh.1 \ | awk '/^timezone :.*$/ { print $0; }' )" @@ -1741,7 +1741,7 @@ function test_custom_ssh_configuration () docker run \ --detach \ --name ssh.1 \ - --env "SSH_AUTOSTART_SUPERVISOR_STDOUT=true" \ + --env "ENABLE_SUPERVISOR_STDOUT=true" \ --publish ${DOCKER_PORT_MAP_TCP_22}:22 \ jdeathe/centos-ssh:latest \ &> /dev/null @@ -1764,7 +1764,7 @@ function test_custom_ssh_configuration () docker run \ --detach \ --name ssh.1 \ - --env "SSH_AUTOSTART_SUPERVISOR_STDOUT=false" \ + --env "ENABLE_SUPERVISOR_STDOUT=false" \ --publish ${DOCKER_PORT_MAP_TCP_22}:22 \ jdeathe/centos-ssh:latest \ &> /dev/null @@ -1787,7 +1787,7 @@ function test_custom_ssh_configuration () docker run \ --detach \ --name ssh.1 \ - --env "SSH_AUTOSTART_SSHD_BOOTSTRAP=false" \ + --env "ENABLE_SSHD_BOOTSTRAP=false" \ --publish ${DOCKER_PORT_MAP_TCP_22}:22 \ jdeathe/centos-ssh:latest \ &> /dev/null @@ -1795,7 +1795,7 @@ function test_custom_ssh_configuration () sleep ${STARTUP_TIME} it "Can disable sshd-bootstrap." - docker logs \ + 2>&1 docker logs \ ssh.1 \ | grep -qE 'INFO success: sshd-bootstrap entered RUNNING state' @@ -1811,7 +1811,7 @@ function test_custom_ssh_configuration () docker run \ --detach \ --name ssh.1 \ - --env "SSH_AUTOSTART_SSHD=false" \ + --env "ENABLE_SSHD_WRAPPER=false" \ --publish ${DOCKER_PORT_MAP_TCP_22}:22 \ jdeathe/centos-ssh:latest \ &> /dev/null @@ -1884,7 +1884,7 @@ function test_custom_sftp_configuration () it "Can connect using password authentication." password="$( - docker logs \ + 2>&1 docker logs \ sftp.1 \ | awk '/^password :.*$/ { print $3; }' )" @@ -1925,7 +1925,7 @@ function test_custom_sftp_configuration () it "Logs the setting value." password_authentication="$( - docker logs \ + 2>&1 docker logs \ sftp.1 \ | awk '/^password authentication :.*$/ { print $0; }' )" @@ -1987,7 +1987,7 @@ function test_custom_sftp_configuration () it "Logs the setting value." chroot_path="$( - docker logs \ + 2>&1 docker logs \ sftp.1 \ | awk '/^chroot path :.*$/ { print $0; }' )" @@ -2060,7 +2060,7 @@ function test_custom_sftp_configuration () it "Logs N/A key signature." user_key_signature="$( - docker logs \ + 2>&1 docker logs \ sftp.1 \ | sed -n -e '/^rsa private key fingerprint :$/{ n; p; }' \ | awk '{ print $1; }' @@ -2090,8 +2090,8 @@ function test_custom_sftp_configuration () docker run \ --detach \ --name www-data.pool-1.1.1 \ - --env "SSH_AUTOSTART_SSHD=false" \ - --env "SSH_AUTOSTART_SSHD_BOOTSTRAP=true" \ + --env "ENABLE_SSHD_WRAPPER=false" \ + --env "ENABLE_SSHD_BOOTSTRAP=true" \ --volume www-data.pool-1.1.1:/var/www \ jdeathe/centos-ssh:latest \ &> /dev/null @@ -2283,7 +2283,7 @@ function test_healthcheck () docker run \ --detach \ --name ssh.1 \ - --env SSH_AUTOSTART_SSHD=false \ + --env ENABLE_SSHD_WRAPPER=false \ jdeathe/centos-ssh:latest \ &> /dev/null @@ -2349,7 +2349,7 @@ function test_healthcheck () -v event_lag="${event_lag_seconds}" \ -v interval="${interval_seconds}" \ -v retries="${retries}" \ - 'BEGIN { print event_lag + (interval * retries); }' + 'BEGIN { print (2 * event_lag) + (interval * retries); }' )" health_status="$(