From 3dc247767d274e87d26850150559a9b8fce5a51f Mon Sep 17 00:00:00 2001 From: Pino de Candia Date: Mon, 12 Mar 2018 16:50:31 +0000 Subject: [PATCH] Enable pam-ussh module to check user ssh cert on sudo authentication. Change-Id: Iffde339572885b21673731dd69fb9b2ba4df6073 Signed-off-by: Pino de Candia --- README.rst | 20 +++++++++++------ devstack/plugin.sh | 1 + files/user-cloud-config | 50 ++++++++++++++++++++++++++++++++++++----- tatu/api/models.py | 1 + tatu/config.py | 2 ++ 5 files changed, 62 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index e835fc2..3045d7e 100644 --- a/README.rst +++ b/README.rst @@ -162,14 +162,20 @@ KeyPairs continue to work as designed, which is useful for debugging Tatu or having a fallback method to access the VMs. Tatu's policy is that any role containing the word "admin" results in a user -account with passwordless sudo privileges. Thanks to the uber/pam-ussh -integration (not yet merged as of March 9, 2018) sudo privilege is revoked as -soon as the VM learns that the user's certificate has been revoked. However, -uber/pam-ussh requires the client to run ssh-agent and ssh-add their -certificate. +account with sudo privileges. Note that because of this policy, an OpenStack +user may not have sudo privileges on VMs she herself launched. -Note that because of this policy, an OpenStack user may not have sudo -privileges on VMs she herself launched. +Uber's pam-ussh module +---------------------- + +Thanks to the uber/pam-ussh integration sudo privilege is revoked as soon as +the VM learns that the user's certificate has been revoked. However, +uber/pam-ussh requires the client to run ssh-agent, ssh-add their key +(corresponding to their certificate) and launch ssh with the -A option. + +This feature is enabled/disabled by setting pam_sudo to True/False in tatu's +configuration. When the feature is disabled, sudo access is not authenticated, +it's password-less (since we don't use passwords in our user account setup). Bastion Management ------------------ diff --git a/devstack/plugin.sh b/devstack/plugin.sh index c22f8d9..e1c78e3 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -53,6 +53,7 @@ function configure_tatu { iniset $TATU_CONF tatu pat_dns_zone_email $TATU_DNS_ZONE_EMAIL iniset $TATU_CONF tatu sqlalchemy_engine `database_connection_url tatu` iniset $TATU_CONF tatu api_endpoint_for_vms $TATU_API_FOR_VMS + iniset $TATU_CONF tatu pam_sudo True # Need Keystone and Nova notifications iniset $KEYSTONE_CONF oslo_messaging_notifications topics notifications,tatu_notifications diff --git a/files/user-cloud-config b/files/user-cloud-config index 5c31225..009d561 100644 --- a/files/user-cloud-config +++ b/files/user-cloud-config @@ -20,6 +20,9 @@ write_files: host_id=$(echo $metadata | grep -Po 'uuid": "\K[^"]*') echo host_id=$host_id vendordata=$(cat /mnt/config/openstack/latest/vendor_data2.json) + pam_sudo=$(echo $vendordata | grep -Po '"pam_sudo": \K[^,]*' | tr '[:upper:]' '[:lower:]') + pam_sudo=${pam_sudo:-false} + echo pam_sudo=$pam_sudo token=$(echo $vendordata | grep -Po '"token": "\K[^"]*') if [ -z $token ]; then echo Failed to extract the Tatu token ID from vendordata @@ -68,14 +71,19 @@ write_files: adduser $i fi done + x=0 for i in ${sudoers//,/ }; do - if [ $(getent group sudo) ]; then - usermod -aG sudo $i - fi - if [ $(getent group wheel) ]; then - usermod -aG wheel $i + prefix=$((130 + x++)) + if [ "$pam_sudo" = true ]; then + echo $i ALL= ALL > /etc/sudoers.d/$prefix-$i + echo Defaults:$i timestamp_timeout=1 >> /etc/sudoers.d/$prefix-$i + else + echo $i ALL= NOPASSWD: ALL > /etc/sudoers.d/$prefix-$i fi done + if [ "$pam_sudo" = true ]; then + (crontab -l; echo "* * * * * /root/tatu-setup-pam.sh >> /var/log/tatu-setup-pam.log") | crontab - + fi sed -i -e '$aTrustedUserCAKeys /etc/ssh/ca_user.pub' /etc/ssh/sshd_config # man sshd_config, under AuthorizedPrincipalsFile: The default is none, i.e. not to use a principals file # – in this case, the username of the user must appear in a certificate's principals list for it to be accepted. @@ -89,6 +97,38 @@ write_files: sed -i -e '$aPort 22' /etc/ssh/sshd_config setenforce permissive systemctl restart sshd + echo Completed! + - path: /root/tatu-setup-pam.sh + permissions: '0700' + owner: root:root + content: | + #!/bin/bash + # Name: tatu-setup-pam.sh + # + # Purpose: Install uber/pam-ussh so that every sudo call authenticates the user's certificate in the background. + # If the user's certificate expires or is revoked, sudo authentication will fail. They lose sudo privileges even if + # they are still logged in. + crontab -l | grep -v 'tatu-setup-pam' | crontab - + cd /root + rm -rf /root/pam-ussh + inst=`which dnf` + inst=${inst:-`which yum`} + inst=${inst:-`which apt-get`} + $inst install -y pam-devel golang git + git clone https://github.com/pinodeca/pam-ussh + cd pam-ussh + git checkout -b krl origin/krl + export GOPATH=/root/pam-ussh/.go + go get golang.org/x/crypto/ssh + go get github.com/stretchr/testify/require + go get github.com/stripe/krl + make + mv pam_ussh.so /lib64/security/ + vendordata=$(cat /mnt/config/openstack/latest/vendor_data2.json) + sudoers=$(echo $vendordata | grep -Po '"sudoers": "\K[^"]*') + echo setting up pam-ussh sudo authentication for $sudoers + sed -i -e '/auth.*pam_unix/i \ + auth sufficient /lib64/security/pam_ussh.so ca_file=/etc/ssh/ca_user.pub authorized_principals='"$sudoers"' revoked_keys_file=/etc/ssh/revoked-keys' /etc/pam.d/system-auth - path: /root/tatu-manage-revoked-keys.sh permissions: '0700' owner: root:root diff --git a/tatu/api/models.py b/tatu/api/models.py index db77f5d..a3acc0e 100644 --- a/tatu/api/models.py +++ b/tatu/api/models.py @@ -301,6 +301,7 @@ class NovaVendorData(object): 'sudoers': ','.join([r for r in roles if "admin" in r]), 'ssh_port': CONF.tatu.ssh_port, 'api_endpoint': CONF.tatu.api_endpoint_for_vms, + 'pam_sudo': CONF.tatu.pam_sudo, } resp.body = json.dumps(vendordata) resp.location = '/hosttokens/' + token.token_id diff --git a/tatu/config.py b/tatu/config.py index 2797d90..a642a2b 100644 --- a/tatu/config.py +++ b/tatu/config.py @@ -26,6 +26,8 @@ LOG = logging.getLogger(__name__) # 1) register options; 2) read the config file; 3) use the options opts = [ + cfg.BoolOpt('pam_sudo', default=False, + help='Use pam-ussh module to validate certificates on sudo calls'), cfg.BoolOpt('use_barbican', default=False, help='Use OpenStack Barbican to store sensitive data'), cfg.BoolOpt('use_pat_bastions', default=True,