diff --git a/README.duo b/README.duo new file mode 100644 index 0000000000000000000000000000000000000000..18c1a0a31f955d3615cc22aaa31bd3572ee95378 --- /dev/null +++ b/README.duo @@ -0,0 +1,41 @@ +This README file explains how to use base::duo. + +VERSION 4->5 UPGRADE NOTE: If your code involves base::duo, that's not going to +work anymore. Have a look at base::duo::config instead, or README.ssh. + +The term Duo uses is "integration", to refer to a set of credentials that a +client (like your system) will use to authenticate itself to Duo. base::duo +manages the process of fetching and customizing 'duo-unix' integrations, which +is the type of integration that can be used for things like login, sudo, and +web-based two-step (such as something that your web application might trigger). + +Duo integrations are keyed on the system name, so if you have multiple Duo uses +on a single system (e.g. SSH and sudo), all uses will share the same Duo +integration, but _may_ use different Duo configuration files: If the Duo uses +on a single system have different needs (such as one failing safe and one +failing secure), that will require separate Duo configuration files. + +Duo integration keys do not change or expire, unless a `wallet destroy` or a +Duo administrator manually deletes a Duo integration. + +If you do ever need to destroy a Duo integration, here's the command to use: + + wallet destroy pam-duo hostname.stanford.edu + +To generate a Duo configuration file, instantiate an object of +base::duo::config, where the name is the path to the Duo configuration file. +For example: + +base::duo::config { '/etc/secure/duo_webapp.conf': + ensure => present, + failsecure => true, +} + +To see the options available, have a look at the header text in +manifests/duo/config.pp. There are other classes in the base::duo namespace, +but they're all invoked as needed by base::duo::config. + +To be honest, the only time you'll need to invoke base::duo::config directly is +when you have a custom thing that wants to leverage Duo. If you're interested +in using Duo to authenticate SSH or sudo, have a look in README.ssh or +README.sudo instead. diff --git a/manifests/duo.pp b/manifests/duo.pp deleted file mode 100644 index f08bc21b3cb82637fe83ee115ffeb62e732fdfd5..0000000000000000000000000000000000000000 --- a/manifests/duo.pp +++ /dev/null @@ -1,26 +0,0 @@ -# Set up Duo. Note that this class does not _enable_ Duo for any service, -# rather, it simply downloads the pam_duo software and the appropriate -# wallet files that allow Duo to be used. - -# See base::sudo and base::ssh for services that leverage this class. - -# wallet_name: the name for the duo wallet object. Defaults to the -# fully-qualified domain name of the host. - -class base::duo( - $wallet_name = $::fqdn -){ - # Pull in Duo's PAM integration package - package { 'libpam-duo': ensure => present } - - # Install the duo configuration. The object is not written to the - # default loaction because base::wallet will not overwrite the - # configuration file supplied with the package install. - $wallet_name_downcase = downcase($wallet_name) - base::wallet { $wallet_name_downcase: - ensure => present, - type => 'duo-pam', - path => '/etc/security/pam_duo_su.conf', - require => Package['libpam-duo'], - } -} diff --git a/manifests/duo/config.pp b/manifests/duo/config.pp new file mode 100644 index 0000000000000000000000000000000000000000..bf2902bae231491e1a11a124556c9558c2b57ff7 --- /dev/null +++ b/manifests/duo/config.pp @@ -0,0 +1,99 @@ +# Set up a custom Duo configuration. Note that this class does not _enable_ Duo. +# Instead, this type downloads a common Duo integration, copies it, and then +# customizes it according to the parameters you specify. +# +# Your client code is responsible for leveraging the configuration, such as by +# using PAM. +# +# See base::sudo and base::ssh for services that leverage this class. +# +# Here are the parameters: +# +# name: (namevar) The path to place the customized Duo configuration file. +# +# ensure: Set to "present" (the default) or "absent". +# +# wallet_name: the name for the common Duo wallet object. Defaults to the +# fully-qualified domain name of the host. +# +# use_gecos: A boolean, defaults to false. When true, Duo will get the +# username from the GECOS field (known in Puppet as the comment field) in the +# system passwd file. When false, Duo will use the user's username. This is +# used when a user is logging in with an account where their username does not +# match their Duo username. +# +# fail_secure: A boolean, defaults to false. When false, a Duo timeout will +# cause the Duo authentication to pass, allowing the user to continue logging +# in. When true, a Duo timeout will cause the Duo authentication to fail, +# blocking the user from logging in. + +define base::duo::config ( + $ensure = 'present', + $wallet_name = $::fqdn, + $use_gecos = false, + $fail_secure = false, +) { + # Validate $ensure, $use_gecos, and $fail_secure + if ($ensure != 'present') and ($ensure != 'absent') { + fail('ensure may only be "present" or "absent"') + } + if !is_bool($use_gecos) { + fail('base::duo::use_gecos must be true or false') + } + if !is_bool($fail_secure) { + fail('base::duo::fail_secure must be true or false') + } + + # Translate $use_gecos from boolean into present/absent + if $use_gecos { + $include_gecos_line = present + } else { + $include_gecos_line = absent + } + + # Translate $fail_secure from boolean into present/absent + if $fail_secure { + $include_fail_line = present + } else { + $include_fail_line = absent + } + + # Decide if we even need to do anything! + if $ensure == 'present' { + # If the common config hasn't been loaded, do that now + if !defined(Base::Duo::Config::Common[$wallet_name]) { + base::duo::config::common { $wallet_name: + ensure => present, + } + } + + # First, copy our common Duo config file + file { $name: + ensure => present, + source => "/etc/security/pam_duo_${wallet_name}.conf", + replace => false, + require => Base::Duo::Config::Common[$wallet_name], + } + + # Next, add our GECOS and failmode lines + file_line { "duo_gecos_${name}": + ensure => $include_gecos_line, + path => $name, + line => 'send_gecos = yes', + require => File[$name], + } + file_line { "duo_fail_secure_${name}": + ensure => $include_fail_line, + path => $name, + line => 'failmode = secure', + require => File[$name], + } + } # Done handling $ensure == 'present' + + else { + # Make sure our custom Duo config file is gone + file { $name: + ensure => absent, + } + } # Done handling $ensure == 'absent' +} diff --git a/manifests/duo/config/common.pp b/manifests/duo/config/common.pp new file mode 100644 index 0000000000000000000000000000000000000000..3da41f1b5ea8eeb7244295e737c838a02efc4be0 --- /dev/null +++ b/manifests/duo/config/common.pp @@ -0,0 +1,37 @@ +# Set up some common Duo configuration. Normally each host has its own Duo +# key, but some hosts have multiple Duo keys (for example, one for the host and +# one for an application running on the host). So, we need a separate type +# that can be called to pull the Duo credentials from Wallet. +# +# The Wallet object will be stored to /etc/security/pam_duo_$name.conf +# If you don't need to customize anything, then your code can use it directly, +# otherwise your code will need to copy it, and then make changes as +# appropriate. + +define base::duo::config::common ( + $ensure = 'present', +) { + # Validate $ensure + if ($ensure != 'present') and ($ensure != 'absent') { + fail('ensure may only be "present" or "absent"') + } + + # Lowercase the name, and then pull the object from Wallet + $wallet_name_downcase = downcase($name) + + # Bring in the Wallet object, or get rid of it! + # Also, bring in the Duo package, as a convenience to the client. + if ($ensure == present) { + require base::duo::package + + base::wallet { $wallet_name_downcase: + ensure => present, + type => 'duo-pam', + path => "/etc/security/pam_duo_${name}.conf", + } + } else { + file { "/etc/security/pam_duo_${name}.conf": + ensure => absent, + } + } +} diff --git a/manifests/duo/package.pp b/manifests/duo/package.pp new file mode 100644 index 0000000000000000000000000000000000000000..3f3e24145e664d28e8de778b2eb410318b721cac --- /dev/null +++ b/manifests/duo/package.pp @@ -0,0 +1,50 @@ +# base::duo::package is used to bring in the packages that provide Duo +# functionality. This does not activate Duo, nor does it configure Duo. +# Clients should `require base::duo::package` in their code, so that multiple +# sources can "require" Duo. Then, your code should declare an appropriate +# base::duo::config to set up the configuration the way you want. +# +# As a convenience, if your code is using the base::duo::config type or the +# base::duo::config::common type, then you get base::duo::package +# automatically! + +class base::duo::package ( + $wallet_name = $::fqdn, + $use_gecos = false, + $fail_secure = false, +) { + # What we pull in depends on the OS family + case $::osfamily { + redhat: { + # For RHEL, bring in the duo_unix, with Duo's key + include base::rpm::duo + base::rpm::import { 'duo-rpmkey': + url => 'http://yum.stanford.edu/RPM-GPG-KEY-DUO', + signature => 'gpg-pubkey-15d32efc-522883e4'; + } + package { 'duo_unix': + ensure => installed, + require => Base::Rpm::Import[ 'duo-rpmkey' ], + } + } + debian: { + # The libpam-duo that ships with Debian doesn't support GECOS, so we need + # a backport. That means we need a pin, and THAT means we need to make + # sure that the debian-stanford backports repository is already set up! + # libpam-duo-gecos is a custom-made virtual package that ensures we are + # using a new-enough version of libpam-duo. + file { '/etc/apt/preferences.d/duo': + ensure => present, + content => template('base/duo/duo.erb'), + require => File['/etc/apt/sources.list.d/stanford.list'], + } + package { 'libpam-duo-gecos': + ensure => present, + require => File['/etc/apt/preferences.d/duo'], + } + } + default: { + fail('Your OS family is not recognized for installing the Duo PAM module') + } + } +} diff --git a/templates/duo/duo.erb b/templates/duo/duo.erb new file mode 100644 index 0000000000000000000000000000000000000000..13c807568c8088be44e24eaebef22077987fbef3 --- /dev/null +++ b/templates/duo/duo.erb @@ -0,0 +1,21 @@ +# Use backported Duo packages +# (This file is maintained by Puppet) +# +# We need a Duo package that includes GECOS support. The existing Duo packages +# in main don't have that, so we need to use the backported package we build. + +Package: libduo-dev +Pin: release a=<%= lsbdistcodename %>-backports +Pin-Priority: 995 + +Package: libduo3 +Pin: release a=<%= lsbdistcodename %>-backports +Pin-Priority: 995 + +Package: libpam-duo +Pin: release a=<%= lsbdistcodename %>-backports +Pin-Priority: 995 + +Package: login-duo +Pin: release a=<%= lsbdistcodename %>-backports +Pin-Priority: 995