summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Goodman <akgood@duosecurity.com>2012-01-25 11:41:40 -0500
committerAdam Goodman <akgood@duosecurity.com>2012-01-25 11:41:40 -0500
commit9847008070022cf19a2fb5db33e36cc11c8aec45 (patch)
treeb9f995308c8216d7b4a370dde98128715fb06e25
parent01d525f4282227092cfb2cfa5488ae1734301a55 (diff)
downloadopenvpn-duo-plugin-9847008070022cf19a2fb5db33e36cc11c8aec45.tar.gz
openvpn-duo-plugin-9847008070022cf19a2fb5db33e36cc11c8aec45.zip
add perl version of duo_openvpn.py
-rw-r--r--duo_openvpn.pl223
1 files changed, 223 insertions, 0 deletions
diff --git a/duo_openvpn.pl b/duo_openvpn.pl
new file mode 100644
index 0000000..9998fcd
--- /dev/null
+++ b/duo_openvpn.pl
@@ -0,0 +1,223 @@
+#/usr/bin/env perl
+
+use strict;
+use warnings;
+use LWP::UserAgent;
+use Sys::Syslog qw(:standard);
+use URI::Escape;
+use MIME::Base64;
+use JSON::XS;
+use Digest::HMAC_SHA1 qw(hmac_sha1_hex);
+use Data::Dumper;
+$Data::Dumper::Indent = 0;
+$Data::Dumper::Terse = 1;
+
+my $API_RESULT_AUTH = qr/^auth$/;
+my $API_RESULT_ALLOW = qr/^allow$/;
+my $API_RESULT_DENY = qr/^deny$/;
+my $API_RESULT_ENROLL = qr/^enroll$/;
+my $control = $ENV{'control'};
+
+openlog 'duo_openvpn.pl', 'pid', 'LOG_AUTH';
+
+if (not $control) {
+ logger('required control configuration');
+ exit 1;
+}
+
+my $ikey = $ENV{'ikey'};
+my $skey = $ENV{'skey'};
+my $host = $ENV{'host'};
+
+if (not $ikey or not $skey or not $host) {
+ logger('required ikey/skey/host configuration');
+ failure();
+ exit 1;
+}
+
+my $username = $ENV{'username'};
+my $password = $ENV{'password'};
+my $ipaddr = $ENV{'ipaddr'} || '0.0.0.0';
+
+if (not $control or not $username or not $host) {
+ logger('required environmental variables not found');
+ failure();
+}
+
+preauth();
+auth();
+failure();
+
+
+sub canonicalize {
+ my $host = shift;
+ my $uri = shift;
+ my $params = shift;
+
+ my @canon = ('POST', lc $host, $uri);
+ my @args = ();
+
+ foreach my $key (keys %{$params}) {
+ push @args, (uri_escape($key) . '=' . uri_escape($params->{$key}));
+ }
+
+ return join '&', @canon, @args;
+}
+
+
+sub sign {
+ my ($ikey, $skey, $host, $path, $args) = @_;
+
+ my $sig = hmac_sha1_hex(canonicalize($host, $path, $args), $skey);
+ my $auth = "$ikey:$sig";
+ return 'Basic ' . encode_base64($auth);
+}
+
+
+sub call {
+ my ($ikey, $skey, $host, $path, $kwargs) = @_;
+
+ my $ua = LWP::UserAgent->new();
+
+ $ua->default_header(
+ 'Authorization' => sign($ikey, $skey, $host, $path, $kwargs),
+ );
+
+ my $response = $ua->post('https://' . $host . $path, $kwargs);
+ my $data = '{}';
+
+ if ($response->is_success) {
+ $data = $response->content;
+ }
+
+ return ($response->code, $response->message, $data);
+}
+
+
+sub api {
+ my ($ikey, $skey, $host, $path, $args) = @_;
+
+ my ($status, $reason, $json) = call($ikey, $skey, $host, $path, $args);
+
+ if ($status != 200) {
+ logger("Received $status $reason: $json");
+ failure();
+ }
+
+ my $data = decode_json $json;
+
+ if (defined $data->{stat} and $data->{stat} !~ /^OK$/o) {
+ logger("Received error response: $json");
+ failure();
+ }
+
+ if (not defined $data->{response}) {
+ logger("Received bad response: $json");
+ failure();
+ }
+
+ if (not defined $data->{'response'}{'result'}) {
+ logger("invalid API response: $json");
+ failure();
+ }
+
+ if (not defined $data->{'response'}{'status'}) {
+ logger("invalid API response: $json");
+ failure();
+ }
+
+ return $data->{response};
+}
+
+
+sub auth {
+ logger("authentication for $username");
+
+ my $args = {
+ 'user' => $username,
+ 'factor' => 'auto',
+ 'auto' => $password,
+ 'ipaddr' => $ipaddr,
+ };
+
+ my $response = api($ikey, $skey, $host, '/rest/v1/auth', $args);
+
+ my $result = $response->{'result'};
+ my $status = $response->{'status'};
+
+ if ($result =~ $API_RESULT_ALLOW) {
+ logger("auth success for $username: $status");
+ success();
+ }
+ elsif ($result =~ $API_RESULT_DENY) {
+ logger("auth failure for $username: $status");
+ failure();
+ }
+ else {
+ logger("unknown auth result: $result");
+ failure();
+ }
+}
+
+
+sub preauth {
+ logger("pre-authentication for $username");
+
+ my $args = {
+ user => $username,
+ };
+
+ my $response = api($ikey, $skey, $host, '/rest/v1/preauth', $args);
+
+ my $result = $response->{'result'};
+ my $status = $response->{'status'};
+
+ if ($result =~ $API_RESULT_AUTH) {
+ return;
+ }
+
+ if ($result =~ $API_RESULT_ENROLL) {
+ logger("user $username is not enrolled: $status");
+ failure();
+ }
+ elsif ($result =~ $API_RESULT_DENY) {
+ logger("preauth failure for $username: $status");
+ failure();
+ }
+ elsif ($result =~ $API_RESULT_ALLOW) {
+ logger("preauth success for $username: $status");
+ success();
+ }
+ else {
+ logger("unknown preauth result: $result");
+ failure();
+ }
+}
+
+
+sub logger {
+ my $msg = shift;
+ syslog('info', "Duo OpenVPN: $msg");
+}
+
+
+sub success {
+ logger("writing success code to $control");
+
+ open CONTROL, '>', $control or die "Error [$control]: $!";
+ print CONTROL '1';
+ close CONTROL;
+
+ exit 0;
+}
+
+
+sub failure {
+ logger("writing failure code to $control");
+
+ open CONTROL, '>', $control or die "Error [$control]: $!";
+ print CONTROL '0';
+ close CONTROL;
+
+ exit 1;
+}