From 34d83c2dd2a17af92e48d74ebfbbfce28c7e8525 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Wed, 8 Oct 2014 20:33:56 -0700 Subject: Split Duo type out into multiple sub-types The existing functionality is now in the duo-pam object type. The old duo type now returns output in a generic config file, and new types for the Duo auth proxy in LDAP and Radius proxies are added. Change-Id: I1525d79b44dafcf3ef85368297baefafcb5dc179 --- perl/lib/Wallet/Admin.pm | 3 + perl/lib/Wallet/Object/Duo.pm | 41 +++--- perl/lib/Wallet/Object/Duo/LDAPProxy.pm | 202 +++++++++++++++++++++++++++++ perl/lib/Wallet/Object/Duo/PAM.pm | 205 ++++++++++++++++++++++++++++++ perl/lib/Wallet/Object/Duo/RadiusProxy.pm | 204 +++++++++++++++++++++++++++++ 5 files changed, 633 insertions(+), 22 deletions(-) create mode 100644 perl/lib/Wallet/Object/Duo/LDAPProxy.pm create mode 100644 perl/lib/Wallet/Object/Duo/PAM.pm create mode 100644 perl/lib/Wallet/Object/Duo/RadiusProxy.pm diff --git a/perl/lib/Wallet/Admin.pm b/perl/lib/Wallet/Admin.pm index d32a0c2..381ffc9 100644 --- a/perl/lib/Wallet/Admin.pm +++ b/perl/lib/Wallet/Admin.pm @@ -126,6 +126,9 @@ sub default_data { # types default rows. my @record = ([ qw/ty_name ty_class/ ], [ 'duo', 'Wallet::Object::Duo' ], + [ 'duo-pam', 'Wallet::Object::Duo::PAM' ], + [ 'duo-radius', 'Wallet::Object::Duo::RadiusProxy' ], + [ 'duo-ldap', 'Wallet::Object::Duo::LDAPProxy' ], [ 'file', 'Wallet::Object::File' ], [ 'keytab', 'Wallet::Object::Keytab' ], [ 'wa-keyring', 'Wallet::Object::WAKeyring' ]); diff --git a/perl/lib/Wallet/Object/Duo.pm b/perl/lib/Wallet/Object/Duo.pm index 6edc4fa..168ec8b 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 # 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 @@ -84,7 +84,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 +104,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); @@ -194,10 +195,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 +235,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 +258,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 +272,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. See L -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. See L for more information. If create() fails, it throws an exception. @@ -314,9 +314,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..cd32523 --- /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 +# 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, 'ldap'); + 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 + +=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 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 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 = + skey = + api_host = + +The C 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. + +=head1 AUTHORS + +Jon Robertson + +=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 +# Jon Robertson +# 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 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 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 = + skey = + host = + +The C 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. + +=head1 AUTHORS + +Russ Allbery +Jon Robertson + +=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..2a29dec --- /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 +# 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 + +=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 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 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 = + skey = + api_host = + client = radius_client + +The C 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. + +=head1 AUTHORS + +Jon Robertson + +=cut -- cgit v1.2.3 From 8cafcdfdaf66ca7a7735c899ce99a714e718197c Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Thu, 9 Oct 2014 09:19:22 -0700 Subject: NEWS: Added item for Duo object expansion Change-Id: I91b8b5fd4043effe8b23a62624c47519976ace64 --- NEWS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS b/NEWS index 039494e..2d1a1e2 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,14 @@ User-Visible wallet Changes +wallet 1.11 (2014-10-08) + + The duo object type has been split into several sub-types, each for a + specific type of Duo integration. The old type's functionality has + been moved to duo-pam (Wallet::Object::Duo::PAM), and new types are + supported for Duo's auth proxy configurations for LDAP and Radius. + These types are duo-radius and duo-ldap + (Wallet::Object::Duo::RadiusProxy and Wallet::Object::Duo::LDAPProxy). + wallet 1.1 (2014-07-16) A new object type, duo (Wallet::Object::Duo), is now supported. This -- cgit v1.2.3 From 94cfb1c7969515a863d7b0e09f00b6ced5f4fc5c Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Thu, 9 Oct 2014 09:55:21 -0700 Subject: Created tests for new Duo object types Change-Id: I818be125f3195316b44e650ba6e05b8e0b831ea6 --- perl/lib/Wallet/Config.pm | 6 +- perl/lib/Wallet/Object/Duo/LDAPProxy.pm | 2 +- perl/t/data/duo/integration-ldap.json | 11 +++ perl/t/data/duo/integration-radius.json | 11 +++ perl/t/object/duo-ldap.t | 160 +++++++++++++++++++++++++++++++ perl/t/object/duo-pam.t | 159 ++++++++++++++++++++++++++++++ perl/t/object/duo-radius.t | 165 ++++++++++++++++++++++++++++++++ perl/t/object/duo.t | 7 +- 8 files changed, 513 insertions(+), 8 deletions(-) create mode 100644 perl/t/data/duo/integration-ldap.json create mode 100644 perl/t/data/duo/integration-radius.json create mode 100644 perl/t/object/duo-ldap.t create mode 100644 perl/t/object/duo-pam.t create mode 100644 perl/t/object/duo-radius.t diff --git a/perl/lib/Wallet/Config.pm b/perl/lib/Wallet/Config.pm index 527658c..2eb57f9 100644 --- a/perl/lib/Wallet/Config.pm +++ b/perl/lib/Wallet/Config.pm @@ -217,9 +217,9 @@ 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 to create UNIX integrations. +The type of integration to create. The default value is C to create +UNIX integrations, since this was the first integration created and users +may rely on it to still be the default. =cut diff --git a/perl/lib/Wallet/Object/Duo/LDAPProxy.pm b/perl/lib/Wallet/Object/Duo/LDAPProxy.pm index cd32523..757a61a 100644 --- a/perl/lib/Wallet/Object/Duo/LDAPProxy.pm +++ b/perl/lib/Wallet/Object/Duo/LDAPProxy.pm @@ -42,7 +42,7 @@ sub create { $time ||= time; my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host, - $time, 'ldap'); + $time, 'ldapproxy'); return $self; } diff --git a/perl/t/data/duo/integration-ldap.json b/perl/t/data/duo/integration-ldap.json new file mode 100644 index 0000000..78a4c9f --- /dev/null +++ b/perl/t/data/duo/integration-ldap.json @@ -0,0 +1,11 @@ +{ + "enroll_policy": "enroll", + "greeting": "", + "groups_allowed": [], + "integration_key": "DIRWIH0ZZPV4G88B37VQ", + "name": "Integration for LDAP proxy", + "notes": "", + "secret_key": "QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o", + "type": "ldap", + "visual_style": "default" +} diff --git a/perl/t/data/duo/integration-radius.json b/perl/t/data/duo/integration-radius.json new file mode 100644 index 0000000..514a33e --- /dev/null +++ b/perl/t/data/duo/integration-radius.json @@ -0,0 +1,11 @@ +{ + "enroll_policy": "enroll", + "greeting": "", + "groups_allowed": [], + "integration_key": "DIRWIH0ZZPV4G88B37VQ", + "name": "Integration for Radius proxy", + "notes": "", + "secret_key": "QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o", + "type": "radius", + "visual_style": "default" +} diff --git a/perl/t/object/duo-ldap.t b/perl/t/object/duo-ldap.t new file mode 100644 index 0000000..3648eba --- /dev/null +++ b/perl/t/object/duo-ldap.t @@ -0,0 +1,160 @@ +#!/usr/bin/perl +# +# Tests for the Duo Auth proxy LDAP integration object implementation. +# +# Written by Russ Allbery +# Copyright 2014 +# The Board of Trustees of the Leland Stanford Junior University +# +# See LICENSE for licensing terms. + +use strict; +use warnings; + +use POSIX qw(strftime); +use Test::More; + +BEGIN { + eval 'use Net::Duo'; + plan skip_all => 'Net::Duo required for testing duo' + if $@; + eval 'use Net::Duo::Mock::Agent'; + plan skip_all => 'Net::Duo::Mock::Agent required for testing duo' + if $@; +} + +BEGIN { + use_ok('Wallet::Admin'); + use_ok('Wallet::Config'); + use_ok('Wallet::Object::Duo::LDAPProxy'); +} + +use lib 't/lib'; +use Util; + +# Some global defaults to use. +my $user = 'admin@EXAMPLE.COM'; +my $host = 'localhost'; +my @trace = ($user, $host, time); +my $date = strftime ('%Y-%m-%d %H:%M:%S', localtime $trace[2]); + +# Flush all output immediately. +$| = 1; + +# Use Wallet::Admin to set up the database. +db_setup; +my $admin = eval { Wallet::Admin->new }; +is ($@, '', 'Database connection succeeded'); +is ($admin->reinitialize ($user), 1, 'Database initialization succeeded'); +my $schema = $admin->schema; + +# Create a mock object to use for Duo calls. +my $mock = Net::Duo::Mock::Agent->new ({ key_file => 't/data/duo/keys.json' }); + +# Test error handling in the absence of configuration. +my $object = eval { + Wallet::Object::Duo::LDAPProxy->new ('duo-ldap', 'test', $schema); +}; +is ($object, undef, 'Wallet::Object::Duo::LDAPProxy new with no config failed'); +is ($@, "duo object implementation not configured\n", '...with correct error'); +$object = eval { + Wallet::Object::Duo::LDAPProxy->create ('duo-ldap', 'test', $schema, + @trace); +}; +is ($object, undef, 'Wallet::Object::Duo::LDAPProxy creation with no config failed'); +is ($@, "duo object implementation not configured\n", '...with correct error'); + +# Set up the Duo configuration. +$Wallet::Config::DUO_AGENT = $mock; +$Wallet::Config::DUO_KEY_FILE = 't/data/duo/keys.json'; + +# Test creating an integration. +note ('Test creating an integration'); +my $expected = { + name => 'test (ldapproxy)', + notes => 'Managed by wallet', + type => 'ldapproxy', +}; +$mock->expect ( + { + method => 'POST', + uri => '/admin/v1/integrations', + content => $expected, + response_file => 't/data/duo/integration.json', + } +); +$object = Wallet::Object::Duo::LDAPProxy->create ('duo-ldap', 'test', $schema, + @trace); +isa_ok ($object, 'Wallet::Object::Duo::LDAPProxy'); + +# Check the metadata about the new wallet object. +$expected = <<"EOO"; + Type: duo-ldap + Name: test + Duo key: DIRWIH0ZZPV4G88B37VQ + Created by: $user + Created from: $host + Created on: $date +EOO +is ($object->show, $expected, 'Show output is correct'); + +# Test retrieving the integration information. +note ('Test retrieving an integration'); +$mock->expect ( + { + method => 'GET', + uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ', + response_file => 't/data/duo/integration-ldap.json', + } +); +my $data = $object->get (@trace); +ok (defined ($data), 'Retrieval succeeds'); +$expected = <<'EOO'; +[ldap_server_challenge] +ikey = DIRWIH0ZZPV4G88B37VQ +skey = QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o +api_host = example-admin.duosecurity.com +EOO +is ($data, $expected, '...and integration data is correct'); + +# Ensure that we can't retrieve the object when locked. +is ($object->flag_set ('locked', @trace), 1, + 'Setting object to locked succeeds'); +is ($object->get, undef, '...and now get fails'); +is ($object->error, 'cannot get duo-ldap:test: object is locked', + '...with correct error'); +is ($object->flag_clear ('locked', @trace), 1, + '...and clearing locked flag works'); + +# Create a new object by wallet type and name. +$object = Wallet::Object::Duo::LDAPProxy->new ('duo-ldap', 'test', $schema); + +# Test deleting an integration. We can't test this entirely properly because +# currently Net::Duo::Mock::Agent doesn't support stacking multiple expected +# calls and delete makes two calls. +note ('Test deleting an integration'); +$mock->expect ( + { + method => 'GET', + uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ', + response_file => 't/data/duo/integration.json', + } +); +TODO: { + local $TODO = 'Net::Duo::Mock::Agent not yet capable'; + + is ($object->destroy (@trace), 1, 'Duo object deletion succeeded'); + $object = eval { Wallet::Object::Duo::LDAPProxy->new ('duo-ldap', 'test', + $schema) }; + is ($object, undef, '...and now object cannot be retrieved'); + is ($@, "cannot find duo:test\n", '...with correct error'); +} + +# Clean up. +$admin->destroy; +END { + unlink ('wallet-db'); +} + +# Done testing. +done_testing (); diff --git a/perl/t/object/duo-pam.t b/perl/t/object/duo-pam.t new file mode 100644 index 0000000..7b88787 --- /dev/null +++ b/perl/t/object/duo-pam.t @@ -0,0 +1,159 @@ +#!/usr/bin/perl +# +# Tests for the Duo PAM integration object implementation. +# +# Written by Russ Allbery +# Copyright 2014 +# The Board of Trustees of the Leland Stanford Junior University +# +# See LICENSE for licensing terms. + +use strict; +use warnings; + +use POSIX qw(strftime); +use Test::More; + +BEGIN { + eval 'use Net::Duo'; + plan skip_all => 'Net::Duo required for testing duo' + if $@; + eval 'use Net::Duo::Mock::Agent'; + plan skip_all => 'Net::Duo::Mock::Agent required for testing duo' + if $@; +} + +BEGIN { + use_ok('Wallet::Admin'); + use_ok('Wallet::Config'); + use_ok('Wallet::Object::Duo::PAM'); +} + +use lib 't/lib'; +use Util; + +# Some global defaults to use. +my $user = 'admin@EXAMPLE.COM'; +my $host = 'localhost'; +my @trace = ($user, $host, time); +my $date = strftime ('%Y-%m-%d %H:%M:%S', localtime $trace[2]); + +# Flush all output immediately. +$| = 1; + +# Use Wallet::Admin to set up the database. +db_setup; +my $admin = eval { Wallet::Admin->new }; +is ($@, '', 'Database connection succeeded'); +is ($admin->reinitialize ($user), 1, 'Database initialization succeeded'); +my $schema = $admin->schema; + +# Create a mock object to use for Duo calls. +my $mock = Net::Duo::Mock::Agent->new ({ key_file => 't/data/duo/keys.json' }); + +# Test error handling in the absence of configuration. +my $object = eval { + Wallet::Object::Duo::PAM->new ('duo-pam', 'test', $schema); +}; +is ($object, undef, 'Wallet::Object::Duo::PAM new with no config failed'); +is ($@, "duo object implementation not configured\n", '...with correct error'); +$object = eval { + Wallet::Object::Duo::PAM->create ('duo-pam', 'test', $schema, @trace); +}; +is ($object, undef, 'Wallet::Object::Duo::PAM creation with no config failed'); +is ($@, "duo object implementation not configured\n", '...with correct error'); + +# Set up the Duo configuration. +$Wallet::Config::DUO_AGENT = $mock; +$Wallet::Config::DUO_KEY_FILE = 't/data/duo/keys.json'; + +# Test creating an integration. +note ('Test creating an integration'); +my $expected = { + name => 'test (unix)', + notes => 'Managed by wallet', + type => 'unix', +}; +$mock->expect ( + { + method => 'POST', + uri => '/admin/v1/integrations', + content => $expected, + response_file => 't/data/duo/integration.json', + } +); +$object = Wallet::Object::Duo::PAM->create ('duo-pam', 'test', $schema, + @trace); +isa_ok ($object, 'Wallet::Object::Duo::PAM'); + +# Check the metadata about the new wallet object. +$expected = <<"EOO"; + Type: duo-pam + Name: test + Duo key: DIRWIH0ZZPV4G88B37VQ + Created by: $user + Created from: $host + Created on: $date +EOO +is ($object->show, $expected, 'Show output is correct'); + +# Test retrieving the integration information. +note ('Test retrieving an integration'); +$mock->expect ( + { + method => 'GET', + uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ', + response_file => 't/data/duo/integration.json', + } +); +my $data = $object->get (@trace); +ok (defined ($data), 'Retrieval succeeds'); +$expected = <<'EOO'; +[duo] +ikey = DIRWIH0ZZPV4G88B37VQ +skey = QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o +host = example-admin.duosecurity.com +EOO +is ($data, $expected, '...and integration data is correct'); + +# Ensure that we can't retrieve the object when locked. +is ($object->flag_set ('locked', @trace), 1, + 'Setting object to locked succeeds'); +is ($object->get, undef, '...and now get fails'); +is ($object->error, 'cannot get duo-pam:test: object is locked', + '...with correct error'); +is ($object->flag_clear ('locked', @trace), 1, + '...and clearing locked flag works'); + +# Create a new object by wallet type and name. +$object = Wallet::Object::Duo::PAM->new ('duo-pam', 'test', $schema); + +# Test deleting an integration. We can't test this entirely properly because +# currently Net::Duo::Mock::Agent doesn't support stacking multiple expected +# calls and delete makes two calls. +note ('Test deleting an integration'); +$mock->expect ( + { + method => 'GET', + uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ', + response_file => 't/data/duo/integration.json', + } +); +TODO: { + local $TODO = 'Net::Duo::Mock::Agent not yet capable'; + + is ($object->destroy (@trace), 1, 'Duo object deletion succeeded'); + $object = eval { Wallet::Object::Duo::PAM->new ('duo-pam', 'test', + $schema) }; + is ($object, undef, '...and now object cannot be retrieved'); + is ($@, "cannot find duo:test\n", '...with correct error'); +} + +# Clean up. +$admin->destroy; +END { + unlink ('wallet-db'); +} + +# Done testing. +done_testing (); diff --git a/perl/t/object/duo-radius.t b/perl/t/object/duo-radius.t new file mode 100644 index 0000000..f258518 --- /dev/null +++ b/perl/t/object/duo-radius.t @@ -0,0 +1,165 @@ +#!/usr/bin/perl +# +# Tests for the Duo Auth proxy Radius integration object implementation. +# +# Written by Russ Allbery +# Copyright 2014 +# The Board of Trustees of the Leland Stanford Junior University +# +# See LICENSE for licensing terms. + +use strict; +use warnings; + +use POSIX qw(strftime); +use Test::More; + +BEGIN { + eval 'use Net::Duo'; + plan skip_all => 'Net::Duo required for testing duo' + if $@; + eval 'use Net::Duo::Mock::Agent'; + plan skip_all => 'Net::Duo::Mock::Agent required for testing duo' + if $@; +} + +BEGIN { + use_ok('Wallet::Admin'); + use_ok('Wallet::Config'); + use_ok('Wallet::Object::Duo::RadiusProxy'); +} + +use lib 't/lib'; +use Util; + +# Some global defaults to use. +my $user = 'admin@EXAMPLE.COM'; +my $host = 'localhost'; +my @trace = ($user, $host, time); +my $date = strftime ('%Y-%m-%d %H:%M:%S', localtime $trace[2]); + +# Flush all output immediately. +$| = 1; + +# Use Wallet::Admin to set up the database. +db_setup; +my $admin = eval { Wallet::Admin->new }; +is ($@, '', 'Database connection succeeded'); +is ($admin->reinitialize ($user), 1, 'Database initialization succeeded'); +my $schema = $admin->schema; + +# Create a mock object to use for Duo calls. +my $mock = Net::Duo::Mock::Agent->new ({ key_file => 't/data/duo/keys.json' }); + +# Test error handling in the absence of configuration. +my $object = eval { + Wallet::Object::Duo::RadiusProxy->new ('duo-raduys', 'test', $schema); +}; +is ($object, undef, + 'Wallet::Object::Duo::RadiusProxy new with no config failed'); +is ($@, "duo object implementation not configured\n", '...with correct error'); +$object = eval { + Wallet::Object::Duo::RadiusProxy->create ('duo-radius', 'test', $schema, + @trace); +}; +is ($object, undef, + 'Wallet::Object::Duo::RadiusProxy creation with no config failed'); +is ($@, "duo object implementation not configured\n", '...with correct error'); + +# Set up the Duo configuration. +$Wallet::Config::DUO_AGENT = $mock; +$Wallet::Config::DUO_KEY_FILE = 't/data/duo/keys.json'; + +# Test creating an integration. +note ('Test creating an integration'); +my $expected = { + name => 'test (radius)', + notes => 'Managed by wallet', + type => 'radius', +}; +$mock->expect ( + { + method => 'POST', + uri => '/admin/v1/integrations', + content => $expected, + response_file => 't/data/duo/integration-radius.json', + } +); +$object = Wallet::Object::Duo::RadiusProxy->create ('duo-radius', 'test', + $schema, @trace); +isa_ok ($object, 'Wallet::Object::Duo::RadiusProxy'); + +# Check the metadata about the new wallet object. +$expected = <<"EOO"; + Type: duo-radius + Name: test + Duo key: DIRWIH0ZZPV4G88B37VQ + Created by: $user + Created from: $host + Created on: $date +EOO +is ($object->show, $expected, 'Show output is correct'); + +# Test retrieving the integration information. +note ('Test retrieving an integration'); +$mock->expect ( + { + method => 'GET', + uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ', + response_file => 't/data/duo/integration-radius.json', + } +); +my $data = $object->get (@trace); +ok (defined ($data), 'Retrieval succeeds'); +$expected = <<'EOO'; +[radius_server_challenge] +ikey = DIRWIH0ZZPV4G88B37VQ +skey = QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o +api_host = example-admin.duosecurity.com +client = radius_client +EOO +is ($data, $expected, '...and integration data is correct'); + +# Ensure that we can't retrieve the object when locked. +is ($object->flag_set ('locked', @trace), 1, + 'Setting object to locked succeeds'); +is ($object->get, undef, '...and now get fails'); +is ($object->error, 'cannot get duo-radius:test: object is locked', + '...with correct error'); +is ($object->flag_clear ('locked', @trace), 1, + '...and clearing locked flag works'); + +# Create a new object by wallet type and name. +$object = Wallet::Object::Duo::RadiusProxy->new ('duo-radius', 'test', + $schema); + +# Test deleting an integration. We can't test this entirely properly because +# currently Net::Duo::Mock::Agent doesn't support stacking multiple expected +# calls and delete makes two calls. +note ('Test deleting an integration'); +$mock->expect ( + { + method => 'GET', + uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ', + response_file => 't/data/duo/integration.json', + } +); +TODO: { + local $TODO = 'Net::Duo::Mock::Agent not yet capable'; + + is ($object->destroy (@trace), 1, 'Duo object deletion succeeded'); + $object = eval { + Wallet::Object::Duo::RadiusProxy->new ('duo-radius', 'test', $schema); + }; + is ($object, undef, '...and now object cannot be retrieved'); + is ($@, "cannot find duo:test\n", '...with correct error'); +} + +# Clean up. +$admin->destroy; +END { + unlink ('wallet-db'); +} + +# Done testing. +done_testing (); diff --git a/perl/t/object/duo.t b/perl/t/object/duo.t index f73fe7e..a975597 100755 --- a/perl/t/object/duo.t +++ b/perl/t/object/duo.t @@ -108,10 +108,9 @@ $mock->expect ( my $data = $object->get (@trace); ok (defined ($data), 'Retrieval succeeds'); $expected = <<'EOO'; -[duo] -ikey = DIRWIH0ZZPV4G88B37VQ -skey = QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o -host = example-admin.duosecurity.com +Integration key: DIRWIH0ZZPV4G88B37VQ +Secret key: QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o +Host: example-admin.duosecurity.com EOO is ($data, $expected, '...and integration data is correct'); -- cgit v1.2.3 From 58186415a0f232cf84ced650ecc1326df66f9c6d Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Thu, 9 Oct 2014 22:24:18 -0700 Subject: Added new column in the Duo table New column is required to differentiate the Duo table entries now that we have more than one Duo object type. Added the new field and rebuilt schema definitions and upgrade files. Change-Id: Icf538eaded93f4f2820984c087d4850a586a7db1 --- perl/lib/Wallet/Schema.pm | 2 +- perl/lib/Wallet/Schema/Result/Duo.pm | 14 +- perl/sql/Wallet-Schema-0.09-0.10-MySQL.sql | 14 ++ perl/sql/Wallet-Schema-0.09-0.10-PostgreSQL.sql | 18 ++ perl/sql/Wallet-Schema-0.09-0.10-SQLite.sql | 26 +++ perl/sql/Wallet-Schema-0.10-MySQL.sql | 209 ++++++++++++++++++++++ perl/sql/Wallet-Schema-0.10-PostgreSQL.sql | 216 +++++++++++++++++++++++ perl/sql/Wallet-Schema-0.10-SQLite.sql | 220 ++++++++++++++++++++++++ 8 files changed, 716 insertions(+), 3 deletions(-) create mode 100644 perl/sql/Wallet-Schema-0.09-0.10-MySQL.sql create mode 100644 perl/sql/Wallet-Schema-0.09-0.10-PostgreSQL.sql create mode 100644 perl/sql/Wallet-Schema-0.09-0.10-SQLite.sql create mode 100644 perl/sql/Wallet-Schema-0.10-MySQL.sql create mode 100644 perl/sql/Wallet-Schema-0.10-PostgreSQL.sql create mode 100644 perl/sql/Wallet-Schema-0.10-SQLite.sql diff --git a/perl/lib/Wallet/Schema.pm b/perl/lib/Wallet/Schema.pm index cb4c93e..5b850c0 100644 --- a/perl/lib/Wallet/Schema.pm +++ b/perl/lib/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.09'; +our $VERSION = '0.10'; __PACKAGE__->load_namespaces; __PACKAGE__->load_components (qw/Schema::Versioned/); diff --git a/perl/lib/Wallet/Schema/Result/Duo.pm b/perl/lib/Wallet/Schema/Result/Duo.pm index 80a71dc..6ad61e9 100644 --- a/perl/lib/Wallet/Schema/Result/Duo.pm +++ b/perl/lib/Wallet/Schema/Result/Duo.pm @@ -45,9 +45,19 @@ __PACKAGE__->table("duo"); __PACKAGE__->add_columns( "du_name", { data_type => "varchar", is_nullable => 0, size => 255 }, + "du_type", + { data_type => "varchar", is_nullable => 0, size => 16 }, "du_key", { data_type => "varchar", is_nullable => 0, size => 255 }, ); -__PACKAGE__->set_primary_key("du_name"); - +__PACKAGE__->set_primary_key("du_name", "du_type"); + +__PACKAGE__->belongs_to( + 'object', + 'Wallet::Schema::Result::Object', + { + 'foreign.ob_type' => 'self.du_type', + 'foreign.ob_name' => 'self.du_name', + }, + ); 1; diff --git a/perl/sql/Wallet-Schema-0.09-0.10-MySQL.sql b/perl/sql/Wallet-Schema-0.09-0.10-MySQL.sql new file mode 100644 index 0000000..3c54c6d --- /dev/null +++ b/perl/sql/Wallet-Schema-0.09-0.10-MySQL.sql @@ -0,0 +1,14 @@ +-- Convert schema 'sql/Wallet-Schema-0.09-MySQL.sql' to 'Wallet::Schema v0.10':; + +BEGIN; + +ALTER TABLE duo DROP PRIMARY KEY, + ADD COLUMN du_type varchar(16) NOT NULL, + ADD INDEX duo_idx_du_type_du_name (du_type, du_name), + ADD PRIMARY KEY (du_name, du_type), + ADD CONSTRAINT duo_fk_du_type_du_name FOREIGN KEY (du_type, du_name) REFERENCES objects (ob_type, ob_name), + ENGINE=InnoDB; + + +COMMIT; + diff --git a/perl/sql/Wallet-Schema-0.09-0.10-PostgreSQL.sql b/perl/sql/Wallet-Schema-0.09-0.10-PostgreSQL.sql new file mode 100644 index 0000000..c69e6a5 --- /dev/null +++ b/perl/sql/Wallet-Schema-0.09-0.10-PostgreSQL.sql @@ -0,0 +1,18 @@ +-- Convert schema 'sql/Wallet-Schema-0.09-PostgreSQL.sql' to 'sql/Wallet-Schema-0.10-PostgreSQL.sql':; + +BEGIN; + +ALTER TABLE duo DROP CONSTRAINT duo_pkey; + +ALTER TABLE duo ADD COLUMN du_type character varying(16) NOT NULL; + +CREATE INDEX duo_idx_du_type_du_name on duo (du_type, du_name); + +ALTER TABLE duo ADD PRIMARY KEY (du_name, du_type); + +ALTER TABLE duo ADD CONSTRAINT duo_fk_du_type_du_name FOREIGN KEY (du_type, du_name) + REFERENCES objects (ob_type, ob_name) DEFERRABLE; + + +COMMIT; + diff --git a/perl/sql/Wallet-Schema-0.09-0.10-SQLite.sql b/perl/sql/Wallet-Schema-0.09-0.10-SQLite.sql new file mode 100644 index 0000000..5feb89f --- /dev/null +++ b/perl/sql/Wallet-Schema-0.09-0.10-SQLite.sql @@ -0,0 +1,26 @@ +-- Convert schema 'sql/Wallet-Schema-0.09-SQLite.sql' to 'sql/Wallet-Schema-0.10-SQLite.sql':; + +BEGIN; + +-- Back up Duo data to a temp table. SQLite has limited ALTER TABLE support, +-- so we need to do this to alter the keys on the table. +CREATE TEMPORARY TABLE duo_backup ( + du_name varchar(255) NOT NULL, + du_key varchar(255) NOT NULL, + PRIMARY KEY (du_name) +); +INSERT INTO duo_backup SELECT du_name,du_key FROM duo; +DROP TABLE duo; + +-- Create the new Duo table and move the old data into it. +CREATE TABLE duo ( + du_name varchar(255) NOT NULL, + du_type varchar(16) NOT NULL, + du_key varchar(255) NOT NULL, + PRIMARY KEY (du_name, du_type), + FOREIGN KEY (du_type, du_name) REFERENCES objects(ob_type, ob_name) +); +INSERT INTO duo SELECT du_name,du_key,'duo' FROM duo_backup; +DROP TABLE duo_backup; + +COMMIT; diff --git a/perl/sql/Wallet-Schema-0.10-MySQL.sql b/perl/sql/Wallet-Schema-0.10-MySQL.sql new file mode 100644 index 0000000..c0b7fcc --- /dev/null +++ b/perl/sql/Wallet-Schema-0.10-MySQL.sql @@ -0,0 +1,209 @@ +-- +-- Created by SQL::Translator::Producer::MySQL +-- Created on Thu Oct 9 20:54:55 2014 +-- +SET foreign_key_checks=0; + +DROP TABLE IF EXISTS `acl_history`; + +-- +-- Table: `acl_history` +-- +CREATE TABLE `acl_history` ( + `ah_id` integer NOT NULL auto_increment, + `ah_acl` integer NOT NULL, + `ah_name` varchar(255) NULL, + `ah_action` varchar(16) NOT NULL, + `ah_scheme` varchar(32) NULL, + `ah_identifier` varchar(255) NULL, + `ah_by` varchar(255) NOT NULL, + `ah_from` varchar(255) NOT NULL, + `ah_on` datetime NOT NULL, + INDEX `acl_history_idx_ah_acl` (`ah_acl`), + INDEX `acl_history_idx_ah_name` (`ah_name`), + PRIMARY KEY (`ah_id`) +); + +DROP TABLE IF EXISTS `acl_schemes`; + +-- +-- Table: `acl_schemes` +-- +CREATE TABLE `acl_schemes` ( + `as_name` varchar(32) NOT NULL, + `as_class` varchar(64) NULL, + PRIMARY KEY (`as_name`) +) ENGINE=InnoDB; + +DROP TABLE IF EXISTS `acls`; + +-- +-- Table: `acls` +-- +CREATE TABLE `acls` ( + `ac_id` integer NOT NULL auto_increment, + `ac_name` varchar(255) NOT NULL, + PRIMARY KEY (`ac_id`), + UNIQUE `ac_name` (`ac_name`) +) ENGINE=InnoDB; + +DROP TABLE IF EXISTS `enctypes`; + +-- +-- Table: `enctypes` +-- +CREATE TABLE `enctypes` ( + `en_name` varchar(255) NOT NULL, + PRIMARY KEY (`en_name`) +); + +DROP TABLE IF EXISTS `flags`; + +-- +-- Table: `flags` +-- +CREATE TABLE `flags` ( + `fl_type` varchar(16) NOT NULL, + `fl_name` varchar(255) NOT NULL, + `fl_flag` enum('locked', 'unchanging') NOT NULL, + PRIMARY KEY (`fl_type`, `fl_name`, `fl_flag`) +); + +DROP TABLE IF EXISTS `keytab_enctypes`; + +-- +-- Table: `keytab_enctypes` +-- +CREATE TABLE `keytab_enctypes` ( + `ke_name` varchar(255) NOT NULL, + `ke_enctype` varchar(255) NOT NULL, + PRIMARY KEY (`ke_name`, `ke_enctype`) +); + +DROP TABLE IF EXISTS `keytab_sync`; + +-- +-- Table: `keytab_sync` +-- +CREATE TABLE `keytab_sync` ( + `ks_name` varchar(255) NOT NULL, + `ks_target` varchar(255) NOT NULL, + PRIMARY KEY (`ks_name`, `ks_target`) +); + +DROP TABLE IF EXISTS `object_history`; + +-- +-- Table: `object_history` +-- +CREATE TABLE `object_history` ( + `oh_id` integer NOT NULL auto_increment, + `oh_type` varchar(16) NOT NULL, + `oh_name` varchar(255) NOT NULL, + `oh_action` varchar(16) NOT NULL, + `oh_field` varchar(16) NULL, + `oh_type_field` varchar(255) NULL, + `oh_old` varchar(255) NULL, + `oh_new` varchar(255) NULL, + `oh_by` varchar(255) NOT NULL, + `oh_from` varchar(255) NOT NULL, + `oh_on` datetime NOT NULL, + INDEX `object_history_idx_oh_type_oh_name` (`oh_type`, `oh_name`), + PRIMARY KEY (`oh_id`) +); + +DROP TABLE IF EXISTS `sync_targets`; + +-- +-- Table: `sync_targets` +-- +CREATE TABLE `sync_targets` ( + `st_name` varchar(255) NOT NULL, + PRIMARY KEY (`st_name`) +); + +DROP TABLE IF EXISTS `types`; + +-- +-- Table: `types` +-- +CREATE TABLE `types` ( + `ty_name` varchar(16) NOT NULL, + `ty_class` varchar(64) NULL, + PRIMARY KEY (`ty_name`) +) ENGINE=InnoDB; + +DROP TABLE IF EXISTS `acl_entries`; + +-- +-- Table: `acl_entries` +-- +CREATE TABLE `acl_entries` ( + `ae_id` integer NOT NULL, + `ae_scheme` varchar(32) NOT NULL, + `ae_identifier` varchar(255) NOT NULL, + INDEX `acl_entries_idx_ae_scheme` (`ae_scheme`), + INDEX `acl_entries_idx_ae_id` (`ae_id`), + PRIMARY KEY (`ae_id`, `ae_scheme`, `ae_identifier`), + CONSTRAINT `acl_entries_fk_ae_scheme` FOREIGN KEY (`ae_scheme`) REFERENCES `acl_schemes` (`as_name`), + CONSTRAINT `acl_entries_fk_ae_id` FOREIGN KEY (`ae_id`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +DROP TABLE IF EXISTS `objects`; + +-- +-- Table: `objects` +-- +CREATE TABLE `objects` ( + `ob_type` varchar(16) NOT NULL, + `ob_name` varchar(255) NOT NULL, + `ob_owner` integer NULL, + `ob_acl_get` integer NULL, + `ob_acl_store` integer NULL, + `ob_acl_show` integer NULL, + `ob_acl_destroy` integer NULL, + `ob_acl_flags` integer NULL, + `ob_expires` datetime NULL, + `ob_created_by` varchar(255) NOT NULL, + `ob_created_from` varchar(255) NOT NULL, + `ob_created_on` datetime NOT NULL, + `ob_stored_by` varchar(255) NULL, + `ob_stored_from` varchar(255) NULL, + `ob_stored_on` datetime NULL, + `ob_downloaded_by` varchar(255) NULL, + `ob_downloaded_from` varchar(255) NULL, + `ob_downloaded_on` datetime NULL, + `ob_comment` varchar(255) NULL, + INDEX `objects_idx_ob_acl_destroy` (`ob_acl_destroy`), + INDEX `objects_idx_ob_acl_flags` (`ob_acl_flags`), + INDEX `objects_idx_ob_acl_get` (`ob_acl_get`), + INDEX `objects_idx_ob_owner` (`ob_owner`), + INDEX `objects_idx_ob_acl_show` (`ob_acl_show`), + INDEX `objects_idx_ob_acl_store` (`ob_acl_store`), + INDEX `objects_idx_ob_type` (`ob_type`), + PRIMARY KEY (`ob_name`, `ob_type`), + CONSTRAINT `objects_fk_ob_acl_destroy` FOREIGN KEY (`ob_acl_destroy`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `objects_fk_ob_acl_flags` FOREIGN KEY (`ob_acl_flags`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `objects_fk_ob_acl_get` FOREIGN KEY (`ob_acl_get`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `objects_fk_ob_owner` FOREIGN KEY (`ob_owner`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `objects_fk_ob_acl_show` FOREIGN KEY (`ob_acl_show`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `objects_fk_ob_acl_store` FOREIGN KEY (`ob_acl_store`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `objects_fk_ob_type` FOREIGN KEY (`ob_type`) REFERENCES `types` (`ty_name`) +) ENGINE=InnoDB; + +DROP TABLE IF EXISTS `duo`; + +-- +-- Table: `duo` +-- +CREATE TABLE `duo` ( + `du_name` varchar(255) NOT NULL, + `du_type` varchar(16) NOT NULL, + `du_key` varchar(255) NOT NULL, + INDEX `duo_idx_du_type_du_name` (`du_type`, `du_name`), + PRIMARY KEY (`du_name`, `du_type`), + CONSTRAINT `duo_fk_du_type_du_name` FOREIGN KEY (`du_type`, `du_name`) REFERENCES `objects` (`ob_type`, `ob_name`) +) ENGINE=InnoDB; + +SET foreign_key_checks=1; + diff --git a/perl/sql/Wallet-Schema-0.10-PostgreSQL.sql b/perl/sql/Wallet-Schema-0.10-PostgreSQL.sql new file mode 100644 index 0000000..3bcb0ae --- /dev/null +++ b/perl/sql/Wallet-Schema-0.10-PostgreSQL.sql @@ -0,0 +1,216 @@ +-- +-- Created by SQL::Translator::Producer::PostgreSQL +-- Created on Thu Oct 9 20:54:56 2014 +-- +-- +-- Table: acl_history. +-- +DROP TABLE "acl_history" CASCADE; +CREATE TABLE "acl_history" ( + "ah_id" serial NOT NULL, + "ah_acl" integer NOT NULL, + "ah_name" character varying(255), + "ah_action" character varying(16) NOT NULL, + "ah_scheme" character varying(32), + "ah_identifier" character varying(255), + "ah_by" character varying(255) NOT NULL, + "ah_from" character varying(255) NOT NULL, + "ah_on" timestamp NOT NULL, + PRIMARY KEY ("ah_id") +); +CREATE INDEX "acl_history_idx_ah_acl" on "acl_history" ("ah_acl"); +CREATE INDEX "acl_history_idx_ah_name" on "acl_history" ("ah_name"); + +-- +-- Table: acl_schemes. +-- +DROP TABLE "acl_schemes" CASCADE; +CREATE TABLE "acl_schemes" ( + "as_name" character varying(32) NOT NULL, + "as_class" character varying(64), + PRIMARY KEY ("as_name") +); + +-- +-- Table: acls. +-- +DROP TABLE "acls" CASCADE; +CREATE TABLE "acls" ( + "ac_id" serial NOT NULL, + "ac_name" character varying(255) NOT NULL, + PRIMARY KEY ("ac_id"), + CONSTRAINT "ac_name" UNIQUE ("ac_name") +); + +-- +-- Table: enctypes. +-- +DROP TABLE "enctypes" CASCADE; +CREATE TABLE "enctypes" ( + "en_name" character varying(255) NOT NULL, + PRIMARY KEY ("en_name") +); + +-- +-- Table: flags. +-- +DROP TABLE "flags" CASCADE; +CREATE TABLE "flags" ( + "fl_type" character varying(16) NOT NULL, + "fl_name" character varying(255) NOT NULL, + "fl_flag" character varying NOT NULL, + PRIMARY KEY ("fl_type", "fl_name", "fl_flag") +); + +-- +-- Table: keytab_enctypes. +-- +DROP TABLE "keytab_enctypes" CASCADE; +CREATE TABLE "keytab_enctypes" ( + "ke_name" character varying(255) NOT NULL, + "ke_enctype" character varying(255) NOT NULL, + PRIMARY KEY ("ke_name", "ke_enctype") +); + +-- +-- Table: keytab_sync. +-- +DROP TABLE "keytab_sync" CASCADE; +CREATE TABLE "keytab_sync" ( + "ks_name" character varying(255) NOT NULL, + "ks_target" character varying(255) NOT NULL, + PRIMARY KEY ("ks_name", "ks_target") +); + +-- +-- Table: object_history. +-- +DROP TABLE "object_history" CASCADE; +CREATE TABLE "object_history" ( + "oh_id" serial NOT NULL, + "oh_type" character varying(16) NOT NULL, + "oh_name" character varying(255) NOT NULL, + "oh_action" character varying(16) NOT NULL, + "oh_field" character varying(16), + "oh_type_field" character varying(255), + "oh_old" character varying(255), + "oh_new" character varying(255), + "oh_by" character varying(255) NOT NULL, + "oh_from" character varying(255) NOT NULL, + "oh_on" timestamp NOT NULL, + PRIMARY KEY ("oh_id") +); +CREATE INDEX "object_history_idx_oh_type_oh_name" on "object_history" ("oh_type", "oh_name"); + +-- +-- Table: sync_targets. +-- +DROP TABLE "sync_targets" CASCADE; +CREATE TABLE "sync_targets" ( + "st_name" character varying(255) NOT NULL, + PRIMARY KEY ("st_name") +); + +-- +-- Table: types. +-- +DROP TABLE "types" CASCADE; +CREATE TABLE "types" ( + "ty_name" character varying(16) NOT NULL, + "ty_class" character varying(64), + PRIMARY KEY ("ty_name") +); + +-- +-- Table: acl_entries. +-- +DROP TABLE "acl_entries" CASCADE; +CREATE TABLE "acl_entries" ( + "ae_id" integer NOT NULL, + "ae_scheme" character varying(32) NOT NULL, + "ae_identifier" character varying(255) NOT NULL, + PRIMARY KEY ("ae_id", "ae_scheme", "ae_identifier") +); +CREATE INDEX "acl_entries_idx_ae_scheme" on "acl_entries" ("ae_scheme"); +CREATE INDEX "acl_entries_idx_ae_id" on "acl_entries" ("ae_id"); + +-- +-- Table: objects. +-- +DROP TABLE "objects" CASCADE; +CREATE TABLE "objects" ( + "ob_type" character varying(16) NOT NULL, + "ob_name" character varying(255) NOT NULL, + "ob_owner" integer, + "ob_acl_get" integer, + "ob_acl_store" integer, + "ob_acl_show" integer, + "ob_acl_destroy" integer, + "ob_acl_flags" integer, + "ob_expires" timestamp, + "ob_created_by" character varying(255) NOT NULL, + "ob_created_from" character varying(255) NOT NULL, + "ob_created_on" timestamp NOT NULL, + "ob_stored_by" character varying(255), + "ob_stored_from" character varying(255), + "ob_stored_on" timestamp, + "ob_downloaded_by" character varying(255), + "ob_downloaded_from" character varying(255), + "ob_downloaded_on" timestamp, + "ob_comment" character varying(255), + PRIMARY KEY ("ob_name", "ob_type") +); +CREATE INDEX "objects_idx_ob_acl_destroy" on "objects" ("ob_acl_destroy"); +CREATE INDEX "objects_idx_ob_acl_flags" on "objects" ("ob_acl_flags"); +CREATE INDEX "objects_idx_ob_acl_get" on "objects" ("ob_acl_get"); +CREATE INDEX "objects_idx_ob_owner" on "objects" ("ob_owner"); +CREATE INDEX "objects_idx_ob_acl_show" on "objects" ("ob_acl_show"); +CREATE INDEX "objects_idx_ob_acl_store" on "objects" ("ob_acl_store"); +CREATE INDEX "objects_idx_ob_type" on "objects" ("ob_type"); + +-- +-- Table: duo. +-- +DROP TABLE "duo" CASCADE; +CREATE TABLE "duo" ( + "du_name" character varying(255) NOT NULL, + "du_type" character varying(16) NOT NULL, + "du_key" character varying(255) NOT NULL, + PRIMARY KEY ("du_name", "du_type") +); +CREATE INDEX "duo_idx_du_type_du_name" on "duo" ("du_type", "du_name"); + +-- +-- Foreign Key Definitions +-- + +ALTER TABLE "acl_entries" ADD CONSTRAINT "acl_entries_fk_ae_scheme" FOREIGN KEY ("ae_scheme") + REFERENCES "acl_schemes" ("as_name") DEFERRABLE; + +ALTER TABLE "acl_entries" ADD CONSTRAINT "acl_entries_fk_ae_id" FOREIGN KEY ("ae_id") + REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_acl_destroy" FOREIGN KEY ("ob_acl_destroy") + REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_acl_flags" FOREIGN KEY ("ob_acl_flags") + REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_acl_get" FOREIGN KEY ("ob_acl_get") + REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_owner" FOREIGN KEY ("ob_owner") + REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_acl_show" FOREIGN KEY ("ob_acl_show") + REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_acl_store" FOREIGN KEY ("ob_acl_store") + REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_type" FOREIGN KEY ("ob_type") + REFERENCES "types" ("ty_name") DEFERRABLE; + +ALTER TABLE "duo" ADD CONSTRAINT "duo_fk_du_type_du_name" FOREIGN KEY ("du_type", "du_name") + REFERENCES "objects" ("ob_type", "ob_name") DEFERRABLE; + diff --git a/perl/sql/Wallet-Schema-0.10-SQLite.sql b/perl/sql/Wallet-Schema-0.10-SQLite.sql new file mode 100644 index 0000000..b3450d1 --- /dev/null +++ b/perl/sql/Wallet-Schema-0.10-SQLite.sql @@ -0,0 +1,220 @@ +-- +-- Created by SQL::Translator::Producer::SQLite +-- Created on Thu Oct 9 20:51:25 2014 +-- + +BEGIN TRANSACTION; + +-- +-- Table: acl_history +-- +DROP TABLE acl_history; + +CREATE TABLE acl_history ( + ah_id INTEGER PRIMARY KEY NOT NULL, + ah_acl integer NOT NULL, + ah_name varchar(255), + ah_action varchar(16) NOT NULL, + ah_scheme varchar(32), + ah_identifier varchar(255), + ah_by varchar(255) NOT NULL, + ah_from varchar(255) NOT NULL, + ah_on datetime NOT NULL +); + +CREATE INDEX acl_history_idx_ah_acl ON acl_history (ah_acl); + +CREATE INDEX acl_history_idx_ah_name ON acl_history (ah_name); + +-- +-- Table: acl_schemes +-- +DROP TABLE acl_schemes; + +CREATE TABLE acl_schemes ( + as_name varchar(32) NOT NULL, + as_class varchar(64), + PRIMARY KEY (as_name) +); + +-- +-- Table: acls +-- +DROP TABLE acls; + +CREATE TABLE acls ( + ac_id INTEGER PRIMARY KEY NOT NULL, + ac_name varchar(255) NOT NULL +); + +CREATE UNIQUE INDEX ac_name ON acls (ac_name); + +-- +-- Table: enctypes +-- +DROP TABLE enctypes; + +CREATE TABLE enctypes ( + en_name varchar(255) NOT NULL, + PRIMARY KEY (en_name) +); + +-- +-- Table: flags +-- +DROP TABLE flags; + +CREATE TABLE flags ( + fl_type varchar(16) NOT NULL, + fl_name varchar(255) NOT NULL, + fl_flag enum NOT NULL, + PRIMARY KEY (fl_type, fl_name, fl_flag) +); + +-- +-- Table: keytab_enctypes +-- +DROP TABLE keytab_enctypes; + +CREATE TABLE keytab_enctypes ( + ke_name varchar(255) NOT NULL, + ke_enctype varchar(255) NOT NULL, + PRIMARY KEY (ke_name, ke_enctype) +); + +-- +-- Table: keytab_sync +-- +DROP TABLE keytab_sync; + +CREATE TABLE keytab_sync ( + ks_name varchar(255) NOT NULL, + ks_target varchar(255) NOT NULL, + PRIMARY KEY (ks_name, ks_target) +); + +-- +-- Table: object_history +-- +DROP TABLE object_history; + +CREATE TABLE object_history ( + oh_id INTEGER PRIMARY KEY NOT NULL, + oh_type varchar(16) NOT NULL, + oh_name varchar(255) NOT NULL, + oh_action varchar(16) NOT NULL, + oh_field varchar(16), + oh_type_field varchar(255), + oh_old varchar(255), + oh_new varchar(255), + oh_by varchar(255) NOT NULL, + oh_from varchar(255) NOT NULL, + oh_on datetime NOT NULL +); + +CREATE INDEX object_history_idx_oh_type_oh_name ON object_history (oh_type, oh_name); + +-- +-- Table: sync_targets +-- +DROP TABLE sync_targets; + +CREATE TABLE sync_targets ( + st_name varchar(255) NOT NULL, + PRIMARY KEY (st_name) +); + +-- +-- Table: types +-- +DROP TABLE types; + +CREATE TABLE types ( + ty_name varchar(16) NOT NULL, + ty_class varchar(64), + PRIMARY KEY (ty_name) +); + +-- +-- Table: acl_entries +-- +DROP TABLE acl_entries; + +CREATE TABLE acl_entries ( + ae_id integer NOT NULL, + ae_scheme varchar(32) NOT NULL, + ae_identifier varchar(255) NOT NULL, + PRIMARY KEY (ae_id, ae_scheme, ae_identifier), + FOREIGN KEY (ae_scheme) REFERENCES acl_schemes(as_name), + FOREIGN KEY (ae_id) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE INDEX acl_entries_idx_ae_scheme ON acl_entries (ae_scheme); + +CREATE INDEX acl_entries_idx_ae_id ON acl_entries (ae_id); + +-- +-- Table: objects +-- +DROP TABLE objects; + +CREATE TABLE objects ( + ob_type varchar(16) NOT NULL, + ob_name varchar(255) NOT NULL, + ob_owner integer, + ob_acl_get integer, + ob_acl_store integer, + ob_acl_show integer, + ob_acl_destroy integer, + ob_acl_flags integer, + ob_expires datetime, + ob_created_by varchar(255) NOT NULL, + ob_created_from varchar(255) NOT NULL, + ob_created_on datetime NOT NULL, + ob_stored_by varchar(255), + ob_stored_from varchar(255), + ob_stored_on datetime, + ob_downloaded_by varchar(255), + ob_downloaded_from varchar(255), + ob_downloaded_on datetime, + ob_comment varchar(255), + PRIMARY KEY (ob_name, ob_type), + FOREIGN KEY (ob_acl_destroy) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY (ob_acl_flags) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY (ob_acl_get) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY (ob_owner) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY (ob_acl_show) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY (ob_acl_store) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY (ob_type) REFERENCES types(ty_name) +); + +CREATE INDEX objects_idx_ob_acl_destroy ON objects (ob_acl_destroy); + +CREATE INDEX objects_idx_ob_acl_flags ON objects (ob_acl_flags); + +CREATE INDEX objects_idx_ob_acl_get ON objects (ob_acl_get); + +CREATE INDEX objects_idx_ob_owner ON objects (ob_owner); + +CREATE INDEX objects_idx_ob_acl_show ON objects (ob_acl_show); + +CREATE INDEX objects_idx_ob_acl_store ON objects (ob_acl_store); + +CREATE INDEX objects_idx_ob_type ON objects (ob_type); + +-- +-- Table: duo +-- +DROP TABLE duo; + +CREATE TABLE duo ( + du_name varchar(255) NOT NULL, + du_type varchar(16) NOT NULL, + du_key varchar(255) NOT NULL, + PRIMARY KEY (du_name, du_type), + FOREIGN KEY (du_type, du_name) REFERENCES objects(ob_type, ob_name) +); + +CREATE INDEX duo_idx_du_type_du_name ON duo (du_type, du_name); + +COMMIT; -- cgit v1.2.3 From 9c639ffd83e2dd9b14503b62740589ee5cca25df Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Mon, 13 Oct 2014 22:18:42 -0700 Subject: Fixed new SQLite schema to include IF EXISTS on drop tables Change-Id: I54edbb543be8bfcf0de355da3cef82c6ac1bf27f --- perl/sql/Wallet-Schema-0.10-SQLite.sql | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/perl/sql/Wallet-Schema-0.10-SQLite.sql b/perl/sql/Wallet-Schema-0.10-SQLite.sql index b3450d1..94a185c 100644 --- a/perl/sql/Wallet-Schema-0.10-SQLite.sql +++ b/perl/sql/Wallet-Schema-0.10-SQLite.sql @@ -1,14 +1,14 @@ --- +-- -- Created by SQL::Translator::Producer::SQLite -- Created on Thu Oct 9 20:51:25 2014 --- +-- BEGIN TRANSACTION; -- -- Table: acl_history -- -DROP TABLE acl_history; +DROP TABLE IF EXISTS acl_history; CREATE TABLE acl_history ( ah_id INTEGER PRIMARY KEY NOT NULL, @@ -29,7 +29,7 @@ CREATE INDEX acl_history_idx_ah_name ON acl_history (ah_name); -- -- Table: acl_schemes -- -DROP TABLE acl_schemes; +DROP TABLE IF EXISTS acl_schemes; CREATE TABLE acl_schemes ( as_name varchar(32) NOT NULL, @@ -40,7 +40,7 @@ CREATE TABLE acl_schemes ( -- -- Table: acls -- -DROP TABLE acls; +DROP TABLE IF EXISTS acls; CREATE TABLE acls ( ac_id INTEGER PRIMARY KEY NOT NULL, @@ -52,7 +52,7 @@ CREATE UNIQUE INDEX ac_name ON acls (ac_name); -- -- Table: enctypes -- -DROP TABLE enctypes; +DROP TABLE IF EXISTS enctypes; CREATE TABLE enctypes ( en_name varchar(255) NOT NULL, @@ -62,7 +62,7 @@ CREATE TABLE enctypes ( -- -- Table: flags -- -DROP TABLE flags; +DROP TABLE IF EXISTS flags; CREATE TABLE flags ( fl_type varchar(16) NOT NULL, @@ -74,7 +74,7 @@ CREATE TABLE flags ( -- -- Table: keytab_enctypes -- -DROP TABLE keytab_enctypes; +DROP TABLE IF EXISTS keytab_enctypes; CREATE TABLE keytab_enctypes ( ke_name varchar(255) NOT NULL, @@ -85,7 +85,7 @@ CREATE TABLE keytab_enctypes ( -- -- Table: keytab_sync -- -DROP TABLE keytab_sync; +DROP TABLE IF EXISTS keytab_sync; CREATE TABLE keytab_sync ( ks_name varchar(255) NOT NULL, @@ -96,7 +96,7 @@ CREATE TABLE keytab_sync ( -- -- Table: object_history -- -DROP TABLE object_history; +DROP TABLE IF EXISTS object_history; CREATE TABLE object_history ( oh_id INTEGER PRIMARY KEY NOT NULL, @@ -117,7 +117,7 @@ CREATE INDEX object_history_idx_oh_type_oh_name ON object_history (oh_type, oh_n -- -- Table: sync_targets -- -DROP TABLE sync_targets; +DROP TABLE IF EXISTS sync_targets; CREATE TABLE sync_targets ( st_name varchar(255) NOT NULL, @@ -127,7 +127,7 @@ CREATE TABLE sync_targets ( -- -- Table: types -- -DROP TABLE types; +DROP TABLE IF EXISTS types; CREATE TABLE types ( ty_name varchar(16) NOT NULL, @@ -138,7 +138,7 @@ CREATE TABLE types ( -- -- Table: acl_entries -- -DROP TABLE acl_entries; +DROP TABLE IF EXISTS acl_entries; CREATE TABLE acl_entries ( ae_id integer NOT NULL, @@ -156,7 +156,7 @@ CREATE INDEX acl_entries_idx_ae_id ON acl_entries (ae_id); -- -- Table: objects -- -DROP TABLE objects; +DROP TABLE IF EXISTS objects; CREATE TABLE objects ( ob_type varchar(16) NOT NULL, @@ -205,7 +205,7 @@ CREATE INDEX objects_idx_ob_type ON objects (ob_type); -- -- Table: duo -- -DROP TABLE duo; +DROP TABLE IF EXISTS duo; CREATE TABLE duo ( du_name varchar(255) NOT NULL, -- cgit v1.2.3 From 0e39650c6a831987057a8a9ed615dc3023fcab4a Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Mon, 13 Oct 2014 22:28:30 -0700 Subject: Added Duo RDP object to the wallet Change-Id: Ic728297fa830ffdd40c1580e32a81f8c5123f66a --- perl/lib/Wallet/Admin.pm | 3 +- perl/lib/Wallet/Object/Duo/RDP.pm | 204 +++++++++++++++++++++++++++++++++++ perl/t/data/duo/integration-rdp.json | 11 ++ perl/t/object/duo-rdp.t | 158 +++++++++++++++++++++++++++ 4 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 perl/lib/Wallet/Object/Duo/RDP.pm create mode 100644 perl/t/data/duo/integration-rdp.json create mode 100644 perl/t/object/duo-rdp.t diff --git a/perl/lib/Wallet/Admin.pm b/perl/lib/Wallet/Admin.pm index 381ffc9..8120e9c 100644 --- a/perl/lib/Wallet/Admin.pm +++ b/perl/lib/Wallet/Admin.pm @@ -126,9 +126,10 @@ sub default_data { # types default rows. my @record = ([ qw/ty_name ty_class/ ], [ 'duo', 'Wallet::Object::Duo' ], + [ 'duo-ldap', 'Wallet::Object::Duo::LDAPProxy' ], [ 'duo-pam', 'Wallet::Object::Duo::PAM' ], [ 'duo-radius', 'Wallet::Object::Duo::RadiusProxy' ], - [ 'duo-ldap', 'Wallet::Object::Duo::LDAPProxy' ], + [ 'duo-rdp', 'Wallet::Object::Duo::RDP' ], [ 'file', 'Wallet::Object::File' ], [ 'keytab', 'Wallet::Object::Keytab' ], [ 'wa-keyring', 'Wallet::Object::WAKeyring' ]); diff --git a/perl/lib/Wallet/Object/Duo/RDP.pm b/perl/lib/Wallet/Object/Duo/RDP.pm new file mode 100644 index 0000000..4e7ff04 --- /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 +# Jon Robertson +# 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 + +=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 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 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: + Secret key: + Host: + +The C 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. + +=head1 AUTHORS + +Russ Allbery +Jon Robertson + +=cut diff --git a/perl/t/data/duo/integration-rdp.json b/perl/t/data/duo/integration-rdp.json new file mode 100644 index 0000000..28e925f --- /dev/null +++ b/perl/t/data/duo/integration-rdp.json @@ -0,0 +1,11 @@ +{ + "enroll_policy": "enroll", + "greeting": "", + "groups_allowed": [], + "integration_key": "DIRWIH0ZZPV4G88B37VQ", + "name": "Integration for Radius proxy", + "notes": "", + "secret_key": "QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o", + "type": "rdp", + "visual_style": "default" +} diff --git a/perl/t/object/duo-rdp.t b/perl/t/object/duo-rdp.t new file mode 100644 index 0000000..9b2d566 --- /dev/null +++ b/perl/t/object/duo-rdp.t @@ -0,0 +1,158 @@ +#!/usr/bin/perl +# +# Tests for the Duo RDP integration object implementation. +# +# Written by Russ Allbery +# Copyright 2014 +# The Board of Trustees of the Leland Stanford Junior University +# +# See LICENSE for licensing terms. + +use strict; +use warnings; + +use POSIX qw(strftime); +use Test::More; + +BEGIN { + eval 'use Net::Duo'; + plan skip_all => 'Net::Duo required for testing duo' + if $@; + eval 'use Net::Duo::Mock::Agent'; + plan skip_all => 'Net::Duo::Mock::Agent required for testing duo' + if $@; +} + +BEGIN { + use_ok('Wallet::Admin'); + use_ok('Wallet::Config'); + use_ok('Wallet::Object::Duo::RDP'); +} + +use lib 't/lib'; +use Util; + +# Some global defaults to use. +my $user = 'admin@EXAMPLE.COM'; +my $host = 'localhost'; +my @trace = ($user, $host, time); +my $date = strftime ('%Y-%m-%d %H:%M:%S', localtime $trace[2]); + +# Flush all output immediately. +$| = 1; + +# Use Wallet::Admin to set up the database. +db_setup; +my $admin = eval { Wallet::Admin->new }; +is ($@, '', 'Database connection succeeded'); +is ($admin->reinitialize ($user), 1, 'Database initialization succeeded'); +my $schema = $admin->schema; + +# Create a mock object to use for Duo calls. +my $mock = Net::Duo::Mock::Agent->new ({ key_file => 't/data/duo/keys.json' }); + +# Test error handling in the absence of configuration. +my $object = eval { + Wallet::Object::Duo::RDP->new ('duo-rdp', 'test', $schema); +}; +is ($object, undef, 'Wallet::Object::Duo::RDP new with no config failed'); +is ($@, "duo object implementation not configured\n", '...with correct error'); +$object = eval { + Wallet::Object::Duo::RDP->create ('duo-rdp', 'test', $schema, @trace); +}; +is ($object, undef, 'Wallet::Object::Duo::RDP creation with no config failed'); +is ($@, "duo object implementation not configured\n", '...with correct error'); + +# Set up the Duo configuration. +$Wallet::Config::DUO_AGENT = $mock; +$Wallet::Config::DUO_KEY_FILE = 't/data/duo/keys.json'; + +# Test creating an integration. +note ('Test creating an integration'); +my $expected = { + name => 'test (rdp)', + notes => 'Managed by wallet', + type => 'rdp', +}; +$mock->expect ( + { + method => 'POST', + uri => '/admin/v1/integrations', + content => $expected, + response_file => 't/data/duo/integration-rdp.json', + } +); +$object = Wallet::Object::Duo::RDP->create ('duo-rdp', 'test', $schema, + @trace); +isa_ok ($object, 'Wallet::Object::Duo::RDP'); + +# Check the metadata about the new wallet object. +$expected = <<"EOO"; + Type: duo-rdp + Name: test + Duo key: DIRWIH0ZZPV4G88B37VQ + Created by: $user + Created from: $host + Created on: $date +EOO +is ($object->show, $expected, 'Show output is correct'); + +# Test retrieving the integration information. +note ('Test retrieving an integration'); +$mock->expect ( + { + method => 'GET', + uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ', + response_file => 't/data/duo/integration-rdp.json', + } +); +my $data = $object->get (@trace); +ok (defined ($data), 'Retrieval succeeds'); +$expected = <<'EOO'; +Integration key: DIRWIH0ZZPV4G88B37VQ +Secret key: QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o +Host: example-admin.duosecurity.com +EOO +is ($data, $expected, '...and integration data is correct'); + +# Ensure that we can't retrieve the object when locked. +is ($object->flag_set ('locked', @trace), 1, + 'Setting object to locked succeeds'); +is ($object->get, undef, '...and now get fails'); +is ($object->error, 'cannot get duo-rdp:test: object is locked', + '...with correct error'); +is ($object->flag_clear ('locked', @trace), 1, + '...and clearing locked flag works'); + +# Create a new object by wallet type and name. +$object = Wallet::Object::Duo::RDP->new ('duo-rdp', 'test', $schema); + +# Test deleting an integration. We can't test this entirely properly because +# currently Net::Duo::Mock::Agent doesn't support stacking multiple expected +# calls and delete makes two calls. +note ('Test deleting an integration'); +$mock->expect ( + { + method => 'GET', + uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ', + response_file => 't/data/duo/integration-rdp.json', + } +); +TODO: { + local $TODO = 'Net::Duo::Mock::Agent not yet capable'; + + is ($object->destroy (@trace), 1, 'Duo object deletion succeeded'); + $object = eval { Wallet::Object::Duo::RDP->new ('duo-rdp', 'test', + $schema) }; + is ($object, undef, '...and now object cannot be retrieved'); + is ($@, "cannot find duo:test\n", '...with correct error'); +} + +# Clean up. +$admin->destroy; +END { + unlink ('wallet-db'); +} + +# Done testing. +done_testing (); -- cgit v1.2.3 From 44df8ab537e13c4bb41d0d1c85d4a42891c6ea12 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Mon, 13 Oct 2014 22:35:23 -0700 Subject: Wallet::Object::Duo: Respect new type field in Duo table Make all the searches and creations for the Duo table add or search for the type field as well. This avoids one Duo type clobbering another for the same object name. Change-Id: I62192c3616f43c7acd8ce3f94db8a0e43e77e317 --- perl/lib/Wallet/Object/Duo.pm | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/perl/lib/Wallet/Object/Duo.pm b/perl/lib/Wallet/Object/Duo.pm index 168ec8b..d08294b 100644 --- a/perl/lib/Wallet/Object/Duo.pm +++ b/perl/lib/Wallet/Object/Duo.pm @@ -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'); }; @@ -122,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); @@ -148,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); @@ -179,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'); }; -- cgit v1.2.3 From b658b799cb10b48d1a5aca19a7e63fe91d2af77a Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 14 Oct 2014 13:23:40 -0700 Subject: Added Duo to the Stanford policy module Duo object types currently all assume that the name of the object is the hostname of the server it's for. Change-Id: Ieb5ba144cd39d6aeb3a20466c75a2836a170744f --- perl/lib/Wallet/Policy/Stanford.pm | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/perl/lib/Wallet/Policy/Stanford.pm b/perl/lib/Wallet/Policy/Stanford.pm index 5ac29e0..07d32a4 100644 --- a/perl/lib/Wallet/Policy/Stanford.pm +++ b/perl/lib/Wallet/Policy/Stanford.pm @@ -174,6 +174,13 @@ sub _host_for_keytab { return $host; } +# Map a duo-type object name to a hostname. Currently all Duo objects are +# named just for the hostname, so this is easy. +sub _host_for_file { + my ($name) = @_; + return $name; +} + # The default owner of host-based objects should be the host keytab and the # NetDB ACL for that host, with one twist. If the creator of a new node is # using a root instance, we want to require everyone managing that node be @@ -183,8 +190,13 @@ sub default_owner { # How to determine the host for host-based objects. my %host_for = ( - keytab => \&_host_for_keytab, - file => \&_host_for_file, + 'keytab' => \&_host_for_keytab, + 'file' => \&_host_for_file, + 'duo' => \&_host_for_duo, + 'duo-pam' => \&_host_for_duo, + 'duo-radius' => \&_host_for_duo, + 'duo-ldap' => \&_host_for_duo, + 'duo-rdp' => \&_host_for_duo, ); # If we have a possible host mapping, see if we can use that. @@ -368,6 +380,14 @@ sub verify_name { } } + # Check the naming conventions for all Duo object types. The object + # should simply be the host name for now. + if ($type =~ m{^duo(-\w+)?$}) { + if ($name !~ m{ [.] }xms) { + return "host name $name is not fully qualified"; + } + } + # Success. return; } -- cgit v1.2.3 From 1381f2e0d0bb2edfa4b52c9926c57b22379ee248 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 14 Oct 2014 17:06:43 -0700 Subject: Added rename support for file objects File objects now support a rename command, which will rename the object and move the file to the right spot in the file store under its new name. Change-Id: I10ea2b8012586d69f0894905cfba54a738f3e418 --- perl/lib/Wallet/Object/Base.pm | 2 +- perl/lib/Wallet/Object/File.pm | 52 +++++++++++++++++++++++++++++++++++++++++- perl/lib/Wallet/Server.pm | 46 +++++++++++++++++++++++++++++++++++++ perl/t/object/file.t | 10 ++++++-- 4 files changed, 106 insertions(+), 4 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/File.pm b/perl/lib/Wallet/Object/File.pm index 1ff1288..65fe40e 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,55 @@ 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. + $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 +195,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"; } diff --git a/perl/lib/Wallet/Server.pm b/perl/lib/Wallet/Server.pm index 95fd4e6..34075ed 100644 --- a/perl/lib/Wallet/Server.pm +++ b/perl/lib/Wallet/Server.pm @@ -244,6 +244,52 @@ sub autocreate { return 1; } +# Rename an object. We validate that the new name also falls within naming +# constraints, then need to change all references to that. If any updates +# fail, we'll revert the entire commit. +sub rename { + my ($self, $type, $name, $new_name) = @_; + + my $schema = $self->{schema}; + my $user = $self->{user}; + my $host = $self->{host}; + + # Currently we only can rename file objects. + if (type ne 'file') { + $self->error ('rename is only supported for file objects'); + return; + } + + # Validate the new name. + if (defined (&Wallet::Config::verify_name)) { + my $error = Wallet::Config::verify_name ($type, $new_name, $user); + if ($error) { + $self->error ("${type}:${name} rejected: $error"); + return; + } + } + + # Get the object and error if it does not already exist. + my $class = $self->type_mapping ($type); + unless ($class) { + $self->error ("unknown object type $type"); + return; + } + my $object = eval { $class->new ($type, $name, $schema) }; + if ($@) { + $self->error ($@); + return; + } + + # Rename the object. + $object = eval { $class->rename ($type, $name, $schema, $user, $host) }; + if ($@) { + $self->error ($@); + return; + } + return $object; +} + # Given the name and type of an object, returns a Perl object representing it # or returns undef and sets the internal error. sub retrieve { diff --git a/perl/t/object/file.t b/perl/t/object/file.t index 201f46d..b7f295a 100755 --- a/perl/t/object/file.t +++ b/perl/t/object/file.t @@ -12,7 +12,7 @@ use strict; use warnings; use POSIX qw(strftime); -use Test::More tests => 56; +use Test::More tests => 60; use Wallet::Admin; use Wallet::Config; @@ -101,9 +101,15 @@ is ($object->error, 'data exceeds maximum of 1024 bytes', is ($object->store ('', @trace), 1, 'Storing the empty object works'); is ($object->get (@trace), '', ' and get returns the right thing'); +# Test renaming a file object. +is ($object->rename ('test-rename', @trace), 1, 'Renaming the object works'); +is ($object->{name}, 'test-rename', ' and the object is renamed'); +ok (-f 'test-files/2b/test-rename', ' and the file is in the new location'); +ok (! -f 'test-files/09/test', ' and nothing is in the old location'); + # Test destruction. is ($object->destroy (@trace), 1, 'Destroying the object works'); -ok (! -f 'test-files/09/test', ' and the file is gone'); +ok (! -f 'test-files/2b/test-rename', ' and the file is gone'); # Now try some aggressive names. $object = eval { -- cgit v1.2.3 From 80004a08e0580c25965ae37f602db1970157ddfb Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 14 Oct 2014 17:06:43 -0700 Subject: Added rename support for file objects File objects now support a rename command, which will rename the object and move the file to the right spot in the file store under its new name. Change-Id: I10ea2b8012586d69f0894905cfba54a738f3e418 --- server/wallet-backend | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/wallet-backend b/server/wallet-backend index a2e6e6f..8dfc952 100755 --- a/server/wallet-backend +++ b/server/wallet-backend @@ -287,6 +287,9 @@ sub command { failure ($server->error, @_); } } + } elsif ($command eq 'rename') { + check_args (3, 3, [], @args); + $server->rename (@args) or failure ($server->error, @_); } elsif ($command eq 'setacl') { check_args (4, 4, [], @args); $server->acl (@args) or failure ($server->error, @_); @@ -552,6 +555,12 @@ If is given, sets the owner of the object identified by and to . If is the empty string, clears the owner of the object. +=item rename + +Renames an existing object. This currently only supports file objects, +where it renames the object itself, then the name and location of the +object in the file store. + =item setacl Sets the ACL , which must be one of C, C, C, -- cgit v1.2.3 From f0d6302bbc5fd477236e3c5ea2193a17ef405bba Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 14 Oct 2014 17:06:43 -0700 Subject: Added rename support for file objects File objects now support a rename command, which will rename the object and move the file to the right spot in the file store under its new name. Change-Id: I10ea2b8012586d69f0894905cfba54a738f3e418 --- perl/lib/Wallet/Object/File.pm | 10 +++++++--- perl/lib/Wallet/Server.pm | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/perl/lib/Wallet/Object/File.pm b/perl/lib/Wallet/Object/File.pm index 65fe40e..226e32c 100644 --- a/perl/lib/Wallet/Object/File.pm +++ b/perl/lib/Wallet/Object/File.pm @@ -81,9 +81,13 @@ sub rename { $object->ob_name ($new_name); # Update the file to the path for the new name, and die if we can't. - $self->{name} = $new_name; - my $new_path = $self->file_path; - move($old_path, $new_path) or die $!; + # 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; diff --git a/perl/lib/Wallet/Server.pm b/perl/lib/Wallet/Server.pm index 34075ed..f6ea342 100644 --- a/perl/lib/Wallet/Server.pm +++ b/perl/lib/Wallet/Server.pm @@ -255,7 +255,7 @@ sub rename { my $host = $self->{host}; # Currently we only can rename file objects. - if (type ne 'file') { + if ($type ne 'file') { $self->error ('rename is only supported for file objects'); return; } @@ -282,7 +282,7 @@ sub rename { } # Rename the object. - $object = eval { $class->rename ($type, $name, $schema, $user, $host) }; + eval { $object->rename ($new_name, $schema, $user, $host) }; if ($@) { $self->error ($@); return; -- cgit v1.2.3 From d3c1f060b491f9fba5d8765e221dc931d2639ccb Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Mon, 27 Oct 2014 20:13:09 -0700 Subject: Updated NEWS with pending release notes Change-Id: I7730b4779180d7ad85dd4d1b6e71d8576a27a662 --- NEWS | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 2d1a1e2..2ad0864 100644 --- a/NEWS +++ b/NEWS @@ -1,13 +1,19 @@ User-Visible wallet Changes -wallet 1.11 (2014-10-08) +wallet 1.2 (2014-10-08) The duo object type has been split into several sub-types, each for a specific type of Duo integration. The old type's functionality has been moved to duo-pam (Wallet::Object::Duo::PAM), and new types are - supported for Duo's auth proxy configurations for LDAP and Radius. - These types are duo-radius and duo-ldap - (Wallet::Object::Duo::RadiusProxy and Wallet::Object::Duo::LDAPProxy). + supported for Duo's auth proxy configurations for LDAP and Radius, + and their RDP configuration. These types are duo-radius, duo-ldap, + and duo-rdp (Wallet::Object::Duo::RadiusProxy, + Wallet::Object::Duo::LDAPProxy, and Wallet::Object::Duo::RDP). The + old duo type still exists for compatability. + + A rename command has been added for file type objects. This will + change the name of the object itself, and move any stored data for the + file to the correct location for the new name. wallet 1.1 (2014-07-16) -- cgit v1.2.3 From 49f5b0d38e96d13b6cfb329b21599f9d6ff853d6 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Thu, 6 Nov 2014 15:37:23 -0800 Subject: Stanford.pm: Fixed incorrect name for new duo host parsing function Change-Id: Ica75f6614476088a9952cd7d97749d27811aed7e --- perl/lib/Wallet/Policy/Stanford.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perl/lib/Wallet/Policy/Stanford.pm b/perl/lib/Wallet/Policy/Stanford.pm index 07d32a4..a392476 100644 --- a/perl/lib/Wallet/Policy/Stanford.pm +++ b/perl/lib/Wallet/Policy/Stanford.pm @@ -176,7 +176,7 @@ sub _host_for_keytab { # Map a duo-type object name to a hostname. Currently all Duo objects are # named just for the hostname, so this is easy. -sub _host_for_file { +sub _host_for_duo { my ($name) = @_; return $name; } -- cgit v1.2.3