diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1bc0034 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [Unreleased] + +## [0.3.1] - 2022-02-08 + +### Added +- Added a changelog + +[unreleased]: https://github.com/ibm/repo-template/compare/v0.0.1...HEAD +[0.3.1]: https://github.com/ibm/repo-template/releases/tag/v0.0.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5d7dab9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,84 @@ +## Contributing In General +Our project welcomes external contributions. If you have an itch, please feel +free to scratch it. + +To contribute code or documentation, please submit a [pull request](https://github.com/ibm/sftp-only-container/pulls). + +A good way to familiarize yourself with the codebase and contribution process is +to look for and tackle low-hanging fruit in the [issue tracker](https://github.com/ibm/sftp-only-container/issues). +Before embarking on a more ambitious contribution, please quickly [get in touch](#communication) with us. + +**Note: We appreciate your effort, and want to avoid a situation where a contribution +requires extensive rework (by you or by us), sits in backlog for a long time, or +cannot be accepted at all!** + +### Proposing new features + +If you would like to implement a new feature, please [raise an issue](https://github.com/ibm/sftp-only-container/issues) +before sending a pull request so the feature can be discussed. This is to avoid +you wasting your valuable time working on a feature that the project developers +are not interested in accepting into the code base. + +### Fixing bugs + +If you would like to fix a bug, please [raise an issue](https://github.com/ibm/sftp-only-container/issues) before sending a +pull request so it can be tracked. + +### Merge approval + +The project maintainers use LGTM (Looks Good To Me) in comments on the code +review to indicate acceptance. A change requires LGTMs from two of the +maintainers of each component affected. + +For a list of the maintainers, see the [MAINTAINERS.md](MAINTAINERS.md) page. + +## Legal + +Each source file must include a license header for the Apache +Software License 2.0. Using the SPDX format is the simplest approach. +e.g. + +``` +/* +Copyright All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +``` + +We have tried to make it as easy as possible to make contributions. This +applies to how we handle the legal aspects of contribution. We use the +same approach - the [Developer's Certificate of Origin 1.1 (DCO)](https://github.com/hyperledger/fabric/blob/master/docs/source/DCO1.1.txt) - that the Linux® Kernel [community](https://elinux.org/Developer_Certificate_Of_Origin) +uses to manage code contributions. + +We simply ask that when submitting a patch for review, the developer +must include a sign-off statement in the commit message. + +Here is an example Signed-off-by line, which indicates that the +submitter accepts the DCO: + +``` +Signed-off-by: John Doe +``` + +You can include this automatically when you commit a change to your +local git repository using the following command: + +``` +git commit -s +``` + +## Communication +**FIXME** Please feel free to connect with us on our [Slack channel](link). + +## Setup +**FIXME** Please add any special setup instructions for your project to help the developer +become productive quickly. + +## Testing +**FIXME** Please provide information that helps the developer test any changes they make +before submitting. + +## Coding style guidelines +**FIXME** Optional, but recommended: please share any specific style guidelines you might +have for your project. diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 0000000..1cc99f7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,55 @@ +# +# SFTP only Container - thomasw64/sshd +# +# Under Apache 2.0 License see LICENSE file. +# +# Copyright IBM 2021,2022 +# SPDX-License-Identifier: Apache2.0 +# +# Authors: +# - Thomas Weinzettl +# +#=============================================================================== + +# Choose from one of the two: +# ubi8-minimal:latest ..... if you have RH licenses +# fedora-minimal:latest ... if you prefer complete open source +# FROM registry.access.redhat.com/ubi8-minimal:latest +FROM registry.fedoraproject.org/fedora-minimal:latest + +LABEL org.opencontainers.image.title="SFTP only Container" +LABEL org.opencontainers.image.description="A container that allows to share docker/podman volumes via a secure SFTP only connection." +LABEL org.opencontainers.image.authors="thomasw@ae.ibm.com" +LABEL org.opencontainers.image.source="https://github.com/IBM/sftp-only-container.git" +LABEL org.opencontainers.image.vendor="IBM" +LABEL org.opencontainers.image.licenses="Apache-2.0" +#LABEL description="A ssh container with an simple method to import public keys" +LABEL org.opencontainers.image.version="0.3.1" + +RUN microdnf --nodocs -y install openssh-server sudo && \ + microdnf clean all + +RUN mkdir -p /home/.sshd/ && \ + chmod 700 /home/.sshd + +RUN sed -i "s/#PubkeyAuthentication yes/PubkeyAuthentication yes/g" /etc/ssh/sshd_config && \ + sed -i "s/PermitRootLogin yes/PermitRootLogin no/g" /etc/ssh/sshd_config && \ + sed -i "s/PasswordAuthentication yes/PasswordAuthentication no/g" /etc/ssh/sshd_config && \ + sed -i "s/GSSAPIAuthentication yes/GSSAPIAuthentication no/g" /etc/ssh/sshd_config && \ + sed -i "s/#PermitEmptyPasswords no/PermitEmptyPasswords no/g" /etc/ssh/sshd_config + +COPY entrypoint.sh /entrypoint.sh +COPY ssh-key.sh /bin/ssh-key.sh +COPY ssh-functions.sh /bin/ssh-functions.sh +COPY containeradm /bin/containeradm + +ENV SFTP_ONLY=no +ENV DEBUG=0 + +VOLUME ["/Volume","/home/"] + +EXPOSE 22 + +ENTRYPOINT ["/entrypoint.sh"] + +CMD ["/sbin/sshd","-D","-e"] diff --git a/LICENSE b/LICENSE index 261eeb9..7cb4cc8 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2021, 2022 IBM Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000..5fda93c --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,3 @@ +# MAINTAINERS + +Thomas Weinzettl - thomasw@ae.ibm.com diff --git a/README.md b/README.md index 3b90813..08a6ac8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,97 @@ + # sftp-only-container -container files and shell scripts for the IBM Developer tutorial sftp-only container for IBM zCX (or any other appliance-like container runtime). + + + + + +## Scope + +container files and shell scripts for the IBM Developer tutorial sftp-only +container for IBM zCX (or any other appliance-like container runtime). + +*TODO* add link to public site, once published. + +## Usage - Start the container + +### Requirements + +- IBM Z Container Extension (zCX) or other remote container runtime (docker or podman) e.g. podman machines on MacOS +- volume for `/home` to contain _authorized_keys_ for ssh public key +authentication +- volume for `/Volume` to host the hub of container volumes to mount onto + +### Start the container + +Here is an example to start the container. The `dummy_volume` is an example of +how to add another container volume to the sftp_only container. + +``` +$ docker run --name sftp-only --hostname sftp-only --rm -d -p 2022:22 \ +-v sftp-home:/home -v sftp-volume:/Volume -v dummy_volume:/Volume/dummy \ +-e SFTP_ONLY=yes thomasw/sftp-only:latest +``` + +| Environment Variable | Values | description | +| --- | --- | --- | +| SFTP_ONLY | yes / **no** | **Default:** no
Set to _yes_ if the container should restrict the access to sftp, and change the root to `/Volume` | +| DEBUG | **0** numeric | **Default:** 0 (for no output)
1 or higher is more verbose | + +## Usage User Administration + +This can be done on the running container with `docker exec` or on the `/home` +volume while stopped. + +``` +$ docker exec sftp-only containeradm +... +``` + +or + +``` +$ docker run --rm -v sftp-home:/home thomasw/sftp-only:latest containeradm +... +``` + +To get started you need to add a user and add his ssh public key like this: + +``` +$ docker exec sftp-only containeradm user add username +User username was added. +$ docker exec sftp-only containeradm key add \ +"username:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFHe4Aqe5RbyC1d7Zco+EI9Q4VUvtwcLEHHURK02pe+B test-key" +added key to user username +$ +``` + +Here is a list of the most important commands: + +| Task | Command | +| --- | --- | +| Add a user | `... containeradm user add username` or
`... containeradm user add username:1000:1000` | +| Delete a user | `... containeradm user del username` | +| List users | `... containeradm user list` | +| Add user to a group | `... containeradm user addgrp username groupname` | +| Remove user from a group | `... containeradm user rmgrp username groupname` | +| Add ssh public key | `... containeradm key "username:ssh-ed25119 AAAA...."` | +| List keys | `... containeradm key list username` | +| Dump the ssh config | `... containeradm showconfig` | +| Regenerate the hostkeys | `... containeradm hostkey refresh` + +## License + +The Dockerfiles and associated shell scripts are licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html) + +All source files must include a Copyright and License header. The SPDX license header is preferred because it can be easily scanned. + +If you would like to see the detailed LICENSE click [here](LICENSE). + +```text +# +# Copyright 2020- IBM Inc. All rights reserved +# SPDX-License-Identifier: Apache2.0 +# +``` + +[issues]: https://github.com/IBM/sftp-only-container/issues/new diff --git a/containeradm b/containeradm new file mode 100755 index 0000000..662d1cf --- /dev/null +++ b/containeradm @@ -0,0 +1,323 @@ +#!/bin/bash +# Under Apache 2.0 License see LICENSE file. +# +# Copyright IBM 2021,2022 +# SPDX-License-Identifier: Apache2.0 +# +# Authors: +# - Thomas Weinzettl +# +#=============================================================================== + +# We need to set DEBUG to 0 if it is not set in order to get the debug level +# right. +[ -z $DEBUG ] && DEBUG=0 || [ $DEBUG -ge 1 ] && echo "Container: Debug level=${DEBUG}" + + +# This shell script is helps to administer the container users as well as the +# ssh keys of the user and the host. +# +source /bin/ssh-functions.sh + +###### +# This function will print all possible users with their uid:gid settings +# the container is aware of in `id` format. +# +list_users() { + users=`ls /home/` + for user in $users ; do + if [ -d /home/${user} ]; then + # the `id` command helps us to print out the groups and ids + id ${user} + fi + done +} + +###### +# This function adds a ssh-key to a users authorized_keys file. It is split in two +# for security reasons. The acual add is executed with as the user whos key is to +# be added. +# +# We only parse the parameters, the format is "username:sshkey" +# +add_key(){ + IFS=":" read -r user key <<< "$@" + [ $DEBUG -ge 1 ] && echo "Container: DEBUG > add_key for user=${user} with key=${key:0:24}..." + + # + # We call here `sudo` as the user to add the key, to avoid any permission bit, or + # ownership issues. + sudo -E -u $user ssh-key.sh add $key +} + +###### +# This function lists the keys installed for one user +# $1 .. is the user name. +# +list_keys(){ + user=$1 + echo "Container: list keys for ${user}" + + # + # To avoid permission or ownership issues we call the list script as the user + sudo -E -u ${user} ssh-key.sh list +} + +###### +# This functions adds a new user to the container. It requires a username as parameter. +# Optionally uid and gid can be specified. +# +add_user() { + IFS=":" read -r user uid gid <<<$@ + + [ -z "$uid" ] && uid_cmd="" || uid_cmd="-u ${uid}" + + if [ -z "$gid" ]; then + gid_cmd="" + else + gid_cmd="-g ${gid}" + groupadd -g $gid $user + fi + + # -m needed to ensure we have the home-dir + useradd -m $uid_cmd $gid_cmd $user + echo "User $user was added." +} + +###### +# This functions adds a new group to the data. It requires a group name as +# parameter. Optionally a gid can be specified. +# +add_group() { + IFS=":" read -r group gid <<<$@ + + [ -z "$gid" ] && gid_cmd="" || gid_cmd="-g ${gid}" + + groupadd $gid_cmd $group + + echo "Group $group was added." +} + + +###### +# remove a user from a group +# +remove_from_group() { + IFS=" " read -r user group force <<< "$@" + [ $DEBUG -ge 1 ] && echo "Container: DEBUG > add_to_group user=${user},group=${group}" + + [ "$user" == "$group" ] && { + echo "Error: cannot remove primary group" + return + } + + if [ -f /home/${user}/.groups.d/${group} ]; then + rm /home/${user}/.groups.d/${group} + gpasswd -d $user $group + fi +} + +###### +# This function checks if a group exists +# +group_is_existing(){ + IFS=" " read -r group trash <<<$@ + result=0 + + [ -z "$group" ] || { + getent group $group >/dev/null && result=1 + } + + return $result +} + +###### +# This function adds a user to a group. It stores the group associations in +# $HOME/.groups.d/${groupname} by owning it by the user. +# +add_to_group() { + IFS=" " read -r user group <<< "$@" + [ $DEBUG -ge 1 ] && echo "Container: DEBUG > add_to_group user=${user},group=${group}" + + # We need to have an .groups.d directoy inside $HOME + if [ ! -d /home/${user}/.groups.d ]; then + sudo -u $user mkdir -p /home/${user}/.groups.d + sudo -u $user chmod 700 /home/${user}/.groups.d + fi + + group_is_existing $group + isexisting=$? + + [ $isexisting == 1 ] && { + gpasswd -a ${user} ${group} + sudo -u ${user} -g ${group} touch /home/${user}/.groups.d/${group} + sudo -u ${user} -g ${group} chmod 400 /home/${user}/.groups.d/${group} + } || { + echo "Group $group does not exist." + } + +} + +###### +# This function removes users +# +del_user(){ + IFS=" " read -r user <<<$@ + + userdel ${user} + + if [ -d /home/$user ]; then + rm -Rf /home/${user} + fi + + echo "User $user was deleted." +} + +###### +# This function regenerates the hostkeys. +# +hostkey_refresh(){ + rm -r /etc/ssh/ssh_host_*_key /etc/ssh/ssh_host_*_key.pub + ssh-keygen -A + store +} + +##### +# Print usage info +# +usage(){ + echo "containeradm ... usage" + echo "" + echo "# containeradm [object] [command] [parameter]" + echo "" + echo "Objects:" + echo " - user ....... to work with users." + echo " - group ...... to work with groups." + echo " - key ........ to work with public ssh keys." + echo " - hostkey .... to work with host keys." + echo " - showconfig . to show the current sshd_config" +} + +##### +# Print Usage info for user subcommand +usage_user(){ + echo "" + echo "Commands:" + echo " - list ..... to list users" + echo " no parameters" + echo " - add ...... to add a user" + echo " parameters: USERNAME[:[UID]:[GID]])" + echo " - del ...... to delete a user" + echo " parameters: USERNAME" + echo " - addgrp ... add to group" + echo " parameters: USERNAME GROUPNAME" + echo " - rmgrp .... remove from a group" + echo " parameters: USERNAME GROUPNAME" +} + +###### +# Print usage for key subcommand +# +usage_key(){ + echo "" + echo "Commands:" + echo " - list ..... to list a users key" + echo " parameters: USERNAME" + echo " - add ...... to add a key to a user" + echo " parameters: USERNAME:PUBLIC-SSH-KEY" +} + +###### +# Print usage for hostkey subcommand +usage_hostkey() { + echo "" + echo "Commands:" + echo " - refresh .. generates new host keys" +} + +###### +# Print usage for adding grouops +usage_group() { + echo "" + echo "Commands:" + echo " - add .... GROUPNAME[:GID] to add a group" +} + +################################################################################ +# main + +# Lets get the arguments and load them into our variables +read -r object command parameters <<<$@ + +#TODO: switch to case (replacing if) +case "$object" in + "user") + case "$command" in + "list") + list_users + ;; + + "add") + add_user ${parameters} + ;; + + "del") + del_user ${parameters} + ;; + + "addgrp") + add_to_group ${parameters} + ;; + + "rmgrp") + remove_from_group ${parameters} + ;; + + *|"help") + usage + usage_user + ;; + esac + ;; + + "group") + case "$command" in + "add") + add_group ${parameters} + ;; + + *|"help") + usage + usage_group + ;; + esac + ;; + + "key") + if [[ -z "$command" || "$command" == "help" ]]; then + usage + usage_key + elif [ "$command" == "list" ]; then + list_keys $parameters + elif [ "$command" == "add" ]; then + add_key $parameters + fi + ;; + + "hostkey") + if [[ -z "$command" || "$command" == "help" ]]; then + usage + usage_hostkey + elif [ "$command" == "refresh" ]; then + hostkey_refresh + fi + ;; + + "showconfig") + cat /etc/sshd/config + ;; + + *|"help") + usage + ;; +esac diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..e54e4d6 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,176 @@ +#!/bin/bash +# Under Apache 2.0 License see LICENSE file. +# +# Copyright IBM 2021,2022 +# SPDX-License-Identifier: Apache2.0 +# +# Authors: +# - Thomas Weinzettl +# +#=============================================================================== + +# +# We rely on having /run/pids available, as we will place the sshd pid file +# in there. +# +mkdir -p /run/pids/ + +source /bin/ssh-functions.sh + +[ -z $DEBUG ] && DEBUG=0 || echo "Container: Debug level=${DEBUG}" + +# +# This functions is called for every invocation of the container to re-construct +# the /etc/passwd and all users we need to have in there. +# This will create the group and user based on the following parameters: +# $1 ... is the username +# $2 ... is the uid +# $3 ... is the gid +# +# The password field will be set to '!locked', the ! will prevent from users +# using a password for login. +make_user() { + IFS=" " read -r user uid gid <<< "$@" + [ $DEBUG -ge 1 ] && echo "Container: DEBUG > make_user user=${user},uid=${uid},gid=${gid}" + + groupadd -g ${gid} ${user} 2>/dev/null + useradd -m -u ${uid} -g ${gid} ${user} 2>/dev/null + echo "${user}:!locked" | chpasswd -e +} + +# +# This function orchestrates the recreation of all passwd entries for all users +# we need. The volume mounted on /home and the directory in there (with their +# uid:gid settings will describe which users to create) +# +init_users() { + users=`ls /home/` + for user in $users ; do + if [ -d /home/${user} ]; then + uid=`stat -c '%u' /home/${user}` + gid=`stat -c '%g' /home/${user}` + + make_user ${user} ${uid} ${gid} + + if [ -d "/home/${user}/.groups.d" ]; then + groups=`ls /home/${user}/.groups.d/` + [ $DEBUG -ge 1 ] && echo "Container: DEBUG> user=${user} has this groups=(${groups})" + + for group in $groups ; do + #TODO: Need to fix. What if a group does not exist. + + # To make sure we are not tricked into a group that the user + # is not added to, we read it from the file + grp=`stat -c '%G' /home/${user}/.groups.d/${group}` + + # gpasswd needs a silencer (>/dev/null) or writes a message + # like "Adding xxx to group yyy" + gpasswd -a ${user} ${grp} >/dev/null + done + fi + fi + done +} + +# +# This functions modifies the sshd_config to support sftp only access in a chrooted +# environment. +# +set_config_sftp_only(){ + sed -i "s/Subsystem sftp \/usr\/libexec\/openssh\/sftp-server/Subsystem sftp internal-sftp/g" /etc/ssh/sshd_config + sed -i "s/#ChrootDirectory none/ChrootDirectory \/Volume\//g" /etc/ssh/sshd_config + sed -i "s/X11Forwarding yes/X11Forwarding no/g" /etc/ssh/sshd_config + sed -i "s/#AllowTcpForwarding yes/AllowTcpForwarding no/g" /etc/ssh/sshd_config + echo "ForceCommand internal-sftp" >> /etc/ssh/sshd_config + + owner=`stat -c '%u' /Volume` + if [ "${owner}" != "0" ]; then + echo "Container: WARNING /Volume has not the proper ownership" + fi + + owngrp=`stat -c '%g' /Volume` + if [ "${owngrp}" != "0" ]; then + echo "Container: WARNING /Volume has not the proper group ownership" + fi + + perm=`stat -c '%a' /Volume` + if [ "${perm}" != "755" ]; then + echo "Container: WARNING /Volume has not the proper permission bits" + fi +} + +# +# Stop the SSHD Daemon +# +stop_ssh() { + pid=`cat /run/pids/sshd.pid` + echo "Container: stopping sshd pid=${pid}" + kill -s TERM $pid + wait $pid +} + +# +# Start the SSHD Daemon +# +start_ssh() { + echo "Container: starting ssh with command '$@'" + $@ & + pid=$! + echo -n $pid >/run/pids/sshd.pid + echo "Container: sshd (pid=${pid}) started..." + wait ${pid} + exit $? +} + +# +# running the command +# + +# +# First we need to recreate our /etc/passwd and all users that has to be in there +init_users + +# +# If `-e SFTP_ONLY=yes` was specified we change the sshd_config to reflect that. +if [ "$SFTP_ONLY" == "yes" ]; then + echo "Container: SFTP_ONLY is set to ${SFTP_ONLY}" + set_config_sftp_only +fi + +# +# We need to make sure we have ssh keys. +[ $DEBUG -ge 1 ] && echo "Container: DEBUG> checking host keys." +load_host_keys +if [ ! -e /etc/ssh/ssh_host_ed25519_key ]; then + [ $DEBUG -ge 1 ] && echo "Container: DEBUG> generate host keys." + /bin/ssh-keygen -A + store_host_keys +fi + +# +# Now let us figure out what the container is suppose to do today. +if [ "$1" == "help" ]; then + echo "HELP:" + echo "" + echo "supported commands:" + echo "sshd or nothing ... start sshd daemon" + echo "containeradm ...... add or delete users, add keys, ..." + echo "show-config ... show the sshd config file" +elif [ "`basename $1`" == "sshd" ];then + trap stop_ssh SIGTERM SIGINT + + start_ssh $@ +elif [ "$1" == "containeradm" ]; then + shift + /bin/containeradm $@ +elif [ "$1" == "show-config" ]; then + cat /etc/ssh/sshd_config +else + [ "$SFTP_ONLY" == "yes" ] && { + echo "Container runs in SFTP only mode. Command access restricted." + echo "use 'containeradm' command to manage." + } || { + exec $@ + } + +fi diff --git a/sftp.batch b/sftp.batch new file mode 100644 index 0000000..25c4d6f --- /dev/null +++ b/sftp.batch @@ -0,0 +1,8 @@ +mkdir Containers +mkdir Containers/sftp-only +cd Containers/sftp-only +put Dockerfile +put entrypoint.sh +put containeradm +put ssh-functions.sh +put ssh-key.sh diff --git a/ssh-functions.sh b/ssh-functions.sh new file mode 100755 index 0000000..81223b8 --- /dev/null +++ b/ssh-functions.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Under Apache 2.0 License see LICENSE file. +# +# Copyright IBM 2021,2022 +# SPDX-License-Identifier: Apache2.0 +# +# Authors: +# - Thomas Weinzettl +# +#=============================================================================== + + +store_host_key() { + read -r keyname to loc other <<<$@ + + [ -e /etc/ssh/ssh_host_${keyname}_key ] && \ + cp -p -f /etc/ssh/ssh_host_${keyname}_key* /home/.sshd/ +} + +load_host_key() { + read -r keyname to loc other <<<$@ + + [ -e /home/.sshd/ssh_host_${keyname}_key ] && \ + cp -p -n /home/.sshd/ssh_host_${keyname}_key* /etc/ssh/ + +} +# +# +# +store_host_keys() { + keys="dsa rsa ecdsa ed25519" + for key in $keys ; do + store_host_key $key + done +} + +load_host_keys(){ + keys="dsa rsa ecdsa ed25519" + for key in $keys ; do + load_host_key $key + done +} diff --git a/ssh-key.sh b/ssh-key.sh new file mode 100755 index 0000000..e0ab5f3 --- /dev/null +++ b/ssh-key.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# THIS IS TO BE RUN AS USER *** NOT AS ROOT +# +# Under Apache 2.0 License see LICENSE file. +# +# Copyright IBM 2021,2022 +# SPDX-License-Identifier: Apache2.0 +# +# Authors: +# - Thomas Weinzettl +# +#=============================================================================== + +# +# The ssh directory must exist and has to have permissions +# u:rwx g:--- o:--- aka 700 +# +fix_ssh_dir() { + # this helps us to capture othersuff someone could send over what we do not + # want to pass to chmod. + IFS=" " read -r sshdir otherstuff <<<"$@" + + if [ ! -d $sshdir ]; then + mkdir -p $sshdir + fi + + chmod 700 $sshdir +} + +# +# Add a public ssh key to the authorized_keys file, but only if it is a real +# key file. +add_key() { + # key needs to capture all the rest. In this case it is save, as we stuff it + # into a temp keyfile and check it with ssh-keygen -lf, so garbage keys + # won't pass the test. + IFS=" " read -r sshdir key <<<"$@" + + [ $DEBUG -gt 1 ] \ + && echo "ssh-key ($USER) DEBUG: add_key dir=${sshdir} key=${key:0:24}" + + fix_ssh_dir $sshdir + + # We want a temp keyfile, just to check if the key we got passed over is + # acutally a ssh public key. + keyfile=`mktemp /tmp/keyfile.XXXXXXXX` + echo "$key" >>$keyfile + + # -lf will verify the keys and hash them. It will come back with none-0 + # return code if the key is not valid. + ssh-keygen -lf $keyfile >/dev/null 2>&1 || { + echo "ERROR: add_key ${key:0:24}... is not a valid public ssh key." + rm $keyfile + exit 1 + } + + authorized_keys=${sshdir}/authorized_keys + # if the keyfile is there we make it writable for short. + [ -e $authorized_keys ] && chmod 600 $authorized_keys + # let us not trust the temp file, as someone could have written to it. We + # take the key that we have written into the temp file. + echo ${key} >> $authorized_keys + chmod 644 $keyfile + rm $keyfile + + # lets make the keyfile read-only again + chmod 400 $authorized_keys + echo "added key to user $USER" +} + +# +# List all the keys for the current user. +list_keys(){ + # we make sure we do not pass othergarbage to ssh-keygen + IFS=" " read -r sshdir othergarbage <<<"$@" + + [ $DEBUG -ge 1 ] && echo "DEBUG: list_keys dir=${sshdir} " + + fix_ssh_dir $sshdir + authorized_keys=${sshdir}/authorized_keys + [ -e $authorized_keys ] \ + && ssh-keygen -lf $authorized_keys \ + || echo "No keys found." +} + +# +# We will not allow to run this as root, so that no-one introduces a ssh +# key to a root user. +effective_user=`id -u` +if [ "${effective_user}" -le 0 ]; then + echo "$0 : please run as user" + echo "$0 : Current user is root, this is not permited." + exit 1 +fi + +# +# Here comes the main program ... +function=$1 +shift + +if [ -z $function ]; then + echo "usage: ssh-key.sh [command]" + echo " " + echo "add .... add a key to a user" + echo "list ... list the keys of a user" +elif [ "$function" == "add" ]; then + add_key $HOME/.ssh "$@" +elif [ "$function" == "list" ]; then + list_keys $HOME/.ssh +fi