Rocky 8 Setup =============== Goal ---- We needed a full-blown setup for running some SFTP/SSHFS performance benchmarks on an internet host and decided to do so on Rocky 8, which comes with a number of throughput and crypto performance optimizations compared to our legacy CentOS 7 platforms. In the meantime we have added support for Rocky 9 as well and intend to go with that distribution for our own sites. So the use of 8 here is mainly historical and NOT a specific limitation. Overview -------- In the particular setup we rely on Rocky 8 running virtualized in KVM on a physical Rocky 8 host. The underlying storage is provided by a remote Lustre 2.12 setup but local storage or another network file system should also do the job. Newer stable versions of Lustre have been released in the meantime and should also work. Communication takes place on a private local network e.g. using a 172.x.y.z address on the first physical network interface. System Setup ------------ First we set up the basic system to fit our needs with hardening and dependency installations. First upgrade all system packages and prevent SELinux interference:: sudo dnf upgrade sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config sudo reboot Then install additional helpers:: sudo dnf install util-linux-user zsh screen nano emacs-nox vim-enhanced \ net-tools rsyslog stow htop lsof rsync fail2ban ipset wget git make Make sure the VM has the four or more IPs needed for the most common virtual migrid hosts (www, sid, io, ext, oid, oidc and cert). The latter two may be left out if you don't have access to external OpenID 2.0 OpenID Connect or X509 certificate authentication infrastructure. You can still use the system with manual user sign up and semi-automatic operator user account acceptance. Prepare PodMan version of Docker and Docker Compose:: sudo dnf erase docker docker-compose sudo dnf install podman podman-docker podman-compose sudo dnf install fuse-overlayfs Harden sshd to use strong crypto in line with Mozilla recommendations and limit remote log in to keys. If possible it's also recommended to only allow log in through a DMZ jump host. Please note that Rocky 8 uses a central systemd conf in /etc/crypto-policies/back-ends/opensshserver.config for configuring Ciphers, KexAlgorithms and MACs rather than the usual /etc/sshd/sshd_config. You can remove those and check that the sshd process does not run with them hard-coded on the command line based on the output of:: ps ax|grep sshd TODO: Install logcheck or similar and postfix with authenticity adjustments Prepare LetsEncrypt certificates with the getssl tool:: VERSION=git-$(date +'%Y%m%d-%H%M%S') sudo mkdir -p /usr/local/packages/getssl-$VERSION/sbin sudo chown -R $(id -nu) /usr/local/packages/getssl-$VERSION cd /usr/local/packages/getssl-$VERSION/sbin sudo wget https://raw.githubusercontent.com/srvrco/getssl/master/getssl sudo chmod 700 getssl sudo chown 0:0 /usr/local/packages/getssl-$VERSION cd /usr/local/packages sudo stow -v --override='sbin/getssl' getssl-$VERSION Setup root account for LetsEncrypt use:: wget https://raw.githubusercontent.com/ucphhpc/migrid-sync/edge/mig/install/mig-user/.vimrc getssl -c bench.erda.dk mkdir -p /etc/httpd/MiG-certificates/letsencrypt chmod -R 755 /etc/httpd/MiG-certificates { echo 'CSR_SUBJECT="/C=DK/O=University of Copenhagen/OU=Benchmark Electronic Research Data Archive/CN=bench.erda.dk"' echo "ACL=('/home/mig/state/wwwpublic/letsencrypt/.well-known/acme-challenge')" echo 'USE_SINGLE_ACL="true"' echo 'SERVER_TYPE="https"' echo 'DOMAIN_CERT_LOCATION="/etc/httpd/MiG-certificates/letsencrypt/bench.erda.dk/server.crt"' echo 'DOMAIN_KEY_LOCATION="/etc/httpd/MiG-certificates/letsencrypt/bench.erda.dk/server.key"' echo 'CA_CERT_LOCATION="/etc/httpd/MiG-certificates/letsencrypt/bench.erda.dk/server.ca.pem"' echo 'DOMAIN_CHAIN_LOCATION="/etc/httpd/MiG-certificates/letsencrypt/bench.erda.dk/server.cert.ca.pem"' echo 'DOMAIN_PEM_LOCATION="/etc/httpd/MiG-certificates/letsencrypt/bench.erda.dk/server.key.crt.ca.pem"' } >> /root/.getssl/bench.erda.dk/getssl.cfg sed -i 's/=www.bench.erda.dk/=bench-www.erda.dk,bench-sid.erda.dk,bench-io.erda.dk,bench-ext.erda.dk,bench-oid.erda.dk,bench-oidc.erda.dk,bench-cert.erda.dk,bench-wayf.erda.dk/g' /root/.getssl/bench.erda.dk/getssl.cfg To avoid having to manually update key and certificate fingerprints in the generated MiGserver.conf you'll want to use a persistent key for SFTP and automate certificate fingerprint update if using the frequently renewed LetsEncrypt certificates as described above for the FTPS and WebDAVS services. You can e.g. use the generated `migcheckssl` cron job with the `getssl` tool from https://github.com/srvrco/getssl to do that, or write your own hooks to generate the fingerprints upon renewal if you prefer to use `certbot` or other tools for managing and renewing your LetsEncrypt certificates. You can find the `ssh-keygen` and `openssl` commands to extract fingerprints of the SSH/SFTP public key and X509 certificates respectively in that same generated `migcheckssl` cronjob or in the template it's generated from at https://github.com/ucphhpc/migrid-sync/blob/next/mig/install/migcheckssl-template.sh.cronjob The important part is to note that docker-migrid is set up to read the fingerprints from `FILE::/etc/httpd/MiG-certificates/combined.pub.md5` `FILE::/etc/httpd/MiG-certificates/combined.pub.sha256` and `FILE::/etc/httpd/MiG-certificates/combined.pem.sha256` respectively corresponding to the `certs/combined.pub.md5`, `certs/combined.pub.sha256` and `certs/combined.pem.sha256` files in your docker-migrid root directory on the host.If those files are maintained the user Setup SFTP/WebDAVS/FTPS page tabs will always show the current fingerprint. Please note that SSH/SFTP public keys are a bit tedious for the clients to verify. We therefore recommend distributing the SFTP public key fingerprint over DNS(SEC) with SSHFP records even for static keys and more so if dynamic. Set up DNS for the 4 or more IPs needed for the virtual hosts (130.225.104.110-117 used here) and configure the VM network to listen on those IPs e.g. on the second physical network interface (e.g. eth1 as here):: { echo 'TYPE="Ethernet"' echo 'GATEWAY="130.225.104.1"' echo 'BOOTPROTO="none"' echo 'DEFROUTE="yes"' echo 'IPV4_FAILURE_FATAL="no"' echo 'IPV6_FAILURE_FATAL="no"' echo 'NAME="eth1"' echo 'DEVICE="eth1"' echo 'ONBOOT="yes"' echo 'MTU=1500' echo 'NM_CONTROLLED=yes' echo 'USERCTL=no' echo 'IPV6_AUTOCONF="no"' echo 'IPV6INIT="no"' echo 'IPADDR="130.225.104.110"' echo 'NETMASK="255.255.255.0"' echo 'IPADDR2="130.225.104.111"' echo 'NETMASK2="255.255.255.0"' echo 'IPADDR3="130.225.104.112"' echo 'NETMASK3="255.255.255.0"' echo 'IPADDR4="130.225.104.113"' echo 'NETMASK4="255.255.255.0"' echo 'IPADDR5="130.225.104.114"' echo 'NETMASK5="255.255.255.0"' echo 'IPADDR6="130.225.104.115"' echo 'NETMASK6="255.255.255.0"' echo 'IPADDR7="130.225.104.116"' echo 'NETMASK7="255.255.255.0"' echo 'IPADDR8="130.225.104.117"' echo 'NETMASK8="255.255.255.0"' } > /etc/sysconfig/network-scripts/ifcfg-eth1 ifup eth1 Make sure the local firewall allows http and https access:: pgrep firewalld > /dev/null && { sudo firewall-cmd --permanent --zone=public --add-service=ssh sudo firewall-cmd --permanent --zone=public --add-service=http sudo firewall-cmd --permanent --zone=public --add-service=https sudo firewall-cmd --reload } Generate initial server certificates with a simple python web server:: mkdir -p /home/mig/state/wwwpublic/letsencrypt/.well-known/acme-challenge screen -S simple-httpd -xRD cd /home/mig/state/wwwpublic/letsencrypt/ python3 -m http.server 80 & [ctrl-a d] getssl --force bench.erda.dk screen -S simple-httpd -xRD [ctrl-c] [ctrl-d] cd /etc/httpd/MiG-certificates/ curl https://ssl-config.mozilla.org/ffdhe4096.txt -o dhparams.pem chmod 755 letsencrypt/bench.erda.dk ln -s letsencrypt/bench.erda.dk . for dom in www sid io ext oid oidc cert wayf; do ln -s letsencrypt/bench.erda.dk bench-${dom}.erda.dk; done ln -s bench.erda.dk/server.crt . ln -s bench.erda.dk/server.key . openssl rsa -in bench.erda.dk/server.key -text > bench.erda.dk/server.pem chmod 400 bench.erda.dk/server.pem chown mig:mig bench.erda.dk/combined.pem cat bench.erda.dk/server.pem bench.erda.dk/server.cert.ca.pem > bench.erda.dk/combined.pem chmod 400 bench.erda.dk/combined.pem ssh-keygen -y -f bench.erda.dk/combined.pem > bench.erda.dk/combined.pub openssl x509 -noout -fingerprint -sha256 -in bench.erda.dk/combined.pem | \ sed 's/.* Fingerprint=//g' > bench.erda.dk/combined.pem.sha256 \ ssh-keygen -l -E md5 -f bench.erda.dk/combined.pub | \ sed 's/.* MD5://g;s/ .*//g' > bench.erda.dk/combined.pub.md5 \ ssh-keygen -l -f bench.erda.dk/combined.pub | \ sed 's/.* SHA256://g;s/ .*//g' > bench.erda.dk/combined.pub.sha256 ln -s bench-io.erda.dk/combined.pem* . ln -s bench-io.erda.dk/combined.pub* . Prepare an unprivileged `mig` account for running docker-migrid using the podman docker wrappers. In that relation we need to disable Jupyter to avoid a problem with support for the complex JUPYTER_SERVICE_DESC env argument:: sudo adduser mig chsh mig -s /usr/bin/zsh su - mig mv .zshrc{,.orig} wget https://raw.githubusercontent.com/ucphhpc/migrid-sync/edge/mig/install/mig-user/.zshrc wget https://raw.githubusercontent.com/ucphhpc/migrid-sync/edge/mig/install/mig-user/.vimrc . ~/.zshrc mkdir -p ~/bin cd ~/bin/ && ln -s /usr/bin/podman-compose docker-compose git clone https://github.com/ucphhpc/docker-migrid.git docker-migrid cd docker-migrid ln -s /etc/httpd/MiG-certificates . ln -s MiG-certificates certs sed 's/dev\([a-z*-]*\)\.erda\.dk/bench\1.erda.dk/g' \ docker-compose_dev.erda.dk_full.yml > \ docker-compose_bench.erda.dk_full.yml ln -s docker-compose_bench.erda.dk_full.yml docker-compose.yml sed 's/dev\([a-z*-]*\)\.erda\.dk/bench\1.erda.dk/g' \ advanced_dev.erda.dk_full.env | \ sed 's/^ENABLE_JUPYTER=True/ENABLE_JUPYTER=False/g' > \ advanced_bench.erda.dk_full.env ln -s advanced_bench.erda.dk_full.env .env make podman-compose -t hostnet up Lustre ------ Install the Lustre client build dependencies:: sudo dnf config-manager --set-enabled powertools sudo dnf -y groupinstall "Development Tools" sudo dnf -y install net-snmp-devel libyaml-devel libselinux-devel libtool sudo dnf -y install kernel-devel-$(uname -r) kernel-rpm-macros kernel-abi-whitelists Build and install the Lustre client:: VERSION=2.12.8 git clone git://git.whamcloud.com/fs/lustre-release.git cd lustre-release git checkout ${VERSION} sh ./autogen.sh ./configure --disable-server --enable-quota --enable-utils --enable-gss make rpms sudo yum remove lustre-client.x86_64 kmod-lustre-client.x86_64 sudo yum localinstall -y ./kmod-lustre-client-${VERSION}-1.el8.x86_64.rpm sudo yum localinstall -y ./lustre-client-${VERSION}-1.el8.x86_64.rpm sudo mv /etc/lnet.conf.rpmsave /etc/lnet.conf sudo service lnet stop sudo lustre_rmmod sudo service lnet start sudo systemctl enable lnet WAYF OpenID Connect Sign Up and Log In -------------------------------------- By default the site will use only the locally managed user database with OpenID 2.0 login through the built-in grid_openid service. One can configure additional authentication methods like X509 user certificates or OpenID 2.0 or OpenID Connect from external ID providers. One such international ID provider with OpenID Connect access is WAYF (https://www.wayf.dk/). In order to access it for authentication purposes it is necessary to get in touch with the WAYF admins and exchange public keys / certificates for the host authentication involved. This includes a bit of configuration on their PHPH self-service interface (https://phph.wayf.dk/). One preparation step is to create a private key and a self-signed certificate e.g. with `openssl`:: [root@bench /]# mkdir -p /etc/httpd/MiG-certificates/wayf.dk/4k [root@bench /]# cd /etc/httpd/MiG-certificates/wayf.dk/4k [root@bench 4k]# openssl req -x509 -newkey rsa:4096 -keyout key.pem \ -out cert.pem -sha256 -days 3650 Generating a 4096 bit RSA private key ....................................................................++ ........................++ writing new private key to 'key.pem' Enter PEM pass phrase: Verifying - Enter PEM pass phrase: ----- You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [XX]:DK State or Province Name (full name) []: Locality Name (eg, city) [Default City]:Copenhagen Organization Name (eg, company) [Default Company Ltd]:KU Organizational Unit Name (eg, section) []:Science HPC Center Common Name (eg, your name or your server's hostname) []:bench-wayf.erda.dk Email Address []: [root@bench 4k]# For Apache we may not want to enter passphrase to launch the service. A key without passphrase can be extracted with:: openssl rsa -in /etc/httpd/MiG-certificates/wayf.dk/4k/key.pem \ -out /etc/httpd/MiG-certificates/wayf.dk/4k/key-nopw.pem Such keys should of course not be generally readable. So we properly protect them with:: [root@bench 4k]# chmod 400 key*.pem We got the corresponding certificate from WAYF and saved it in `/etc/httpd/MiG-certificates/wayf.dk/wayf-idp.pem` After handling the key and auth service setup we configured our site to allow the WAYF OpenID Connect service as a valid ID provider for users wanting to access our site as explained below. Some of the docker-migrid variables needed for the purpose were added in May 2024. It is tested to work with docker-migrid from late May and a recent `migrid-sync` version. In our `.env` file we use these WAYF related variables:: EXTOIDC_DOMAIN=bench-wayf.erda.dk EXT_OIDC_PROVIDER_META_URL="https://${PUBLIC_DOMAIN}/.well-known/wayf-openid-configuration" EXT_OIDC_TITLE="WAYF" EXT_OIDC_CLIENT_NAME="" EXT_OIDC_CLIENT_ID="http://erda.dk" EXT_OIDC_SCOPE="" EXT_OIDC_REMOTE_USER_CLAIM=sub EXT_OIDC_PASS_CLAIM_AS="both" EXT_OIDC_PKCE_METHOD=S256 EXT_OIDC_PROVIDER_ISSUER="https://wayf.wayf.dk" EXT_OIDC_PROVIDER_AUTHORIZATION_ENDPOINT="https://wayf.wayf.dk/saml2/idp/SSOService2.php" EXT_OIDC_PROVIDER_TOKEN_ENDPOINT="https://wayf.wayf.dk/token" EXT_OIDC_PROVIDER_USER_INFO_ENDPOINT="https://wayf.wayf.dk/token" EXT_OIDC_PROVIDER_TOKEN_ENDPOINT_AUTH=none EXT_OIDC_USER_INFO_TOKEN_METHOD=post_param EXT_OIDC_USER_INFO_SIGNED_RESPONSE_ALG=RS256 EXT_OIDC_COOKIE_SAME_SITE="Off" EXT_OIDC_PASS_COOKIES="wayfid" EXT_OIDC_RESPONSE_MODE=form_post EXT_OIDC_PROVIDER_VERIFY_CERT_FILES="/etc/httpd/MiG-certificates/wayf.dk/wayf-idp.pem" EXT_OIDC_PRIVATE_KEY_FILES="wayf#/etc/httpd/MiG-certificates/wayf.dk/4k/key-nopw.pem" EXT_OIDC_PUBLIC_KEY_FILES="wayf#/etc/httpd/MiG-certificates/wayf.dk/4k/cert.pem" EXT_OIDC_ID_TOKEN_ENCRYPTED_RESPONSE_ALG=RSA-OAEP EXT_OIDC_ID_TOKEN_ENCRYPTED_RESPONSE_ENC=A256GCM EXT_OIDC_REWRITE_COOKIE="wayfid:wayf-qa:.erda.dk:0:/:true:true" EXTOIDC_HTTPS_PORT=443 SIGNUP_METHODS="migoid extcert extoid extoidc" LOGIN_METHODS="migoid extcert extoid extoidc" AUTO_ADD_OIDC_USER=True AUTO_ADD_USER_PERMIT="email:[a-z0-9.]+@([a-z]+\.|)ku\.dk$" After building and launching the containers WAYF access is available through the `login` and `signup` backends on the `SID_DOMAIN`. In this case: https://bench-sid.erda.dk/cgi-sid/signup.py?show=extoidc and https://bench-sid.erda.dk/cgi-sid/login.py?show=extoidc Please note that there are a few remarks to add about the variable values. First of all the `EXT_OIDC_CLIENT_ID` is a value negotiated with WAYF. You'll need your own one to map your site(s) to your WAYF (self-service) setup. The same applies for the site suffix part of the `EXT_OIDC_REWRITE_COOKIE` value, where you should replace `.erda.dk` with your configured domain. Regarding the key/cert negotiation we have the three CERT and KEY variables to point to the WAYF certificate plus our own private key and certificate. The former is `EXT_OIDC_PROVIDER_VERIFY_CERT_FILES` and the latter is `EXT_OIDC_PRIVATE_KEY_FILES` and `EXT_OIDC_PUBLIC_KEY_FILES`. The `EXT_OIDC_PROVIDER_META_URL` value is used to point out a OpenID Connect specific discovery URL where the service optionally presents all available settings and parameters. At the time of writing we did not have such a service from WAYF, so in order to get a more straight-forward integration we added a mock one in the form of a simple json file served from our own site. You should either make your own copy based on data from WAYF or use any future discovery service they add. Finally you should decide on a site basis if you want automatic creation of authenticated users or not with the `AUTO_ADD_OID_USER` and `AUTO_ADD_OIDC_USER` variables and likely limit it to certain users or organizations with the `AUTO_ADD_USER_PERMIT` variable. The `AUTO_ADD_OID_USER` variable used to be shared for enabling both OpenID 2.0 and OpenID Connect sign up without operator interaction, but to increase control it was split up with `AUTO_ADD_OIDC_USER` added for the latter. Please note that you may need a version released in July 2024 or later for the split up to fully take effect. If you enable any `AUTO_ADD_X_USER` you can provide a regular expression to limit which such externally authenticated users are actually permitted to use this auto sign up. The variable contains a space-separated list of colon-separated pairs, where each pair is as user field name and a regular expression to require matched for that user field. By default it is set to `distinguished_name:.*`, which will match *any* user ID. To only let authenticated users with an `@yourdomain.org` email address and `Staff` role sign up without operator interaction you can use something like: `AUTO_ADD_USER_PERMIT="email:.+@yourdomain\.org$ role:^Staff$"`