From b3ff52f5a8055bc6995740b60353002a3abefb17 Mon Sep 17 00:00:00 2001
From: Adam Henry Lewenberg <adamhl@stanford.edu>
Date: Thu, 5 Nov 2015 12:49:26 -0800
Subject: [PATCH] Set up base::ssh for duo

---
 manifests/ssh.pp                     | 40 +++++++++-----
 manifests/ssh/config/sshd.pp         |  4 ++
 manifests/ssh/pam.pp                 | 28 ++++++++++
 manifests/sudo.pp                    | 18 ++++++-
 templates/ssh/etc/pam.d/sshd.erb     | 79 ++++++++++++++++++++++++++++
 templates/ssh/sshd_config.erb        |  6 +++
 templates/sudo/etc/sudoers.d/duo.erb |  1 +
 7 files changed, 161 insertions(+), 15 deletions(-)
 create mode 100644 manifests/ssh/pam.pp
 create mode 100644 templates/ssh/etc/pam.d/sshd.erb

diff --git a/manifests/ssh.pp b/manifests/ssh.pp
index eae9e09..1be9188 100644
--- a/manifests/ssh.pp
+++ b/manifests/ssh.pp
@@ -3,10 +3,27 @@
 # system, we lock connections down to campus with iptables by default, and we
 # have a few subclasses that allow things like host keys.
 
