diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e43b0f988953ae3a84b00331d0ccf5f7d51cb3cf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/Dockerfile b/Dockerfile index 0c9d418ee842a888a16c499ddf3981501a52c70b..ea87109dfc1282991d21944d8bb0b3c4e6fd6848 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,29 @@ -FROM debian:stretch -MAINTAINER Adrian Dvergsdal [atmoz.net] - -# Steps done in one RUN layer: -# - Install packages -# - OpenSSH needs /var/run/sshd to run -# - Remove generic host keys, entrypoint generates unique keys -RUN apt-get update && \ - apt-get -y install openssh-server && \ - rm -rf /var/lib/apt/lists/* && \ - mkdir -p /var/run/sshd && \ - rm -f /etc/ssh/ssh_host_*key* - -RUN mkdir -p /var/run/sftp -RUN chown -R root:root /var/run/sftp /run -RUN chmod g+rw /var/run/sftp /run - -ARG SFTP_USERS -RUN if [ -n "$SFTP_USERS" ]; then \ - useradd --no-user-group $SFTP_USERS -g 0 -d /var/www/html ; \ - fi - -COPY files/sshd_config /etc/ssh/sshd_config -COPY files/create-sftp-user /usr/local/bin/ -COPY files/entrypoint / - -EXPOSE 2222 - -ENTRYPOINT ["/entrypoint"] +FROM ubuntu:18.04 + +RUN apt-get -y update \ +&& apt-get -y install openssh-server +# https://help.ubuntu.com/lts/serverguide/openssh-server.html + +RUN mkdir /var/run/sshd + +COPY sshd_config /etc/ssh/sshd_config +RUN chmod g+r /etc/ssh/sshd_config + +# This default is overwritten a variable passed into build process +# $ docker build --build-arg SFTP_UID=1000110000 -t umich-sftp . +ARG SFTP_UID=100000000000 + +# http://manpages.ubuntu.com/manpages/bionic/man8/useradd.8.html +# d: which home dir, g: group, l: don't add to lastlog and faillog, m: create home +# s: which shell, o: non-unique, u: uid, +RUN useradd -g root -m -d /home/sftpuser -s /bin/bash -l -o -u $SFTP_UID sftpuser +RUN echo "sftpuser:sftpuser" | chpasswd + +# Allow access to server keys for sftpuser +RUN chmod g+r /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key + +EXPOSE 2022 + +ADD startup.sh /usr/local/bin +RUN chmod +x /usr/local/bin/startup.sh +CMD ["startup.sh"] diff --git a/README.md b/README.md index 61e69f80197810c4ec738331a8c6dcdb7f7e393a..7f54f7acd3015d869dc3fbee18467783f4c19c58 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,22 @@ # umich-sftp +In order to allow logging in as non-root, we need to create a user that will run the sshd daemon. This user will inherit the uid that will be used to run the pod, the range of which is declared in a project's annotations. Essentially, the pod runs as the sftpuser. We create the user, as the openshift-provided uid is not part of the container image until it is started. +To build/run locally: +$ docker build --build-arg SFTP_UID=1000110000 -t umich-sftp . + +$ docker run -u sftpuser -p 2022:2022 -it umich-sftp + +Slurp the key out of the container onto your laptop +$ docker cp $(docker ps -q):/home/sftpuser/.ssh/ssh_host_ed25519_key . + +And connect using that key file +$ sftp -oIdentityFile=./ssh_host_ed25519_key -P 2022 sftpuser@localhost + +to connect using that key to containersnp: +$ sftp -oIdentityFile=./ssh_host_ed25519_key -P 2001 sftpuser@10.196.49.72 + + +Good background material: +https://serverfault.com/questions/660160/openssh-difference-between-internal-sftp-and-sftp-server + +https://www.ssh.com/ssh/sshd diff --git a/files/create-sftp-user b/files/create-sftp-user deleted file mode 100755 index a92b64650d73c04e9665bf6687090e328d39d500..0000000000000000000000000000000000000000 --- a/files/create-sftp-user +++ /dev/null @@ -1,105 +0,0 @@ -#!/bin/bash -x -set -Eeo pipefail - -# shellcheck disable=2154 -trap 's=$?; echo "$0: Error on line "$LINENO": $BASH_COMMAND"; exit $s' ERR - -# Extended regular expression (ERE) for arguments -reUser='[A-Za-z0-9._][A-Za-z0-9._-]{0,31}' # POSIX.1-2008 -rePass='[^:]{0,255}' -reUid='[[:digit:]]*' -reGid='[[:digit:]]*' -reDir='[^:]*' -#reArgs="^($reUser)(:$rePass)(:e)?(:$reUid)?(:$reGid)?(:$reDir)?$" - -function log() { - echo "[$0] $*" -} - -function validateArg() { - name="$1" - val="$2" - re="$3" - - if [[ "$val" =~ ^$re$ ]]; then - return 0 - else - log "ERROR: Invalid $name \"$val\", do not match required regex pattern: $re" - return 1 - fi -} - -log "Parsing user data: \"$1\"" -IFS=':' read -ra args <<< "$1" - -skipIndex=0 -chpasswdOptions="" -useraddOptions=(--no-user-group) - -user="${args[0]}"; validateArg "username" "$user" "$reUser" || exit 1 -pass="${args[1]}"; validateArg "password" "$pass" "$rePass" || exit 1 - -if [ "${args[2]}" == "e" ]; then - chpasswdOptions="-e" - skipIndex=1 -fi - -uid="${args[$((skipIndex+2))]}"; validateArg "UID" "$uid" "$reUid" || exit 1 -gid="${args[$((skipIndex+3))]}"; validateArg "GID" "$gid" "$reGid" || exit 1 -dir="${args[$((skipIndex+4))]}"; validateArg "dirs" "$dir" "$reDir" || exit 1 - -if getent passwd "$user" > /dev/null; then - log "WARNING: User \"$user\" already exists. Skipping." - exit 0 -fi - -if [ -n "$uid" ]; then - useraddOptions+=(--non-unique --uid "$uid") -fi - -if [ -n "$gid" ]; then - if ! getent group "$gid" > /dev/null; then - groupadd --gid "$gid" "group_$gid" - fi - - useraddOptions+=(--gid "$gid") -fi - -useradd "${useraddOptions[@]}" "$user" -mkdir -p "/home/$user" -chown root:root "/home/$user" -chmod 755 "/home/$user" - -# Retrieving user id to use it in chown commands instead of the user name -# to avoid problems on alpine when the user name contains a '.' -uid="$(id -u "$user")" - -if [ -n "$pass" ]; then - echo "$user:$pass" | chpasswd $chpasswdOptions -else - usermod -p "*" "$user" # disabled password -fi - -# Add SSH keys to authorized_keys with valid permissions -if [ -d "/home/$user/.ssh/keys" ]; then - for publickey in "/home/$user/.ssh/keys"/*; do - cat "$publickey" >> "/home/$user/.ssh/authorized_keys" - done - chown "$uid" "/home/$user/.ssh/authorized_keys" - chmod 600 "/home/$user/.ssh/authorized_keys" -fi - -# Make sure dirs exists -if [ -n "$dir" ]; then - IFS=',' read -ra dirArgs <<< "$dir" - for dirPath in "${dirArgs[@]}"; do - dirPath="/home/$user/$dirPath" - if [ ! -d "$dirPath" ]; then - log "Creating directory: $dirPath" - mkdir -p "$dirPath" - chown -R "$uid:users" "$dirPath" - else - log "Directory already exists: $dirPath" - fi - done -fi diff --git a/files/entrypoint b/files/entrypoint deleted file mode 100755 index fac02fe24a07a72cca40f1520564e64e7a41daf9..0000000000000000000000000000000000000000 --- a/files/entrypoint +++ /dev/null @@ -1,95 +0,0 @@ -#!/bin/bash -x -set -Eeo pipefail - -# shellcheck disable=2154 -trap 's=$?; echo "$0: Error on line "$LINENO": $BASH_COMMAND"; exit $s' ERR - -reArgsMaybe="^[^:[:space:]]+:.*$" # Smallest indication of attempt to use argument -reArgSkip='^([[:blank:]]*#.*|[[:blank:]]*)$' # comment or empty line - -# Paths -userConfPath="/etc/sftp/users.conf" -userConfPathLegacy="/etc/sftp-users.conf" -userConfFinalPath="/var/run/sftp/users.conf" - -function log() { - echo "[$0] $*" >&2 -} - -# Allow running other programs, e.g. bash -if [[ -z "$1" || "$1" =~ $reArgsMaybe ]]; then - startSshd=true -else - startSshd=false -fi - -# Backward compatibility with legacy config path -if [ ! -f "$userConfPath" ] && [ -f "$userConfPathLegacy" ]; then - mkdir -p "$(dirname $userConfPath)" - ln -s "$userConfPathLegacy" "$userConfPath" -fi - -# Create users only on first run -if [ ! -f "$userConfFinalPath" ]; then - mkdir -p "$(dirname $userConfFinalPath)" - - if [ -f "$userConfPath" ]; then - # Append mounted config to final config - grep -v -E "$reArgSkip" < "$userConfPath" > "$userConfFinalPath" - fi - - if $startSshd; then - # Append users from arguments to final config - for user in "$@"; do - echo "$user" >> "$userConfFinalPath" - done - fi - - if [ -n "$SFTP_USERS" ]; then - # Append users from environment variable to final config - IFS=" " read -r -a usersFromEnv <<< "$SFTP_USERS" - for user in "${usersFromEnv[@]}"; do - echo "$user" >> "$userConfFinalPath" - done - fi - - # Check that we have users in config -# if [ -f "$userConfFinalPath" ] && [ "$(wc -l < "$userConfFinalPath")" -gt 0 ]; then - # Import users from final conf file -# while IFS= read -r user || [[ -n "$user" ]]; do -# create-sftp-user "$user" -# done < "$userConfFinalPath" -# elif $startSshd; then -# log "FATAL: No users provided!" -# exit 3 -# fi - - # Generate unique ssh keys for this container, if needed -# if [ ! -f /etc/ssh/ssh_host_ed25519_key ]; then -# ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N '' -# fi -# if [ ! -f /etc/ssh/ssh_host_rsa_key ]; then -# ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N '' -# fi -fi - -# Source custom scripts, if any -if [ -d /etc/sftp.d ]; then - for f in /etc/sftp.d/*; do - if [ -x "$f" ]; then - log "Running $f ..." - $f - else - log "Could not run $f, because it's missing execute permission (+x)." - fi - done - unset f -fi - -if $startSshd; then - log "Executing sshd" - exec /usr/sbin/sshd -D -e -else - log "Executing $*" - exec "$@" -fi diff --git a/files/sshd_config b/files/sshd_config deleted file mode 100755 index 3404920f47065e86dd00cfe57513adccdebf996b..0000000000000000000000000000000000000000 --- a/files/sshd_config +++ /dev/null @@ -1,25 +0,0 @@ -# Secure defaults -# See: https://stribika.github.io/2015/01/04/secure-secure-shell.html -Protocol 2 -HostKey /etc/ssh/ssh_host_ed25519_key -HostKey /etc/ssh/ssh_host_rsa_key - -# Faster connection -# See: https://github.com/atmoz/sftp/issues/11 -UseDNS no - -# Limited access -PermitRootLogin no -X11Forwarding no -AllowTcpForwarding no - -# Force sftp and chroot jail -Subsystem sftp internal-sftp -ForceCommand internal-sftp -#ChrootDirectory %h -ChrootDirectory /var/www/html - -# Enable this for more logs -#LogLevel VERBOSE - -Port 2222 \ No newline at end of file diff --git a/openshift-artifacts/bc.yaml b/openshift-artifacts/bc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..77c0d27d953bae8eae420fa3083bf7069275da43 --- /dev/null +++ b/openshift-artifacts/bc.yaml @@ -0,0 +1,39 @@ +apiVersion: build.openshift.io/v1 +kind: BuildConfig +metadata: + labels: + app: umich-sftp + name: umich-sftp +spec: + failedBuildsHistoryLimit: 5 + nodeSelector: null + output: + to: + kind: ImageStreamTag + name: umich-sftp:latest + postCommit: {} + resources: {} + runPolicy: Serial + source: + git: + ref: os-router + uri: https://gitlab.umich.edu/its-web-platforms/umich-sftp.git + type: Git + strategy: + dockerStrategy: + buildArgs: + - name: "SFTP_UID" + value: "1000110000" + from: + kind: ImageStreamTag + name: ubuntu:18.04 + type: Docker + successfulBuildsHistoryLimit: 5 + triggers: + - imageChange: + type: ImageChange + - type: ConfigChange + - gitlab: + secretReference: + name: gitlab-webhook + type: GitLab diff --git a/openshift-artifacts/dc.yaml b/openshift-artifacts/dc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7645495712441a6181777418901bb7cbc38640d2 --- /dev/null +++ b/openshift-artifacts/dc.yaml @@ -0,0 +1,55 @@ +apiVersion: apps.openshift.io/v1 +kind: DeploymentConfig +metadata: + labels: + app: umich-sftp + name: umich-sftp +spec: + replicas: 1 + revisionHistoryLimit: 10 + selector: + app: umich-sftp + deploymentconfig: umich-sftp + strategy: + activeDeadlineSeconds: 21600 + resources: {} + rollingParams: + intervalSeconds: 1 + maxSurge: 25% + maxUnavailable: 25% + timeoutSeconds: 600 + updatePeriodSeconds: 1 + type: Rolling + template: + metadata: + creationTimestamp: null + labels: + app: umich-sftp + deploymentconfig: umich-sftp + spec: + containers: + - image: umich-sftp:latest + imagePullPolicy: Always + name: umich-sftp + ports: + - containerPort: 2022 + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /home/sftpuser/dir1 + name: sftpuser-dir + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + volumes: + - name: sftpuser-dir + persistentVolumeClaim: + claimName: sftp-storage + test: false + triggers: + - type: ConfigChange +status: {} diff --git a/openshift-artifacts/pvc.yaml b/openshift-artifacts/pvc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fb4e3035044d486978e4ee78890f8ec6ad60fe55 --- /dev/null +++ b/openshift-artifacts/pvc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + annotations: + volume.beta.kubernetes.io/storage-provisioner: openshift.org/aws-efs + name: sftp-storage +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Gi + storageClassName: standard +status: {} diff --git a/sshd_config b/sshd_config new file mode 100644 index 0000000000000000000000000000000000000000..2858b4701f1bbc545e49eaccc10f2a8dd14d37f0 --- /dev/null +++ b/sshd_config @@ -0,0 +1,20 @@ +Protocol 2 +Port 2022 + +# Key for running sshd +HostKey /etc/ssh/ssh_host_ed25519_key + +# Limited access +PermitRootLogin no +X11Forwarding no +AllowTcpForwarding no + +# Force sftp and chroot jail +Subsystem sftp internal-sftp +ForceCommand internal-sftp + +# Regardless of explicit dir or var, os pod cannot chroot to chroot dir +#ChrootDirectory /home/sftpuser + +# Logging +LogLevel VERBOSE diff --git a/startup.sh b/startup.sh new file mode 100644 index 0000000000000000000000000000000000000000..67f99c5472cbb1484b8f619a6e13b90e51bbc230 --- /dev/null +++ b/startup.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# The following commands belong in this script, rather than in +# the dockerfile, because they should be executed as the uid that +# runs the runtime pod, rather than the build pod, which runs as root. +mkdir -p /home/sftpuser/.ssh /home/sftpuser/dir +ssh-keygen -f home/sftpuser/.ssh/ssh_host_ed25519_key -N '' -t ed25519 +chmod 400 /home/sftpuser/.ssh/ssh_host_ed25519_key* +mv /home/sftpuser/.ssh/ssh_host_ed25519_key.pub /home/sftpuser/.ssh/authorized_keys + +# https://www.ssh.com/ssh/sshd#command-line-options +# -e option is useful for logging to stderr. +# -D don't run as daemon, -d debugging mode +#exec /usr/sbin/sshd -D -d -e +exec /usr/sbin/sshd -D -e