diff --git a/Dockerfile b/Dockerfile index f47a6ea..f166dd0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,32 @@ -ARG ALPINE_VERSION=${ALPINE_VERSION:-3.9} -FROM alpine:${ALPINE_VERSION} +ARG ALPINE_VERSION="${ALPINE_VERSION:-3.9}" +FROM alpine:"${ALPINE_VERSION}" LABEL maintainer="https://github.com/hermsi1337" -ARG OPENSSH_VERSION=${OPENSSH_VERSION:-7.9_p1-r5} -ENV OPENSSH_VERSION=${OPENSSH_VERSION} \ - ROOT_PASSWORD=root \ - KEYPAIR_LOGIN=false +ARG OPENSSH_VERSION="${OPENSSH_VERSION:-7.9_p1-r5}" +ENV CONF_VOLUME="/conf.d" +ENV OPENSSH_VERSION="${OPENSSH_VERSION}" \ + CACHED_SSH_DIRECTORY="${CONF_VOLUME}/ssh" \ + AUTHORIZED_KEYS_VOLUME="${CONF_VOLUME}/authorized_keys" \ + ROOT_KEYPAIR_LOGIN_ENABLED="false" \ + ROOT_LOGIN_UNLOCKED="false" \ + USER_LOGIN_SHELL="/bin/bash" \ + USER_LOGIN_SHELL_FALLBACK="/bin/ash" + +RUN apk add --upgrade --no-cache \ + bash \ + bash-completion \ + rsync \ + openssh=${OPENSSH_VERSION} \ + && \ + mkdir -p /root/.ssh "${CONF_VOLUME}" "${AUTHORIZED_KEYS_VOLUME}" \ + && \ + cp -a /etc/ssh "${CACHED_SSH_DIRECTORY}" \ + && \ + rm -rf /var/cache/apk/* COPY entrypoint.sh / -RUN apk add --upgrade --no-cache openssh=${OPENSSH_VERSION} \ - && chmod +x /entrypoint.sh \ - && mkdir -p /root/.ssh \ - && rm -rf /var/cache/apk/* /tmp/* - +COPY conf.d/etc/ /etc/ EXPOSE 22 VOLUME ["/etc/ssh"] ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index 67329cc..1a57c57 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Make your OpenSSH fly on Alpine ### Overview -Use this Dockerfile / -image to start a sshd-server upon a lightweight Alpine container. +Use this Dockerfile / -image to start a slim and highly customizable sshd-server with `bash` and `rsync` installed. ### Regular builds, automagically [![Build Status](https://travis-ci.com/Hermsi1337/docker-sshd.svg?branch=master)](https://travis-ci.com/Hermsi1337/docker-sshd) @@ -11,23 +11,19 @@ Thanks to [Travis-CI](https://travis-ci.com/) this image is pushed weekly and cr For recent tags check [Dockerhub](https://hub.docker.com/r/hermsi/alpine-sshd/tags/). ### Features -* Always installs the latest OpenSSH-Version available for Alpine -* Password of "root"-user can be changed when starting the container using --env -* You can choose between ssh-keypair- and password auth +* `bash`-shell and `rsync` installed +* Default `.bashrc` from `ubuntu` +* Desired shell is configurable by --env +* En- or disable `root`-user by --env + * Choose between keypar and password auth for `root` + * Password for `root` is configurable by --env +* Additional ssh-users can be created by --env + * Authentication for additional users is done by keypair +* Beautifully colored log output -### Extending this image -This image is designed to be as slim and vanilla as possible. -If you need additional Tools like `git` or `bash`-shell, I definetly recommend to build your own image on top of `alpine-sshd`: -```Dockerfile -FROM hermsi/alpine-sshd:latest - -RUN apk add --no-cache --upgrade \ - git \ - bash -``` -### Basic Usage -#### Authentication by password -``` +### Usage examples +#### Authentication as root by password +```bash $ docker run --rm \ --publish=1337:22 \ --env ROOT_PASSWORD=MyRootPW123 \ @@ -35,21 +31,71 @@ hermsi/alpine-sshd ``` After the container is up you are able to ssh in it as root with the in --env provided password for "root"-user. -``` +```bash $ ssh root@mydomain.tld -p 1337 ``` -#### Authentication by ssh-keypair -``` + +#### Authentication as root by ssh-keypair +```bash $ docker run --rm \ --publish=1337:22 \ ---env KEYPAIR_LOGIN=true \ +--env ROOT_KEYPAIR_LOGIN_ENABLED=true \ --volume /path/to/authorized_keys:/root/.ssh/authorized_keys \ hermsi/alpine-sshd ``` + After the container is up you are able to ssh in it as root with a private-key which matches the provided public-key in authorized_keys for "root"-user. -``` +```bash $ ssh root@mydomain.tld -p 1337 -i /path/to/private_key ``` + +#### Authenticate as additional user by ssh-keypair +$ docker run --rm \ +--publish=1337:22 \ +--env SSH_USERS="hermsi:1000:1000" \ +--volume /path/to/hermsi_public_key:/conf.d/authorized_keys/hermsi \ +hermsi/alpine-sshd +``` + +After the container is up you are able to ssh in it as the given user with a private-key that matches the provided public-key in authorized_keys for your created user. +```bash +$ ssh mydomain.tld -l hermsi -p 1337 -i /path/to/hermsi_private_key +``` + +#### Create multiple, additional users with keypair +$ docker run --rm \ +--publish=1337:22 \ +--env SSH_USERS="hermsi:1000:1000,dennis:1001:1001" \ +--volume /path/to/hermsi_public_key:/conf.d/authorized_keys/hermsi \ +--volume /path/to/dennis_public_key:/conf.d/authorized_keys/dennis \ +hermsi/alpine-sshd +``` + +After the container is up you are able to ssh in it as one of the given users with a private-key that matches the provided public-key in authorized_keys for your desired user. +```bash +$ ssh root@mydomain.tld -p 1337 -i /path/to/private_key +``` + +### Configuration +While beeing very slim and vanilla this image is still highly customizable. + +#### Environment variables +| Variable | Possible Values | Default value | Explanation | +|:-----------------:|:-----------------:|:----------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------:| +| ROOT_LOGIN_UNLOCKED | 'true' or 'false' | 'false' | Whether to enable or disable login as 'root' user | +| ROOT_KEYPAIR_LOGIN_ENABLED | 'true' or 'false' | 'false' | Enable login as 'root' by keypair (implies `ROOT_LOGIN_UNLOCKED`). Must mount public-key into container: `/root/.ssh/authorized_keys` | +| USER_LOGIN_SHELL | any existing shell | `/bin/bash` | Choose the desired default shell for all additional users. If the configured shell is not existent, a fallback to `/bin/ash` is applied. | + +### Extending this image +This image is designed to be as slim and vanilla as possible. +If you need additional Tools like `git` , I definetly recommend to build your own image on top of `alpine-sshd`: +```Dockerfile +FROM hermsi/alpine-sshd:latest + +RUN apk add --no-cache \ + git +``` + ### Use with docker-compose I built this image in order to use it along with a nginx and fpm-php container for transferring files via sftp. If you are interested in a Dockerfile which fulfills this need: [this way](https://github.com/Hermsi1337/docker-compose/blob/master/full_php_dev_stack/docker-compose.yml) diff --git a/conf.d/etc/profile.d/ubuntu-bashrc.sh b/conf.d/etc/profile.d/ubuntu-bashrc.sh new file mode 100644 index 0000000..a501343 --- /dev/null +++ b/conf.d/etc/profile.d/ubuntu-bashrc.sh @@ -0,0 +1,103 @@ +# ~/.bashrc: executed by bash(1) for non-login shells. +# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) +# for examples + +# If not running interactively, don't do anything +[ -z "$PS1" ] && return + +# don't put duplicate lines in the history. See bash(1) for more options +# ... or force ignoredups and ignorespace +HISTCONTROL=ignoredups:ignorespace + +# append to the history file, don't overwrite it +shopt -s histappend + +# for setting history length see HISTSIZE and HISTFILESIZE in bash(1) +HISTSIZE=1000 +HISTFILESIZE=2000 + +# check the window size after each command and, if necessary, +# update the values of LINES and COLUMNS. +shopt -s checkwinsize + +# make less more friendly for non-text input files, see lesspipe(1) +[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" + +# set variable identifying the chroot you work in (used in the prompt below) +if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then + debian_chroot=$(cat /etc/debian_chroot) +fi + +# set a fancy prompt (non-color, unless we know we "want" color) +case "$TERM" in + xterm-color) color_prompt=yes;; +esac + +# uncomment for a colored prompt, if the terminal has the capability; turned +# off by default to not distract the user: the focus in a terminal window +# should be on the output of commands, not on the prompt +#force_color_prompt=yes + +if [ -n "$force_color_prompt" ]; then + if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then + # We have color support; assume it's compliant with Ecma-48 + # (ISO/IEC-6429). (Lack of such support is extremely rare, and such + # a case would tend to support setf rather than setaf.) + color_prompt=yes + else + color_prompt= + fi +fi + +if [ "$color_prompt" = yes ]; then + PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' +else + PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' +fi +unset color_prompt force_color_prompt + +# If this is an xterm set the title to user@host:dir +case "$TERM" in +xterm*|rxvt*) + PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" + ;; +*) + ;; +esac + +# enable color support of ls and also add handy aliases +if [ -x /usr/bin/dircolors ]; then + test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" + alias ls='ls --color=auto' + #alias dir='dir --color=auto' + #alias vdir='vdir --color=auto' + + alias grep='grep --color=auto' + alias fgrep='fgrep --color=auto' + alias egrep='egrep --color=auto' +fi + +# some more ls aliases +alias ll='ls -alF' +alias la='ls -A' +alias l='ls -CF' + +# Add an "alert" alias for long running commands. Use like so: +# sleep 10; alert +alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"' + +# Alias definitions. +# You may want to put all your additions into a separate file like +# ~/.bash_aliases, instead of adding them here directly. +# See /usr/share/doc/bash-doc/examples in the bash-doc package. + +if [ -f ~/.bash_aliases ]; then + . ~/.bash_aliases +fi + +# enable programmable completion features (you don't need to enable +# this, if it's already enabled in /etc/bash.bashrc and /etc/profile +# sources /etc/bash.bashrc). +if [ -f /etc/bash_completion ] && ! shopt -oq posix; then + . /etc/bash_completion +fi \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh index d6b3157..ebfd4e3 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,26 +1,163 @@ -#!/bin/sh +#!/usr/bin/env sh -if [ "${ROOT_PASSWORD}" == "root" ] || [ -z "${ROOT_PASSWORD}" ]; then - export ROOT_PASSWORD="$(hexdump -e '"%02x"' -n 16 /dev/urandom)" - echo "Successfully generated a random password for root" +set -e + +# enable debug mode if desired +if [ "${DEBUG}" = "true" ]; then + set -x fi -echo "root:${ROOT_PASSWORD}" | chpasswd +log() { + LEVEL="${1}" + TO_LOG="${2}" + + WHITE='\033[1;37m' + YELLOW='\033[1;33m' + RED='\033[1;31m' + NO_COLOR='\033[0m' + + if [ "${LEVEL}" = "warning" ]; then + LOG_LEVEL="${YELLOW}WARN${NO_COLOR}" + elif [ "${LEVEL}" = "error" ]; then + LOG_LEVEL="${RED}ERROR${NO_COLOR}" + else + LOG_LEVEL="${WHITE}INFO${NO_COLOR}" + if [ -z "${TO_LOG}" ]; then + TO_LOG="${1}" + fi + fi + + echo -e "[${LOG_LEVEL}] ${TO_LOG}" +} + +ensure_mod() { + FILE="${1}" + MOD="${2}" + U_ID="${3}" + G_ID="${4}" + + chmod "${MOD}" "${FILE}" + chown "${U_ID}"."${G_ID}" "${FILE}" +} + +generate_passwd() { + hexdump -e '"%02x"' -n 16 /dev/urandom +} + +# ensure backward comaptibility for earlier versions of this image +if [ -n "${KEYPAIR_LOGIN}" ] && [ "${KEYPAIR_LOGIN}" = "true" ]; then + ROOT_KEYPAIR_LOGIN_ENABLED="${KEYPAIR_LOGIN}" +fi +if [ -n "${ROOT_PASSWORD}" ]; then + ROOT_LOGIN_UNLOCKED="true" +fi + +# enable root login if keypair login is enabled +if [ "${ROOT_KEYPAIR_LOGIN_ENABLED}" = "true" ]; then + ROOT_LOGIN_UNLOCKED="true" +fi + +# initiate default sshd-config if there is none available +if ! find "/etc/ssh" -mindepth 1 -print -quit 2>/dev/null | grep -q .; then + cp -a "${CACHED_SSH_DIRECTORY}"/* /etc/ssh/. +fi +rm -rf "${CACHED_SSH_DIRECTORY}" # generate host keys if not present -ssh-keygen -A +ssh-keygen -A 1>/dev/null + +log "Applying configuration for 'root' user ..." + +if [ "${ROOT_LOGIN_UNLOCKED}" = "true" ] ; then + + # generate random root password + if [ -z "${ROOT_PASSWORD}" ]; then + log " generating random password for user 'root'" + ROOT_PASSWORD="$(generate_passwd)" + fi + + echo "root:${ROOT_PASSWORD}" | chpasswd >/dev/null 2>&1 + log " password for user 'root' set" + log "warning" " user 'root' is now UNLOCKED" + + # set root login mode by password or keypair + if [ "${ROOT_KEYPAIR_LOGIN_ENABLED}" = "true" ] && [ -f "${HOME}/.ssh/authorized_keys" ] ; then + sed -i "s/#PermitRootLogin.*/PermitRootLogin without-password/" /etc/ssh/sshd_config + sed -i "s/#PasswordAuthentication.*/PasswordAuthentication no/" /etc/ssh/sshd_config + ensure_mod "${HOME}/.ssh/authorized_keys" "0600" "root" "root" + log " enabled login by keypair and disabled password-login for user 'root'" + else + sed -i "s/#PermitRootLogin.*/PermitRootLogin\ yes/" /etc/ssh/sshd_config + log " enabled login by password for user 'root'" + fi -# set root login mode by password or keypair -if [ "${KEYPAIR_LOGIN}" = "true" ] && [ -f "${HOME}/.ssh/authorized_keys" ] ; then - sed -i "s/#PermitRootLogin.*/PermitRootLogin without-password/" /etc/ssh/sshd_config - sed -i "s/#PasswordAuthentication.*/PasswordAuthentication no/" /etc/ssh/sshd_config - chmod 600 "${HOME}/.ssh/authorized_keys" - chown root.root "${HOME}/.ssh/authorized_keys" - echo "Enabled root-login by keypair and disabled password-login" else - sed -i s/#PermitRootLogin.*/PermitRootLogin\ yes/ /etc/ssh/sshd_config - echo "Enabled root-login by password" + + sed -i "s/#PermitRootLogin.*/PermitRootLogin no/" /etc/ssh/sshd_config + log " disabled login for user 'root'" + log " user 'root' is now LOCKED" + fi +printf "\n" "" + +log "Applying configuration for additional users ..." + +if [ ! -x "${USER_LOGIN_SHELL}" ]; then + log "error" " can not allocate desired shell '${USER_LOGIN_SHELL}', falling back to '${USER_LOGIN_SHELL_FALLBACK}' ..." + USER_LOGIN_SHELL="${USER_LOGIN_SHELL_FALLBACK}" +fi + +log " desired shell is ${USER_LOGIN_SHELL}" + + +if [ -n "${SSH_USERS}" ]; then + + IFS="," + for USER in ${SSH_USERS}; do + + log " '${USER}'" + + USER_NAME="$(echo "${USER}" | cut -d ':' -f 1)" + USER_UID="$(echo "${USER}" | cut -d ':' -f 2)" + USER_GID="$(echo "${USER}" | cut -d ':' -f 3)" + + if [ -z "${USER_NAME}" ] || [ -z "${USER_UID}" ] || [ -z "${USER_GID}" ]; then + log "error" " skipping invalid data '${USER_NAME}' - UID: '${USER_UID}' GID: '${USER_GID}'" + continue + fi + + getent group "${USER_GID}" >/dev/null 2>&1 || addgroup -g "${USER_GID}" "${USER_NAME}" + getent passwd "${USER_NAME}" >/dev/null 2>&1 || adduser -s "${USER_LOGIN_SHELL}" -D -u "${USER_UID}" -G "${USER_NAME}" "${USER_NAME}" + passwd -u "${USER_NAME}" >/dev/null 2>&1 + mkdir -p "/home/${USER_NAME}/.ssh" + + log " user '${USER_NAME}' created - UID: '${USER_UID}' GID: '${USER_GID}'" + + MOUNTED_AUTHORIZED_KEYS="${AUTHORIZED_KEYS_VOLUME}/${USER_NAME}" + LOCAL_AUTHORIZED_KEYS="/home/${USER_NAME}/.ssh/authorized_keys" + + if [ ! -e "${MOUNTED_AUTHORIZED_KEYS}" ]; then + log "warning" " no SSH authorized_keys found for user '${USER_NAME}'" + else + cp "${MOUNTED_AUTHORIZED_KEYS}" "${LOCAL_AUTHORIZED_KEYS}" + log " copied ${MOUNTED_AUTHORIZED_KEYS} to ${LOCAL_AUTHORIZED_KEYS}" + ensure_mod "${LOCAL_AUTHORIZED_KEYS}" "0600" "${USER_NAME}" "${USER_GID}" + log " set mod 0600 on ${LOCAL_AUTHORIZED_KEYS}" + fi + + printf "\n" "" + + done + unset IFS + +else + + log " no additional SSH-users set" + +fi + +echo "" + # do not detach (-D), log to stderr (-e), passthrough other arguments -exec /usr/sbin/sshd -D -e "$@" +exec /usr/sbin/sshd -D -e "$@" \ No newline at end of file