-class base::ssh {
+# If you want to require Duo on login, set pam_duo to true. This flag will
+# load the appropriate Duo code (via base::duo) and change the sshd_config
+# file so that Duo is required for non-root logins. If you want Duo for
+# sudo, see the base::sudo class.
+# Default: false
+
+class base::ssh(
+  $pam_duo = false
+){
   package { 'openssh-server': ensure => present }
 
-  # Our default ssh rules allow connectiosn from all of campus.  This is
+  if ($pam_duo) {
+    include base::duo
+  }
+
+  # Setup /etc/pam.d/sshd to require Duo on regular logins.
+  class { 'ssh::pam':
+    pam_duo => $pam_duo,
+  }
+
+  # Our default ssh rules allow connections from all of campus.  This is
   # mostly for legacy reasons, since it historically had always done this and
   # we weren't sure what would break.
   #
@@ -29,24 +46,21 @@ class base::ssh {
 
   # Ensure the daemon is running.
   service { 'ssh':
+    ensure  => running,
     name    => $::osfamily ? {
-      Debian               => 'ssh',
-      RedHat               => 'sshd',
+      Debian  => 'ssh',
+      RedHat  => 'sshd',
     },
-    ensure  => running,
     require => Package['openssh-server'],
   }
 
-  # Install our configuration files.
-  base::ssh::config::sshd { '/etc/ssh/sshd_config': ensure => present }
+  # Install ssh (client) configuration file.
   base::ssh::config::ssh  { '/etc/ssh/ssh_config':  ensure => present }
 
-  # Configure PAM for sshd on RHEL 6.
-  if ($::lsbdistcodename == 'santiago') {
-    file { '/etc/pam.d/sshd':
-      ensure => link,
-      target => '/etc/pam.d/system-auth',
-    }
+  # Install sshd (server) configuration file.
+  base::ssh::config::sshd { '/etc/ssh/sshd_config':
+    ensure  => present,
+    pam_duo => $pam_duo,
   }
 
   # Make sure public key authentication to root does not work and clean up
diff --git a/manifests/ssh/config/sshd.pp b/manifests/ssh/config/sshd.pp
index 5c2605c..e3b3464 100644
--- a/manifests/ssh/config/sshd.pp
+++ b/manifests/ssh/config/sshd.pp
@@ -17,6 +17,9 @@
 # If you want to allow root to log in with a password, set
 # rootloginwithpswd 'yes'. Otherwise, root logins with a password
 # are not allowed.
+#
+# If you want to require Duo on login, set pam_duo to true (defaults to
+# false).
 
 define base::ssh::config::sshd(
   $ensure            = 'present',
@@ -28,6 +31,7 @@ define base::ssh::config::sshd(
   $max_tries         = 5,
   $listen_addresses  = 'all',
   $rootloginwithpswd = 'no',
+  $pam_duo           = false,
 ) {
   if $source {
     $template = undef
diff --git a/manifests/ssh/pam.pp b/manifests/ssh/pam.pp
new file mode 100644
index 0000000..88d85dd
--- /dev/null
+++ b/manifests/ssh/pam.pp
@@ -0,0 +1,28 @@
+# Install /etc/pam.d/sshd.
+
+# If $pam_duo is set to true, use a pam stack that requires Duo for
+# regular logins.
+#
+# Currently, only Debian is supported when $pam_duo is true.
+
+class ssh::pam (
+  $pam_duo = false
+){
+
+  # Configure PAM for sshd on RHEL 6.
+  if ($::lsbdistcodename == 'santiago') {
+    file { '/etc/pam.d/sshd':
+      ensure => link,
+      target => '/etc/pam.d/system-auth',
+    }
+  } elsif ($pam_duo) {
+    if ($::osfamily =~ /Debian/) {
+      file {'/etc/pam.d/sshd':
+        ensure => present,
+        source => template('base/ssh/etc/pam.d/sshd.erb'),
+      }
+    } else {
+      fail("cannot call ssh::pam with pam_duo true under OS '$::osfamily'")
+    }
+  }
+}
diff --git a/manifests/sudo.pp b/manifests/sudo.pp
index ca3f7df..0a2725f 100644
--- a/manifests/sudo.pp
+++ b/manifests/sudo.pp
@@ -5,6 +5,9 @@
 # $duo_sudoers: A list of users that are allowed to call sudo.
 # Defaults to the empty array.
 #
+# $timeout: how long (in minutes) between requiring a new Duo re-auth.
+# Default: 30
+#
 # Example.
 # To install sudo with no Duo support:
 #
@@ -14,13 +17,24 @@
 # To install sudo WITH Duo support
 #
 #   class { 'base::sudo':
-#     duo => true,
-#     duo_sudoers => ['adamhl', 'yuelu']
+#     duo         => true,
+#     duo_sudoers => ['adamhl', 'yuelu'],
+#   }
+#
+# Example.
+# To install sudo WITH Duo support and require Duo auths
+# after 4 minutes.
+#
+#   class { 'base::sudo':
+#     duo         => true,
+#     duo_sudoers => ['adamhl', 'yuelu'],
+#     timeout     => 4,
 #   }
 
 class base::sudo(
   $duo         = false,
   $duo_sudoers = [],
+  $timeout     = 30,
 ){
   package { 'sudo':
     ensure => installed
diff --git a/templates/ssh/etc/pam.d/sshd.erb b/templates/ssh/etc/pam.d/sshd.erb
new file mode 100644
index 0000000..8674f6a
--- /dev/null
+++ b/templates/ssh/etc/pam.d/sshd.erb
@@ -0,0 +1,79 @@
+# Configuration requiring duo authentication for normal logins and
+# allowing root logins without duo authentication.
+
+##############################################################################
+# auth
+##############################################################################
+
+# 1. If the user is already logged in as root (presumably by using a root
+#    credential), then "jump over" the pam_duo module to step 3. If not,
+#    go to the next module in the stack (2).
+# 2. If the user is _not_ root, require Duo.
+# 3. Set up the AFS session and then, whether the AFS sesssion setup works
+#    or not, quit the pam stack
+
+auth    [success=1 default=ignore]  pam_succeed_if.so uid eq 0
+auth    required                    pam_duo.so conf=/etc/security/pam_duo_su.conf
+auth    [success=done default=die]  pam_afs_session.so
+
+##############################################################################
+# account
+##############################################################################
+
+# Disallow non-root logins when /etc/nologin exists.
+account    required     pam_nologin.so
+
+# Uncomment and edit /etc/security/access.conf if you need to set complex
+# access limits that are hard to express in sshd_config.
+# account  required     pam_access.so
+
+# Standard Un*x authorization.
+@include common-account
+
+##############################################################################
+# session
+##############################################################################
+
+# SELinux needs to be the first session rule.  This ensures that any
+# lingering context has been cleared.  Without this it is possible that a
+# module could execute code in the wrong domain.
+session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close
+
+# Set the loginuid process attribute.
+session    required     pam_loginuid.so
+
+# Create a new session keyring.
+session    optional     pam_keyinit.so force revoke
+
+# Standard Un*x session setup and teardown.
+@include common-session
+
+# Print the message of the day upon successful login.
+# This includes a dynamically generated part from /run/motd.dynamic
+# and a static (admin-editable) part from /etc/motd.
+session    optional     pam_motd.so  motd=/run/motd.dynamic
+session    optional     pam_motd.so noupdate
+
+# Print the status of the user's mailbox upon successful login.
+session    optional     pam_mail.so standard noenv # [1]
+
+# Set up user limits from /etc/security/limits.conf.
+session    required     pam_limits.so
+
+# Read environment variables from /etc/environment and
+# /etc/security/pam_env.conf.
+session    required     pam_env.so # [1]
+# In Debian 4.0 (etch), locale-related environment variables were moved to
+# /etc/default/locale, so read that as well.
+session    required     pam_env.so user_readenv=1 envfile=/etc/default/locale
+
+# SELinux needs to intervene at login time to ensure that the process starts
+# in the proper default security context.  Only sessions which are intended
+# to run in the user's context should be run after this.
+session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open
+
+##############################################################################
+# Password
+##############################################################################
+
+@include common-password
diff --git a/templates/ssh/sshd_config.erb b/templates/ssh/sshd_config.erb
index 446657f..2ec0b43 100644
--- a/templates/ssh/sshd_config.erb
+++ b/templates/ssh/sshd_config.erb
@@ -66,6 +66,12 @@ GSSAPIStoreCredentialsOnRekey yes
 GSSAPIStoreCredentialsOnRekey yes
 <% end -%>
 
+<% if (@pam_duo) then -%>
+# Require both (GSS-API|PASSWORD) and PAM.
+AuthenticationMethods gssapi-with-mic,keyboard-interactive:pam password,keyboard-interactive:pam
+KerberosAuthentication yes
+<% end -%>
+
 <%- if (@rootloginwithpswd == 'yes') -%>
 # Allow root login with a password (use with care!)
 PermitRootLogin yes
diff --git a/templates/sudo/etc/sudoers.d/duo.erb b/templates/sudo/etc/sudoers.d/duo.erb
index 7527c35..c0be278 100644
--- a/templates/sudo/etc/sudoers.d/duo.erb
+++ b/templates/sudo/etc/sudoers.d/duo.erb
@@ -1,3 +1,4 @@
+Defaults timestamp_timeout=<%= @timeout %>
 <%
   @duo_sudoers.each do |sudoer|
 -%>
-- 
GitLab