summaryrefslogtreecommitdiff
path: root/perl/lib/Wallet/Object/Duo.pm
diff options
context:
space:
mode:
Diffstat (limited to 'perl/lib/Wallet/Object/Duo.pm')
-rw-r--r--perl/lib/Wallet/Object/Duo.pm164
1 files changed, 143 insertions, 21 deletions
diff --git a/perl/lib/Wallet/Object/Duo.pm b/perl/lib/Wallet/Object/Duo.pm
index d08294b..1aca979 100644
--- a/perl/lib/Wallet/Object/Duo.pm
+++ b/perl/lib/Wallet/Object/Duo.pm
@@ -1,7 +1,8 @@
# Wallet::Object::Duo -- Base Duo object implementation for the wallet
#
# Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2014
+# Copyright 2016 Russ Allbery <eagle@eyrie.org>
+# Copyright 2014, 2015
# The Board of Trustees of the Leland Stanford Junior University
#
# See LICENSE for licensing terms.
@@ -11,25 +12,111 @@
##############################################################################
package Wallet::Object::Duo;
-require 5.006;
+use 5.008;
use strict;
use warnings;
-use vars qw(@ISA $VERSION);
use JSON;
-use Net::Duo::Admin;
-use Net::Duo::Admin::Integration;
use Perl6::Slurp qw(slurp);
-use Wallet::Config ();
+use Wallet::Config;
use Wallet::Object::Base;
-@ISA = qw(Wallet::Object::Base);
+our @ISA = qw(Wallet::Object::Base);
+our $VERSION = '1.03';
+
+# Mappings from our types into what Duo calls the integration types.
+our %DUO_TYPES = (
+ 'duo' => {
+ integration => 'unix',
+ output => \&_output_generic,
+ },
+ 'duo-ldap' => {
+ integration => 'ldapproxy',
+ output => \&_output_ldap,
+ },
+ 'duo-pam' => {
+ integration => 'unix',
+ output => \&_output_pam,
+ },
+ 'duo-radius' => {
+ integration => 'radius',
+ output => \&_output_radius,
+ },
+ );
+
+# Extra types to add. These are all just named as the Duo integration name
+# with duo- before it and go to the generic output. Put them here to prevent
+# pages of settings. These are also not all actually set as types in the
+# types table to prevent overpopulation. You should manually create the
+# entries in that table for any Duo integrations you want to add.
+our @EXTRA_TYPES = ('accountsapi', 'adfs', 'adminapi', 'array', 'barracuda',
+ 'cisco', 'citrixcag', 'citrixns', 'confluence', 'drupal',
+ 'f5bigip', 'f5firepass', 'fortinet', 'jira', 'juniper',
+ 'juniperuac', 'lastpass', 'okta', 'onelogin', 'openvpn',
+ 'openvpnas', 'owa', 'paloalto', 'rdgateway', 'rdp',
+ 'rdweb', 'rest', 'rras', 'shibboleth', 'sonicwallsra',
+ 'splunk', 'tmg', 'uag', 'verify', 'vmwareview', 'websdk',
+ 'wordpress');
+for my $type (@EXTRA_TYPES) {
+ my $wallet_type = 'duo-'.$type;
+ $DUO_TYPES{$wallet_type}{integration} = $type;
+ $DUO_TYPES{$wallet_type}{output} = \&_output_generic;
+};
-# This version should be increased on any code change to this module. Always
-# use two digits for the minor version with a leading zero if necessary so
-# that it will sort properly.
-$VERSION = '0.02';
+##############################################################################
+# Get output methods
+##############################################################################
+
+# Output for any miscellaneous Duo integration, usually those that use a GUI
+# to set information and so don't need a custom configuration file.
+sub _output_generic {
+ my ($key, $secret, $hostname) = @_;
+
+ my $output;
+ $output .= "Integration key: $key\n";
+ $output .= "Secret key: $secret\n";
+ $output .= "Host: $hostname\n";
+
+ return $output;
+}
+
+# Output for the Duo unix integration, which hooks into the PAM stack.
+sub _output_pam {
+ my ($key, $secret, $hostname) = @_;
+
+ my $output = "[duo]\n";
+ $output .= "ikey = $key\n";
+ $output .= "skey = $secret\n";
+ $output .= "host = $hostname\n";
+
+ return $output;
+}
+
+# Output for the radius proxy, which can be plugged into the proxy config.
+sub _output_radius {
+ my ($key, $secret, $hostname) = @_;
+
+ my $output = "[radius_server_challenge]\n";
+ $output .= "ikey = $key\n";
+ $output .= "skey = $secret\n";
+ $output .= "api_host = $hostname\n";
+ $output .= "client = radius_client\n";
+
+ return $output;
+}
+
+# Output for the LDAP proxy, which can be plugged into the proxy config.
+sub _output_ldap {
+ my ($key, $secret, $hostname) = @_;
+
+ my $output = "[ldap_server_challenge]\n";
+ $output .= "ikey = $key\n";
+ $output .= "skey = $secret\n";
+ $output .= "api_host = $hostname\n";
+
+ return $output;
+}
##############################################################################
# Core methods
@@ -66,8 +153,20 @@ sub new {
my $key_file = $Wallet::Config::DUO_KEY_FILE;
my $agent = $Wallet::Config::DUO_AGENT;
+ # Check that we can load all of the required modules.
+ eval {
+ require Net::Duo;
+ require Net::Duo::Admin;
+ require Net::Duo::Admin::Integration;
+ };
+ if ($@) {
+ my $error = $@;
+ chomp $error;
+ 1 while ($error =~ s/ at \S+ line \d+\.?\z//);
+ die "Duo object support not available: $error\n";
+ }
+
# Construct the Net::Duo::Admin object.
- require Net::Duo::Admin;
my $duo = Net::Duo::Admin->new (
{
key_file => $key_file,
@@ -86,7 +185,7 @@ sub new {
# great here since we don't have a way to communicate the error back to the
# caller.
sub create {
- my ($class, $type, $name, $schema, $creator, $host, $time, $duo_type) = @_;
+ my ($class, $type, $name, $schema, $creator, $host, $time) = @_;
# We have to have a Duo integration key file set.
if (not $Wallet::Config::DUO_KEY_FILE) {
@@ -95,8 +194,26 @@ sub create {
my $key_file = $Wallet::Config::DUO_KEY_FILE;
my $agent = $Wallet::Config::DUO_AGENT;
+ # Make sure this is actually a type we know about, since this handler
+ # can handle many types.
+ if (!exists $DUO_TYPES{$type}) {
+ die "$type is not a valid duo integration\n";
+ }
+
+ # Check that we can load all of the required modules.
+ eval {
+ require Net::Duo;
+ require Net::Duo::Admin;
+ require Net::Duo::Admin::Integration;
+ };
+ if ($@) {
+ my $error = $@;
+ chomp $error;
+ 1 while ($error =~ s/ at \S+ line \d+\.?\z//);
+ die "Duo object support not available: $error\n";
+ }
+
# Construct the Net::Duo::Admin object.
- require Net::Duo::Admin;
my $duo = Net::Duo::Admin->new (
{
key_file => $key_file,
@@ -105,8 +222,7 @@ sub create {
);
# Create the object in Duo.
- require Net::Duo::Admin::Integration;
- $duo_type ||= $Wallet::Config::DUO_TYPE;
+ my $duo_type = $DUO_TYPES{$type}{integration};
my %data = (
name => "$name ($duo_type)",
notes => 'Managed by wallet',
@@ -201,11 +317,17 @@ sub get {
my $json = JSON->new->utf8 (1)->relaxed (1);
my $config = $json->decode (scalar slurp $Wallet::Config::DUO_KEY_FILE);
- # Construct the returned file.
- my $output;
- $output .= "Integration key: $key\n";
- $output .= 'Secret key: ' . $integration->secret_key . "\n";
- $output .= "Host: $config->{api_hostname}\n";
+ # Construct the returned file. Assume the generic handler in case there
+ # is no valid handler, though that shouldn't happen.
+ my $output_sub;
+ my $type = $self->{type};
+ if (exists $DUO_TYPES{$type}{output}) {
+ $output_sub = $DUO_TYPES{$type}{output};
+ } else {
+ $output_sub = \&_output_generic;
+ }
+ my $output = $output_sub->($key, $integration->secret_key,
+ $config->{api_hostname});
# Log the action and return.
$self->log_action ('get', $user, $host, $time);