Initial commit
This commit is contained in:
commit
0584d79e9a
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
**/*
|
||||||
|
!.gitignore
|
||||||
|
!README.md
|
||||||
|
!docker-compose.yaml
|
||||||
|
!build/
|
||||||
|
!build/*
|
29
README.md
Normal file
29
README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# SSH entrypoint with yubikey OTP support
|
||||||
|
|
||||||
|
This docker container runs a sshd instance which is exposed through TCP 443. It can be a nice and secure way into your network.
|
||||||
|
|
||||||
|
Since this is the only TCP service I expose, traefik handles this automagically while also routing SSL HTTPS traffic the normal way.
|
||||||
|
|
||||||
|
The image is a modified version of https://github.com/Hermsi1337/docker-sshd which has been made to work with yubikey OTP certification and allow for personalized `.ssh/config` files to be loaded.
|
||||||
|
|
||||||
|
### ENV variable `SSH_USERS`
|
||||||
|
|
||||||
|
`SSH_USERS` contain a comma separates lists of username:UID:GUI that will be allowed to login.
|
||||||
|
|
||||||
|
Ex:
|
||||||
|
|
||||||
|
`SSH_USERS=myuser:1000:1000,anotheruser:1001:1001`
|
||||||
|
|
||||||
|
### Key files
|
||||||
|
|
||||||
|
The directory mapped to `/conf.d/authorized_keys` contain files for authorized_keys, authorized yubikeys and ssh config.
|
||||||
|
|
||||||
|
- A file named `myuser` will be copied to `/home/myuser/.ssh/authorized_keys`
|
||||||
|
- A file named `myuser.config` will be copied to `/home/myuser/.ssh/config`
|
||||||
|
- A file name `myuser.yubi` will be copied to `/home/myuser/.yubico/authorized_yubikeys`
|
||||||
|
|
||||||
|
The format of the `.yubi` file is your username followed by a list of the first 12 characters from any OTP from all of your yubikeys, all separated by `:`s. E.g.:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
myuser:cccccccgklgc:ccccccclabca:
|
||||||
|
```
|
10
build/Dockerfile
Normal file
10
build/Dockerfile
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
FROM hermsi/alpine-sshd:latest
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
openssh-server-pam \
|
||||||
|
yubico-pam \
|
||||||
|
google-authenticator \
|
||||||
|
&& \
|
||||||
|
rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
COPY entrypoint.sh /
|
227
build/entrypoint.sh
Executable file
227
build/entrypoint.sh
Executable file
@ -0,0 +1,227 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# This file is copied from https://github.com/Hermsi1337/docker-sshd
|
||||||
|
# Two additions have been made to allow for yubikey authentication
|
||||||
|
# and adding an .ssh/config file
|
||||||
|
#############################################
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# enable debug mode if desired
|
||||||
|
if [[ "${DEBUG}" == "true" ]]; then
|
||||||
|
set -x
|
||||||
|
fi
|
||||||
|
|
||||||
|
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 [[ ! "$(ls -A /etc/ssh)" ]]; then
|
||||||
|
cp -a "${CACHED_SSH_DIRECTORY}"/* /etc/ssh/.
|
||||||
|
fi
|
||||||
|
rm -rf "${CACHED_SSH_DIRECTORY}"
|
||||||
|
|
||||||
|
# generate host keys if not present
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
USER_GROUP="${USER_NAME}"
|
||||||
|
if getent group "${USER_GID}" &>/dev/null ; then
|
||||||
|
USER_GROUP="$(getent group "${USER_GID}" | cut -d ':' -f 1)"
|
||||||
|
log "warning" " desired GID is already present in system. Using the present group-name - GID: '${USER_GID}' GNAME: '${USER_GROUP}'"
|
||||||
|
else
|
||||||
|
addgroup -g "${USER_GID}" "${USER_GROUP}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if getent passwd "${USER_NAME}" &>/dev/null ; then
|
||||||
|
log "warning" " desired USER_NAME is already present in system. Skipping creation - USER_NAME: '${USER_NAME}'"
|
||||||
|
else
|
||||||
|
adduser -s "${USER_LOGIN_SHELL}" -D -u "${USER_UID}" -G "${USER_GROUP}" "${USER_NAME}"
|
||||||
|
log " user '${USER_NAME}' created - UID: '${USER_UID}' GID: '${USER_GID}' GNAME: '${USER_GROUP}'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
passwd -u "${USER_NAME}" &>/dev/null || true
|
||||||
|
mkdir -p "/home/${USER_NAME}/.ssh"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# ADDED
|
||||||
|
#############################################
|
||||||
|
|
||||||
|
log " set mod 0755 on /home/${USER_NAME}/.ssh"
|
||||||
|
ensure_mod "/home/${USER_NAME}/.ssh" "0755" "${USER_NAME}" "${USER_GID}"
|
||||||
|
|
||||||
|
mkdir -p "/home/${USER_NAME}/.yubico"
|
||||||
|
log " set mod 0755 on /home/${USER_NAME}/.yubico"
|
||||||
|
ensure_mod "/home/${USER_NAME}/.yubico" "0755" "${USER_NAME}" "${USER_GID}"
|
||||||
|
MOUNTED_AUTHORIZED_YUBI="${AUTHORIZED_KEYS_VOLUME}/${USER_NAME}.yubi"
|
||||||
|
LOCAL_AUTHORIZED_YUBI="/home/${USER_NAME}/.yubico/authorized_yubikeys"
|
||||||
|
|
||||||
|
if [[ -e "${MOUNTED_AUTHORIZED_YUBI}" ]]; then
|
||||||
|
cp "${MOUNTED_AUTHORIZED_YUBI}" "${LOCAL_AUTHORIZED_YUBI}"
|
||||||
|
log " copied ${MOUNTED_AUTHORIZED_YUBI} to ${LOCAL_AUTHORIZED_YUBI}"
|
||||||
|
ensure_mod "${LOCAL_AUTHORIZED_YUBI}" "0600" "${USER_NAME}" "${USER_GID}"
|
||||||
|
log " set mod 0600 on ${LOCAL_AUTHORIZED_YUBI}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
MOUNTED_CONFIG="${AUTHORIZED_KEYS_VOLUME}/${USER_NAME}.config"
|
||||||
|
LOCAL_CONFIG="/home/${USER_NAME}/.ssh/config"
|
||||||
|
|
||||||
|
if [[ -e "${MOUNTED_CONFIG}" ]]; then
|
||||||
|
cp "${MOUNTED_CONFIG}" "${LOCAL_CONFIG}"
|
||||||
|
log " copied ${MOUNTED_CONFIG} to ${LOCAL_CONFIG}"
|
||||||
|
ensure_mod "${LOCAL_CONFIG}" "0644" "${USER_NAME}" "${USER_GID}"
|
||||||
|
log " set mod 0644 on ${LOCAL_CONFIG}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# END OF ADDITION
|
||||||
|
#############################################
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
|
||||||
|
done
|
||||||
|
unset IFS
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
log " no additional SSH-users set"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# ADDED
|
||||||
|
#############################################
|
||||||
|
|
||||||
|
sed -i "s/#ChallengeResponseAuthentication.*/ChallengeResponseAuthentication yes/" /etc/ssh/sshd_config
|
||||||
|
sed -i "s/#UsePAM.*/UsePAM yes/" /etc/ssh/sshd_config
|
||||||
|
|
||||||
|
echo "auth sufficient pam_yubico.so id=16 debug" >> /etc/pam.d/sshd
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# END OF ADDITION
|
||||||
|
#############################################
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# do not detach (-D), log to stderr (-e), passthrough other arguments
|
||||||
|
exec /usr/sbin/sshd -D -e "$@"
|
22
docker-compose.yaml
Normal file
22
docker-compose.yaml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
ssh:
|
||||||
|
build: ./build
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
environment:
|
||||||
|
SSH_USERS:
|
||||||
|
volumes:
|
||||||
|
- ./authorized_keys:/conf.d/authorized_keys
|
||||||
|
- ./ssh:/etc/ssh
|
||||||
|
labels:
|
||||||
|
traefik.enable: true
|
||||||
|
traefik.tcp.services.ssh.loadbalancer.server.port: 22
|
||||||
|
traefik.tcp.routers.ssh.rule: HostSNI(`*`)
|
||||||
|
traefik.tcp.routers.ssh.entrypoints: websecure
|
Loading…
x
Reference in New Issue
Block a user