aboutsummaryrefslogtreecommitdiff
path: root/perl/Wallet
diff options
context:
space:
mode:
Diffstat (limited to 'perl/Wallet')
-rw-r--r--perl/Wallet/Config.pm47
-rw-r--r--perl/Wallet/Object/Duo.pm331
-rw-r--r--perl/Wallet/Schema.pm17
-rw-r--r--perl/Wallet/Schema/Result/Duo.pm53
4 files changed, 446 insertions, 2 deletions
diff --git a/perl/Wallet/Config.pm b/perl/Wallet/Config.pm
index 0d9d506..ed3dded 100644
--- a/perl/Wallet/Config.pm
+++ b/perl/Wallet/Config.pm
@@ -1,7 +1,7 @@
# Wallet::Config -- Configuration handling for the wallet server.
#
# Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2007, 2008, 2010, 2013
+# Copyright 2007, 2008, 2010, 2013, 2014
# The Board of Trustees of the Leland Stanford Junior University
#
# See LICENSE for licensing terms.
@@ -181,6 +181,51 @@ our $DB_PASSWORD;
=back
+=head1 DUO OBJECT CONFIGURATION
+
+These configuration variables only need to be set if you intend to use the
+C<duo> object type (the Wallet::Object::Duo class).
+
+=over 4
+
+=item DUO_AGENT
+
+If this configuration variable is set, its value should be an object that
+is call-compatible with LWP::UserAgent. This object will be used instead
+of LWP::UserAgent to make API calls to Duo. This is primarily useful for
+testing, allowing replacement of the user agent with a mock implementation
+so that a test can run without needing a Duo account.
+
+=cut
+
+our $DUO_AGENT;
+
+=item DUO_KEY_FILE
+
+The path to a file in JSON format that contains the key and hostname data
+for the Duo Admin API integration used to manage integrations via wallet.
+This file should be in the format expected by the C<key_file> parameter
+to the Net::Duo::Admin constructor. See L<Net::Duo::Admin> for more
+information.
+
+DUO_KEY_FILE must be set to use Duo objects.
+
+=cut
+
+our $DUO_KEY_FILE;
+
+=item DUO_TYPE
+
+The type of integration to create. Currently, only one type of integration
+can be created by one wallet configuration. This restriction may be relaxed
+in the future. The default value is C<unix> to create UNIX integrations.
+
+=cut
+
+our $DUO_TYPE = 'unix';
+
+=back
+
=head1 FILE OBJECT CONFIGURATION
These configuration variables only need to be set if you intend to use the
diff --git a/perl/Wallet/Object/Duo.pm b/perl/Wallet/Object/Duo.pm
new file mode 100644
index 0000000..af2dfab
--- /dev/null
+++ b/perl/Wallet/Object/Duo.pm
@@ -0,0 +1,331 @@
+# Wallet::Object::Duo -- Duo integration object implementation for the wallet.
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package Wallet::Object::Duo;
+require 5.006;
+
+use strict;
+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::Base;
+
+@ISA = qw(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';
+
+##############################################################################
+# Core methods
+##############################################################################
+
+# Override attr_show to display the Duo integration key attribute.
+sub attr_show {
+ my ($self) = @_;
+ my $output = '';
+ 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;
+ }
+ return sprintf ("%15s: %s\n", 'Duo key', $key);
+}
+
+# Override new to start by creating a Net::Duo::Admin object for subsequent
+# calls.
+sub new {
+ my ($class, $type, $name, $schema) = @_;
+
+ # We have to have a Duo integration key file set.
+ if (not $Wallet::Config::DUO_KEY_FILE) {
+ die "duo object implementation not configured\n";
+ }
+ my $key_file = $Wallet::Config::DUO_KEY_FILE;
+ my $agent = $Wallet::Config::DUO_AGENT;
+
+ # Construct the Net::Duo::Admin object.
+ require Net::Duo::Admin;
+ my $duo = Net::Duo::Admin->new (
+ {
+ key_file => $key_file,
+ user_agent => $agent,
+ }
+ );
+
+ # Construct the object.
+ my $self = $class->SUPER::new ($type, $name, $schema);
+ $self->{duo} = $duo;
+ return $self;
+}
+
+# Override create to start by creating a new integration in Duo, and only
+# create the entry in the database if that succeeds. Error handling isn't
+# 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) = @_;
+
+ # We have to have a Duo integration key file set.
+ if (not $Wallet::Config::DUO_KEY_FILE) {
+ die "duo object implementation not configured\n";
+ }
+ my $key_file = $Wallet::Config::DUO_KEY_FILE;
+ my $agent = $Wallet::Config::DUO_AGENT;
+
+ # Construct the Net::Duo::Admin object.
+ require Net::Duo::Admin;
+ my $duo = Net::Duo::Admin->new (
+ {
+ key_file => $key_file,
+ user_agent => $agent,
+ }
+ );
+
+ # Create the object in Duo.
+ require Net::Duo::Admin::Integration;
+ my %data = (
+ name => $name,
+ notes => 'Managed by wallet',
+ type => $Wallet::Config::DUO_TYPE,
+ );
+ my $integration = Net::Duo::Admin::Integration->create ($duo, \%data);
+
+ # Create the object in wallet.
+ my @trace = ($creator, $host, $time);
+ my $self = $class->SUPER::create ($type, $name, $schema, @trace);
+ $self->{duo} = $duo;
+
+ # Add the integration key to the object metadata.
+ my $guard = $self->{schema}->txn_scope_guard;
+ eval {
+ my %record = (
+ du_name => $name,
+ du_key => $integration->integration_key,
+ );
+ $self->{schema}->resultset ('Duo')->create (\%record);
+ $guard->commit;
+ };
+ if ($@) {
+ my $id = $self->{type} . ':' . $self->{name};
+ $self->error ("cannot set Duo key for $id: $@");
+ return;
+ }
+
+ # Done. Return the object.
+ return $self;
+}
+
+# Override destroy to delete the integration out of Duo as well.
+sub destroy {
+ my ($self, $user, $host, $time) = @_;
+ my $id = $self->{type} . ':' . $self->{name};
+ if ($self->flag_check ('locked')) {
+ $self->error ("cannot destroy $id: object is locked");
+ return;
+ }
+ my $schema = $self->{schema};
+ my $guard = $schema->txn_scope_guard;
+ eval {
+ my %search = (du_name => $self->{name});
+ my $row = $schema->resultset ('Duo')->find (\%search);
+ my $key = $row->get_column ('du_key');
+ my $int = Net::Duo::Admin::Integration->new ($self->{duo}, $key);
+ $int->delete;
+ $row->delete;
+ $guard->commit;
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ return $self->SUPER::destroy ($user, $host, $time);
+}
+
+# Our get implementation. Retrieve the integration information from Duo and
+# construct the configuration file expected by the Duo 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);
+ 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
+
+=head1 NAME
+
+Wallet::Object::Duo - Duo integration object implementation for wallet
+
+=head1 SYNOPSIS
+
+ my @name = qw(duo host.example.com);
+ my @trace = ($user, $host, time);
+ my $object = Wallet::Object::Duo->create (@name, $schema, @trace);
+ my $config = $object->get (@trace);
+ $object->destroy (@trace);
+
+=head1 DESCRIPTION
+
+Wallet::Object::Duo is a representation of Duo integrations the wallet.
+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.
+
+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.
+
+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::Base. 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 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
+normally C<duo> 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. 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.
+
+=item destroy(PRINCIPAL, HOSTNAME [, DATETIME])
+
+Destroys a Duo integration object by removing it from the database and
+deleting the integration from Duo. If deleting the Duo integration fails,
+destroy() fails. Returns true on success and false on failure. The
+caller should call error() to get the error message after a failure.
+PRINCIPAL, HOSTNAME, and DATETIME are stored as history information.
+PRINCIPAL should be the user who is destroying the object. If DATETIME
+isn't given, the current time is used.
+
+=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.
+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
+
+Net::Duo(3), Wallet::Config(3), Wallet::Object::Base(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 AUTHOR
+
+Russ Allbery <eagle@eyrie.org>
+
+=cut
diff --git a/perl/Wallet/Schema.pm b/perl/Wallet/Schema.pm
index 2176cab..74b4c99 100644
--- a/perl/Wallet/Schema.pm
+++ b/perl/Wallet/Schema.pm
@@ -18,7 +18,7 @@ use base 'DBIx::Class::Schema';
# 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.
-our $VERSION = '0.08';
+our $VERSION = '0.09';
__PACKAGE__->load_namespaces;
__PACKAGE__->load_components (qw/Schema::Versioned/);
@@ -272,6 +272,21 @@ oh_by stores the authenticated identity that made the change, oh_from
stores the host from which they made the change, and oh_on stores the time
the change was made.
+=head2 Duo Backend Data
+
+Duo integration objects store some additional metadata about the
+integration to aid in synchronization with Duo.
+
+ create table duo
+ (du_name varchar(255)
+ not null references objects(ob_name),
+ du_key varchar(255) not null);
+ create index du_key on duo (du_key);
+
+du_key holds the Duo integration key, which is the unique name of the
+integration within Duo. Additional data may be added later to represent
+the other possible settings within Duo.
+
=head2 Keytab Backend Data
The keytab backend has stub support for synchronizing keys with an
diff --git a/perl/Wallet/Schema/Result/Duo.pm b/perl/Wallet/Schema/Result/Duo.pm
new file mode 100644
index 0000000..80a71dc
--- /dev/null
+++ b/perl/Wallet/Schema/Result/Duo.pm
@@ -0,0 +1,53 @@
+# Wallet schema for Duo metadata.
+#
+# Written by Jon Robertson <jonrober@stanford.edu>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+package Wallet::Schema::Result::Duo;
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=for stopwords
+keytab enctype
+
+=head1 NAME
+
+Wallet::Schema::Result::Duo - Wallet schema for Duo metadata
+
+=head1 DESCRIPTION
+
+=cut
+
+__PACKAGE__->table("duo");
+
+=head1 ACCESSORS
+
+=head2 du_name
+
+ data_type: 'varchar'
+ is_nullable: 0
+ size: 255
+
+=head2 du_key
+
+ data_type: 'varchar'
+ is_nullable: 0
+ size: 255
+
+=cut
+
+__PACKAGE__->add_columns(
+ "du_name",
+ { data_type => "varchar", is_nullable => 0, size => 255 },
+ "du_key",
+ { data_type => "varchar", is_nullable => 0, size => 255 },
+);
+__PACKAGE__->set_primary_key("du_name");
+
+1;