aboutsummaryrefslogtreecommitdiff
path: root/perl/lib/Wallet/Object
diff options
context:
space:
mode:
Diffstat (limited to 'perl/lib/Wallet/Object')
-rw-r--r--perl/lib/Wallet/Object/Base.pm2
-rw-r--r--perl/lib/Wallet/Object/Duo.pm54
-rw-r--r--perl/lib/Wallet/Object/Duo/LDAPProxy.pm202
-rw-r--r--perl/lib/Wallet/Object/Duo/PAM.pm205
-rw-r--r--perl/lib/Wallet/Object/Duo/RDP.pm204
-rw-r--r--perl/lib/Wallet/Object/Duo/RadiusProxy.pm204
-rw-r--r--perl/lib/Wallet/Object/File.pm56
7 files changed, 900 insertions, 27 deletions
diff --git a/perl/lib/Wallet/Object/Base.pm b/perl/lib/Wallet/Object/Base.pm
index a6a78bf..bdd61fb 100644
--- a/perl/lib/Wallet/Object/Base.pm
+++ b/perl/lib/Wallet/Object/Base.pm
@@ -187,7 +187,7 @@ sub log_set {
}
my %fields = map { $_ => 1 }
qw(owner acl_get acl_store acl_show acl_destroy acl_flags expires
- comment flags type_data);
+ comment flags type_data name);
unless ($fields{$field}) {
die "invalid history field $field";
}
diff --git a/perl/lib/Wallet/Object/Duo.pm b/perl/lib/Wallet/Object/Duo.pm
index 6edc4fa..d08294b 100644
--- a/perl/lib/Wallet/Object/Duo.pm
+++ b/perl/lib/Wallet/Object/Duo.pm
@@ -1,4 +1,4 @@
-# Wallet::Object::Duo -- Duo integration object implementation for the wallet.
+# Wallet::Object::Duo -- Base Duo object implementation for the wallet
#
# Written by Russ Allbery <eagle@eyrie.org>
# Copyright 2014
@@ -29,7 +29,7 @@ use Wallet::Object::Base;
# 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.01';
+$VERSION = '0.02';
##############################################################################
# Core methods
@@ -41,7 +41,9 @@ sub attr_show {
my $output = '';
my $key;
eval {
- my %search = (du_name => $self->{name});
+ my %search = (du_name => $self->{name},
+ du_type => $self->{type},
+ );
my $row = $self->{schema}->resultset ('Duo')->find (\%search);
$key = $row->get_column ('du_key');
};
@@ -84,7 +86,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) = @_;
+ my ($class, $type, $name, $schema, $creator, $host, $time, $duo_type) = @_;
# We have to have a Duo integration key file set.
if (not $Wallet::Config::DUO_KEY_FILE) {
@@ -104,10 +106,11 @@ sub create {
# Create the object in Duo.
require Net::Duo::Admin::Integration;
+ $duo_type ||= $Wallet::Config::DUO_TYPE;
my %data = (
- name => "$name ($Wallet::Config::DUO_TYPE)",
+ name => "$name ($duo_type)",
notes => 'Managed by wallet',
- type => $Wallet::Config::DUO_TYPE,
+ type => $duo_type,
);
my $integration = Net::Duo::Admin::Integration->create ($duo, \%data);
@@ -121,6 +124,7 @@ sub create {
eval {
my %record = (
du_name => $name,
+ du_type => $type,
du_key => $integration->integration_key,
);
$self->{schema}->resultset ('Duo')->create (\%record);
@@ -147,7 +151,9 @@ sub destroy {
my $schema = $self->{schema};
my $guard = $schema->txn_scope_guard;
eval {
- my %search = (du_name => $self->{name});
+ my %search = (du_name => $self->{name},
+ du_type => $self->{type},
+ );
my $row = $schema->resultset ('Duo')->find (\%search);
my $key = $row->get_column ('du_key');
my $int = Net::Duo::Admin::Integration->new ($self->{duo}, $key);
@@ -178,7 +184,9 @@ sub get {
# Retrieve the integration from Duo.
my $key;
eval {
- my %search = (du_name => $self->{name});
+ my %search = (du_name => $self->{name},
+ du_type => $self->{type},
+ );
my $row = $self->{schema}->resultset ('Duo')->find (\%search);
$key = $row->get_column ('du_key');
};
@@ -194,10 +202,10 @@ sub get {
my $config = $json->decode (scalar slurp $Wallet::Config::DUO_KEY_FILE);
# Construct the returned file.
- my $output = "[duo]\n";
- $output .= "ikey = $key\n";
- $output .= 'skey = ' . $integration->secret_key . "\n";
- $output .= "host = $config->{api_hostname}\n";
+ my $output;
+ $output .= "Integration key: $key\n";
+ $output .= 'Secret key: ' . $integration->secret_key . "\n";
+ $output .= "Host: $config->{api_hostname}\n";
# Log the action and return.
$self->log_action ('get', $user, $host, $time);
@@ -234,12 +242,11 @@ create a Duo integration, return a configuration file containing the key
and API information for that integration, and delete the integration from
Duo when the wallet object is destroyed.
-Currently, only one configured integration type can be managed by the
-wallet, and the integration information is always returned in the
-configuration file format expected by the Duo UNIX integration. The
-results of retrieving this object will be text, suitable for putting in
-the UNIX integration configuration file, containing the integration key,
-secret key, and admin hostname for that integration.
+Usually you will want to use one of the subclasses of this module, which
+override the output to give you a configuration fragment suited for a
+specific application type. However, you can always use this module for
+generic integrations where you don't mind massaging the output into the
+configuration for the application using Duo.
This object can be retrieved repeatedly without changing the secret key,
matching Duo's native behavior with integrations. To change the keys of
@@ -258,7 +265,7 @@ implementation.
=over 4
-=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME])
+=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME, INTEGRATION_TYPE])
This is a class method and should be called on the Wallet::Object::Duo
class. It creates a new object with the given TYPE and NAME (TYPE is
@@ -272,9 +279,9 @@ time is used.
When a new Duo integration object is created, a new integration will be
created in the configured Duo account and the integration key will be
stored in the wallet object. If the integration already exists, create()
-will fail. The new integration's type is controlled by the DUO_TYPE
-configuration variable, which defaults to C<unix>. See L<Wallet::Config>
-for more information.
+will fail. If an integration type isn't given, the new integration's type
+is controlled by the DUO_TYPE configuration variable, which defaults to
+C<unix>. See L<Wallet::Config> for more information.
If create() fails, it throws an exception.
@@ -314,9 +321,6 @@ isn't given, the current time is used.
=head1 LIMITATIONS
Only one Duo account is supported for a given wallet implementation.
-Currently, only one Duo integration type is supported as well. Further
-development should expand the available integration types, possibly as
-additional wallet object types.
=head1 SEE ALSO
diff --git a/perl/lib/Wallet/Object/Duo/LDAPProxy.pm b/perl/lib/Wallet/Object/Duo/LDAPProxy.pm
new file mode 100644
index 0000000..74ff43c
--- /dev/null
+++ b/perl/lib/Wallet/Object/Duo/LDAPProxy.pm
@@ -0,0 +1,202 @@
+# Wallet::Object::Duo::LDAPProxy -- Duo auth proxy integration for LDAP
+#
+# Written by Jon Robertson <jonrober@stanford.edu>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package Wallet::Object::Duo::LDAPProxy;
+require 5.006;
+
+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::Object::Duo;
+
+@ISA = qw(Wallet::Object::Duo);
+
+# 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.01';
+
+##############################################################################
+# Core methods
+##############################################################################
+
+# Override create to provide the specific Duo integration type that will be
+# used in the remote Duo record.
+sub create {
+ my ($class, $type, $name, $schema, $creator, $host, $time) = @_;
+
+ $time ||= time;
+ my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host,
+ $time, 'ldapproxy');
+ return $self;
+}
+
+# Override get to output the data in a specific format used for Duo LDAP
+# integration
+sub get {
+ my ($self, $user, $host, $time) = @_;
+ $time ||= time;
+
+ # Check that the object isn't locked.
+ my $id = $self->{type} . ':' . $self->{name};
+ if ($self->flag_check ('locked')) {
+ $self->error ("cannot get $id: object is locked");
+ return;
+ }
+
+ # Retrieve the integration from Duo.
+ my $key;
+ eval {
+ my %search = (du_name => $self->{name});
+ my $row = $self->{schema}->resultset ('Duo')->find (\%search);
+ $key = $row->get_column ('du_key');
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ my $integration = Net::Duo::Admin::Integration->new ($self->{duo}, $key);
+
+ # We also need the admin server name, which we can get from the Duo object
+ # configuration with a bit of JSON decoding.
+ 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 = "[ldap_server_challenge]\n";
+ $output .= "ikey = $key\n";
+ $output .= 'skey = ' . $integration->secret_key . "\n";
+ $output .= "api_host = $config->{api_hostname}\n";
+
+ # Log the action and return.
+ $self->log_action ('get', $user, $host, $time);
+ return $output;
+}
+
+1;
+__END__
+
+##############################################################################
+# Documentation
+##############################################################################
+
+=for stopwords
+Allbery Duo integration DBH keytab LDAP auth
+
+=head1 NAME
+
+Wallet::Object::Duo::LDAPProxy -- Duo auth proxy integration for LDAP
+
+=head1 SYNOPSIS
+
+ my @name = qw(duo-ldap host.example.com);
+ my @trace = ($user, $host, time);
+ my $object = Wallet::Object::Duo::LDAPProxy->create (@name, $schema, @trace);
+ my $config = $object->get (@trace);
+ $object->destroy (@trace);
+
+=head1 DESCRIPTION
+
+Wallet::Object::Duo::LDAPProxy is a representation of Duo
+integrations with the wallet, specifically to output Duo integrations
+in a format that can easily be pulled into configuring the Duo
+Authentication Proxy for LDAP. It implements the wallet object API
+and provides the necessary glue to create a Duo integration, return a
+configuration file containing the key and API information for that
+integration, and delete the integration from Duo when the wallet object
+is destroyed.
+
+The integration information is always returned in the configuration file
+format expected by the Authentication Proxy for Duo in configuring it
+for LDAP.
+
+This object can be retrieved repeatedly without changing the secret key,
+matching Duo's native behavior with integrations. To change the keys of
+the integration, delete it and recreate it.
+
+To use this object, at least one configuration parameter must be set. See
+L<Wallet::Config> for details on supported configuration parameters and
+information about how to set wallet configuration.
+
+=head1 METHODS
+
+This object mostly inherits from Wallet::Object::Duo. See the
+documentation for that class for all generic methods. Below are only
+those methods that are overridden or behave specially for this
+implementation.
+
+=over 4
+
+=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME])
+
+This will override the Wallet::Object::Duo class with the information
+needed to create a specific integration type in Duo. It creates a new
+object with the given TYPE and NAME (TYPE is normally C<duo-ldap> and
+must be for the rest of the wallet system to use the right class, but
+this module doesn't check for ease of subclassing), using DBH as the
+handle to the wallet metadata database. PRINCIPAL, HOSTNAME, and
+DATETIME are stored as history information. PRINCIPAL should be the
+user who is creating the object. If DATETIME isn't given, the current
+time is used.
+
+When a new Duo integration object is created, a new integration will be
+created in the configured Duo account and the integration key will be
+stored in the wallet object. If the integration already exists, create()
+will fail.
+
+If create() fails, it throws an exception.
+
+=item get(PRINCIPAL, HOSTNAME [, DATETIME])
+
+Retrieves the configuration information for the Duo integration and
+returns that information in the format expected by the configuration file
+for the Duo UNIX integration. Returns undef on failure. The caller
+should call error() to get the error message if get() returns undef.
+
+The returned configuration look look like:
+
+ [ldap_server_challenge]
+ ikey = <integration-key>
+ skey = <secret-key>
+ api_host = <api-hostname>
+
+The C<host> parameter will be taken from the configuration file pointed
+to by the DUO_KEY_FILE configuration variable.
+
+PRINCIPAL, HOSTNAME, and DATETIME are stored as history information.
+PRINCIPAL should be the user who is downloading the keytab. If DATETIME
+isn't given, the current time is used.
+
+=back
+
+=head1 LIMITATIONS
+
+Only one Duo account is supported for a given wallet implementation.
+
+=head1 SEE ALSO
+
+Net::Duo(3), Wallet::Config(3), Wallet::Object::Duo(3), wallet-backend(8)
+
+This module is part of the wallet system. The current version is
+available from L<http://www.eyrie.org/~eagle/software/wallet/>.
+
+=head1 AUTHORS
+
+Jon Robertson <jonrober@stanford.edu>
+
+=cut
diff --git a/perl/lib/Wallet/Object/Duo/PAM.pm b/perl/lib/Wallet/Object/Duo/PAM.pm
new file mode 100644
index 0000000..6f90ba1
--- /dev/null
+++ b/perl/lib/Wallet/Object/Duo/PAM.pm
@@ -0,0 +1,205 @@
+# Wallet::Object::Duo::PAM -- Duo PAM int. object implementation for wallet
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Jon Robertson <jonrober@stanford.edu>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package Wallet::Object::Duo::PAM;
+require 5.006;
+
+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::Object::Duo;
+
+@ISA = qw(Wallet::Object::Duo);
+
+# 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.01';
+
+##############################################################################
+# Core methods
+##############################################################################
+
+# Override create to provide the specific Duo integration type that will be
+# used in the remote Duo record.
+sub create {
+ my ($class, $type, $name, $schema, $creator, $host, $time) = @_;
+
+ $time ||= time;
+ my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host,
+ $time, 'unix');
+ return $self;
+}
+
+# Override get to output the data in a specific format used by Duo's PAM
+# module.
+sub get {
+ my ($self, $user, $host, $time) = @_;
+ $time ||= time;
+
+ # Check that the object isn't locked.
+ my $id = $self->{type} . ':' . $self->{name};
+ if ($self->flag_check ('locked')) {
+ $self->error ("cannot get $id: object is locked");
+ return;
+ }
+
+ # Retrieve the integration from Duo.
+ my $key;
+ eval {
+ my %search = (du_name => $self->{name});
+ my $row = $self->{schema}->resultset ('Duo')->find (\%search);
+ $key = $row->get_column ('du_key');
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ my $integration = Net::Duo::Admin::Integration->new ($self->{duo}, $key);
+
+ # We also need the admin server name, which we can get from the Duo object
+ # configuration with a bit of JSON decoding.
+ 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 = "[duo]\n";
+ $output .= "ikey = $key\n";
+ $output .= 'skey = ' . $integration->secret_key . "\n";
+ $output .= "host = $config->{api_hostname}\n";
+
+ # Log the action and return.
+ $self->log_action ('get', $user, $host, $time);
+ return $output;
+}
+
+1;
+__END__
+
+##############################################################################
+# Documentation
+##############################################################################
+
+=for stopwords
+Allbery Duo integration DBH keytab
+
+=head1 NAME
+
+Wallet::Object::Duo::PAM -- Duo PAM int. object implementation for wallet
+
+=head1 SYNOPSIS
+
+ my @name = qw(duo-pam host.example.com);
+ my @trace = ($user, $host, time);
+ my $object = Wallet::Object::Duo::PAM->create (@name, $schema, @trace);
+ my $config = $object->get (@trace);
+ $object->destroy (@trace);
+
+=head1 DESCRIPTION
+
+Wallet::Object::Duo::PAM is a representation of Duo integrations with
+the wallet, specifically to output Duo integrations in a format that
+can easily be pulled into configuring the Duo PAM interface. It
+implements the wallet object API and provides the necessary glue to
+create a Duo integration, return a configuration file containing the key
+and API information for that integration, and delete the integration from
+Duo when the wallet object is destroyed.
+
+The integration information is always returned in the configuration file
+format expected by the Duo UNIX integration. The results of retrieving
+this object will be text, suitable for putting in the UNIX integration
+configuration file, containing the integration key, secret key, and admin
+hostname for that integration.
+
+This object can be retrieved repeatedly without changing the secret key,
+matching Duo's native behavior with integrations. To change the keys of
+the integration, delete it and recreate it.
+
+To use this object, at least one configuration parameter must be set. See
+L<Wallet::Config> for details on supported configuration parameters and
+information about how to set wallet configuration.
+
+=head1 METHODS
+
+This object mostly inherits from Wallet::Object::Duo. See the
+documentation for that class for all generic methods. Below are only
+those methods that are overridden or behave specially for this
+implementation.
+
+=over 4
+
+=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME])
+
+This will override the Wallet::Object::Duo class with the information
+needed to create a specific integration type in Duo. It creates a new
+object with the given TYPE and NAME (TYPE is normally C<duo-pam> and must
+be for the rest of the wallet system to use the right class, but this
+module doesn't check for ease of subclassing), using DBH as the handle
+to the wallet metadata database. PRINCIPAL, HOSTNAME, and DATETIME are
+stored as history information. PRINCIPAL should be the user who is
+creating the object. If DATETIME isn't given, the current time is
+used.
+
+When a new Duo integration object is created, a new integration will be
+created in the configured Duo account and the integration key will be
+stored in the wallet object. If the integration already exists, create()
+will fail.
+
+If create() fails, it throws an exception.
+
+=item get(PRINCIPAL, HOSTNAME [, DATETIME])
+
+Retrieves the configuration information for the Duo integration and
+returns that information in the format expected by the configuration file
+for the Duo UNIX integration. Returns undef on failure. The caller
+should call error() to get the error message if get() returns undef.
+
+The returned configuration look look like:
+
+ [duo]
+ ikey = <integration-key>
+ skey = <secret-key>
+ host = <api-hostname>
+
+The C<host> parameter will be taken from the configuration file pointed
+to by the DUO_KEY_FILE configuration variable.
+
+PRINCIPAL, HOSTNAME, and DATETIME are stored as history information.
+PRINCIPAL should be the user who is downloading the keytab. If DATETIME
+isn't given, the current time is used.
+
+=back
+
+=head1 LIMITATIONS
+
+Only one Duo account is supported for a given wallet implementation.
+
+=head1 SEE ALSO
+
+Net::Duo(3), Wallet::Config(3), Wallet::Object::Duo(3), wallet-backend(8)
+
+This module is part of the wallet system. The current version is
+available from L<http://www.eyrie.org/~eagle/software/wallet/>.
+
+=head1 AUTHORS
+
+Russ Allbery <eagle@eyrie.org>
+Jon Robertson <eagle@eyrie.org>
+
+=cut
diff --git a/perl/lib/Wallet/Object/Duo/RDP.pm b/perl/lib/Wallet/Object/Duo/RDP.pm
new file mode 100644
index 0000000..2e975fc
--- /dev/null
+++ b/perl/lib/Wallet/Object/Duo/RDP.pm
@@ -0,0 +1,204 @@
+# Wallet::Object::Duo::RDP -- Duo RDP int. object implementation for wallet
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Jon Robertson <jonrober@stanford.edu>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package Wallet::Object::Duo::RDP;
+require 5.006;
+
+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::Object::Duo;
+
+@ISA = qw(Wallet::Object::Duo);
+
+# 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.01';
+
+##############################################################################
+# Core methods
+##############################################################################
+
+# Override create to provide the specific Duo integration type that will be
+# used in the remote Duo record.
+sub create {
+ my ($class, $type, $name, $schema, $creator, $host, $time) = @_;
+
+ $time ||= time;
+ my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host,
+ $time, 'rdp');
+ return $self;
+}
+
+# Override get to output the data in a specific format used by Duo's RDP
+# module.
+sub get {
+ my ($self, $user, $host, $time) = @_;
+ $time ||= time;
+
+ # Check that the object isn't locked.
+ my $id = $self->{type} . ':' . $self->{name};
+ if ($self->flag_check ('locked')) {
+ $self->error ("cannot get $id: object is locked");
+ return;
+ }
+
+ # Retrieve the integration from Duo.
+ my $key;
+ eval {
+ my %search = (du_name => $self->{name});
+ my $row = $self->{schema}->resultset ('Duo')->find (\%search);
+ $key = $row->get_column ('du_key');
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ my $integration = Net::Duo::Admin::Integration->new ($self->{duo}, $key);
+
+ # We also need the admin server name, which we can get from the Duo object
+ # configuration with a bit of JSON decoding.
+ 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";
+
+ # Log the action and return.
+ $self->log_action ('get', $user, $host, $time);
+ return $output;
+}
+
+1;
+__END__
+
+##############################################################################
+# Documentation
+##############################################################################
+
+=for stopwords
+Allbery Duo integration DBH keytab RDP
+
+=head1 NAME
+
+Wallet::Object::Duo::RDP -- Duo RDP int. object implementation for wallet
+
+=head1 SYNOPSIS
+
+ my @name = qw(duo-rdp host.example.com);
+ my @trace = ($user, $host, time);
+ my $object = Wallet::Object::Duo::RDP->create (@name, $schema, @trace);
+ my $config = $object->get (@trace);
+ $object->destroy (@trace);
+
+=head1 DESCRIPTION
+
+Wallet::Object::Duo::RDP is a representation of Duo integrations with
+the wallet, specifically to output Duo integrations to set up an RDP
+integration. This can be used to set up remote logins, or all Windows
+logins period if so selected in Duo's software. It implements the
+wallet object API and provides the necessary glue to create a Duo
+integration, return a configuration file containing the key and API
+information for that integration, and delete the integration from Duo
+when the wallet object is destroyed.
+
+Because the Duo RDP software is configured by a GUI, the information
+returned for a get operation is a simple set that's readable but not
+useful for directly plugging into a config file. The values would need
+to be cut and pasted into the GUI.
+
+This object can be retrieved repeatedly without changing the secret key,
+matching Duo's native behavior with integrations. To change the keys of
+the integration, delete it and recreate it.
+
+To use this object, at least one configuration parameter must be set. See
+L<Wallet::Config> for details on supported configuration parameters and
+information about how to set wallet configuration.
+
+=head1 METHODS
+
+This object mostly inherits from Wallet::Object::Duo. See the
+documentation for that class for all generic methods. Below are only
+those methods that are overridden or behave specially for this
+implementation.
+
+=over 4
+
+=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME])
+
+This will override the Wallet::Object::Duo class with the information
+needed to create a specific integration type in Duo. It creates a new
+object with the given TYPE and NAME (TYPE is normally C<duo-pam> and must
+be for the rest of the wallet system to use the right class, but this
+module doesn't check for ease of subclassing), using DBH as the handle
+to the wallet metadata database. PRINCIPAL, HOSTNAME, and DATETIME are
+stored as history information. PRINCIPAL should be the user who is
+creating the object. If DATETIME isn't given, the current time is
+used.
+
+When a new Duo integration object is created, a new integration will be
+created in the configured Duo account and the integration key will be
+stored in the wallet object. If the integration already exists, create()
+will fail.
+
+If create() fails, it throws an exception.
+
+=item get(PRINCIPAL, HOSTNAME [, DATETIME])
+
+Retrieves the configuration information for the Duo integration and
+returns that information in the format expected by the configuration file
+for the Duo UNIX integration. Returns undef on failure. The caller
+should call error() to get the error message if get() returns undef.
+
+The returned configuration look look like:
+
+ Integration key: <integration-key>
+ Secret key: <secret-key>
+ Host: <api-hostname>
+
+The C<host> parameter will be taken from the configuration file pointed
+to by the DUO_KEY_FILE configuration variable.
+
+PRINCIPAL, HOSTNAME, and DATETIME are stored as history information.
+PRINCIPAL should be the user who is downloading the keytab. If DATETIME
+isn't given, the current time is used.
+
+=back
+
+=head1 LIMITATIONS
+
+Only one Duo account is supported for a given wallet implementation.
+
+=head1 SEE ALSO
+
+Net::Duo(3), Wallet::Config(3), Wallet::Object::Duo(3), wallet-backend(8)
+
+This module is part of the wallet system. The current version is
+available from L<http://www.eyrie.org/~eagle/software/wallet/>.
+
+=head1 AUTHORS
+
+Russ Allbery <eagle@eyrie.org>
+Jon Robertson <eagle@eyrie.org>
+
+=cut
diff --git a/perl/lib/Wallet/Object/Duo/RadiusProxy.pm b/perl/lib/Wallet/Object/Duo/RadiusProxy.pm
new file mode 100644
index 0000000..faa0c2f
--- /dev/null
+++ b/perl/lib/Wallet/Object/Duo/RadiusProxy.pm
@@ -0,0 +1,204 @@
+# Wallet::Object::Duo::RadiusProxy -- Duo auth proxy integration for radius
+#
+# Written by Jon Robertson <jonrober@stanford.edu>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package Wallet::Object::Duo::RadiusProxy;
+require 5.006;
+
+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::Object::Duo;
+
+@ISA = qw(Wallet::Object::Duo);
+
+# 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.01';
+
+##############################################################################
+# Core methods
+##############################################################################
+
+# Override create to provide the specific Duo integration type that will be
+# used in the remote Duo record.
+sub create {
+ my ($class, $type, $name, $schema, $creator, $host, $time) = @_;
+
+ $time ||= time;
+ my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host,
+ $time, 'radius');
+ return $self;
+}
+
+# Override get to output the data in a specific format used for Duo radius
+# integration
+sub get {
+ my ($self, $user, $host, $time) = @_;
+ $time ||= time;
+
+ # Check that the object isn't locked.
+ my $id = $self->{type} . ':' . $self->{name};
+ if ($self->flag_check ('locked')) {
+ $self->error ("cannot get $id: object is locked");
+ return;
+ }
+
+ # Retrieve the integration from Duo.
+ my $key;
+ eval {
+ my %search = (du_name => $self->{name});
+ my $row = $self->{schema}->resultset ('Duo')->find (\%search);
+ $key = $row->get_column ('du_key');
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ my $integration = Net::Duo::Admin::Integration->new ($self->{duo}, $key);
+
+ # We also need the admin server name, which we can get from the Duo object
+ # configuration with a bit of JSON decoding.
+ 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 = "[radius_server_challenge]\n";
+ $output .= "ikey = $key\n";
+ $output .= 'skey = ' . $integration->secret_key . "\n";
+ $output .= "api_host = $config->{api_hostname}\n";
+ $output .= "client = radius_client\n";
+
+ # Log the action and return.
+ $self->log_action ('get', $user, $host, $time);
+ return $output;
+}
+
+1;
+__END__
+
+##############################################################################
+# Documentation
+##############################################################################
+
+=for stopwords
+Allbery Duo integration DBH keytab auth
+
+=head1 NAME
+
+Wallet::Object::Duo::RadiusProxy -- Duo auth proxy integration for RADIUS
+
+=head1 SYNOPSIS
+
+ my @name = qw(duo-radius host.example.com);
+ my @trace = ($user, $host, time);
+ my $object = Wallet::Object::Duo::RadiusProxy->create (@name, $schema, @trace);
+ my $config = $object->get (@trace);
+ $object->destroy (@trace);
+
+=head1 DESCRIPTION
+
+Wallet::Object::Duo::RadiusProxy is a representation of Duo
+integrations with the wallet, specifically to output Duo integrations
+in a format that can easily be pulled into configuring the Duo
+Authentication Proxy for Radius. It implements the wallet object API
+and provides the necessary glue to create a Duo integration, return a
+configuration file containing the key and API information for that
+integration, and delete the integration from Duo when the wallet object
+is destroyed.
+
+The integration information is always returned in the configuration file
+format expected by the Authentication Proxy for Duo in configuring it
+for Radius.
+
+This object can be retrieved repeatedly without changing the secret key,
+matching Duo's native behavior with integrations. To change the keys of
+the integration, delete it and recreate it.
+
+To use this object, at least one configuration parameter must be set. See
+L<Wallet::Config> for details on supported configuration parameters and
+information about how to set wallet configuration.
+
+=head1 METHODS
+
+This object mostly inherits from Wallet::Object::Duo. See the
+documentation for that class for all generic methods. Below are only
+those methods that are overridden or behave specially for this
+implementation.
+
+=over 4
+
+=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME])
+
+This will override the Wallet::Object::Duo class with the information
+needed to create a specific integration type in Duo. It creates a new
+object with the given TYPE and NAME (TYPE is normally C<duo-radius> and
+must be for the rest of the wallet system to use the right class, but
+this module doesn't check for ease of subclassing), using DBH as the
+handle to the wallet metadata database. PRINCIPAL, HOSTNAME, and
+DATETIME are stored as history information. PRINCIPAL should be the
+user who is creating the object. If DATETIME isn't given, the current
+time is used.
+
+When a new Duo integration object is created, a new integration will be
+created in the configured Duo account and the integration key will be
+stored in the wallet object. If the integration already exists, create()
+will fail.
+
+If create() fails, it throws an exception.
+
+=item get(PRINCIPAL, HOSTNAME [, DATETIME])
+
+Retrieves the configuration information for the Duo integration and
+returns that information in the format expected by the configuration file
+for the Duo UNIX integration. Returns undef on failure. The caller
+should call error() to get the error message if get() returns undef.
+
+The returned configuration look look like:
+
+ [radius_server_challenge]
+ ikey = <integration-key>
+ skey = <secret-key>
+ api_host = <api-hostname>
+ client = radius_client
+
+The C<host> parameter will be taken from the configuration file pointed
+to by the DUO_KEY_FILE configuration variable.
+
+PRINCIPAL, HOSTNAME, and DATETIME are stored as history information.
+PRINCIPAL should be the user who is downloading the keytab. If DATETIME
+isn't given, the current time is used.
+
+=back
+
+=head1 LIMITATIONS
+
+Only one Duo account is supported for a given wallet implementation.
+
+=head1 SEE ALSO
+
+Net::Duo(3), Wallet::Config(3), Wallet::Object::Duo(3), wallet-backend(8)
+
+This module is part of the wallet system. The current version is
+available from L<http://www.eyrie.org/~eagle/software/wallet/>.
+
+=head1 AUTHORS
+
+Jon Robertson <jonrober@stanford.edu>
+
+=cut
diff --git a/perl/lib/Wallet/Object/File.pm b/perl/lib/Wallet/Object/File.pm
index 1ff1288..226e32c 100644
--- a/perl/lib/Wallet/Object/File.pm
+++ b/perl/lib/Wallet/Object/File.pm
@@ -18,6 +18,7 @@ use warnings;
use vars qw(@ISA $VERSION);
use Digest::MD5 qw(md5_hex);
+use File::Copy qw(move);
use Wallet::Config ();
use Wallet::Object::Base;
@@ -55,6 +56,59 @@ sub file_path {
return "$Wallet::Config::FILE_BUCKET/$hash/$name";
}
+# Rename a file object. This includes renaming both the object itself, and
+# updating the file location for that object.
+sub rename {
+ my ($self, $new_name, $user, $host, $time) = @_;
+ $time ||= time;
+
+ my $old_name = $self->{name};
+ my $type = $self->{type};
+ my $schema = $self->{schema};
+ my $old_path = $self->file_path;
+
+ eval {
+
+ # Find the current object record.
+ my $guard = $schema->txn_scope_guard;
+ my %search = (ob_type => $type,
+ ob_name => $old_name);
+ my $object = $schema->resultset('Object')->find (\%search);
+ die "cannot find ${type}:${old_name}\n"
+ unless ($object and $object->ob_name eq $old_name);
+
+ # Update the object name but don't yet commit.
+ $object->ob_name ($new_name);
+
+ # Update the file to the path for the new name, and die if we can't.
+ # If the old path isn't there, then assume we haven't yet stored and
+ # keep going.
+ if ($old_path) {
+ $self->{name} = $new_name;
+ my $new_path = $self->file_path;
+ move($old_path, $new_path) or die $!;
+ }
+
+ $object->update;
+ $guard->commit;
+ };
+ if ($@) {
+ $self->{name} = $old_name;
+ $self->error ("cannot rename object $type $old_name: $!");
+ return;
+ }
+
+ eval {
+ $self->log_set ('name', $old_name, $new_name, $user, $host, $time);
+ };
+ if ($@) {
+ $self->error ("object $type $old_name was renamed but not logged: $!");
+ return 1;
+ }
+
+ return 1;
+}
+
##############################################################################
# Core methods
##############################################################################
@@ -145,7 +199,7 @@ API HOSTNAME DATETIME keytab remctld backend nul Allbery wallet-backend
my @name = qw(file mysql-lsdb)
my @trace = ($user, $host, time);
- my $object = Wallet::Object::Keytab->create (@name, $schema, @trace);
+ my $object = Wallet::Object::File->create (@name, $schema, @trace);
unless ($object->store ("the-password\n")) {
die $object->error, "\n";
}