fix #1470: ad: server and client certificate support
[pve-access-control.git] / PVE / Auth / AD.pm
1 package PVE::Auth::AD;
2
3 use strict;
4 use warnings;
5 use PVE::Auth::Plugin;
6 use Net::LDAP;
7 use Net::IP;
8
9 use base qw(PVE::Auth::Plugin);
10
11 sub type {
12     return 'ad';
13 }
14
15 sub properties {
16     return {
17         server1 => { 
18             description => "Server IP address (or DNS name)",           
19             type => 'string',
20             format => 'address',
21             maxLength => 256,
22         },
23         server2 => { 
24             description => "Fallback Server IP address (or DNS name)",
25             type => 'string',
26             optional => 1,
27             format => 'address',
28             maxLength => 256,
29         },
30         secure => { 
31             description => "Use secure LDAPS protocol.",
32             type => 'boolean', 
33             optional => 1,
34
35         },
36         default => { 
37             description => "Use this as default realm",
38             type => 'boolean', 
39             optional => 1,
40         },
41         comment => { 
42             description => "Description.",
43             type => 'string', 
44             optional => 1,
45             maxLength => 4096,
46         },
47         port => {
48             description => "Server port.",
49             type => 'integer',
50             minimum => 1,
51             maximum => 65535,
52             optional => 1,
53         },
54         domain => {
55             description => "AD domain name",
56             type => 'string',
57             pattern => '\S+',
58             optional => 1,
59             maxLength => 256,
60         },
61         tfa => PVE::JSONSchema::get_standard_option('tfa'), 
62     };
63 }
64
65 sub options {
66     return {
67         server1 => {},
68         server2 => { optional => 1 },
69         domain => {},
70         port => { optional => 1 },
71         secure => { optional => 1 },
72         default => { optional => 1 },,
73         comment => { optional => 1 },
74         tfa => { optional => 1 },
75         verify => { optional => 1 },
76         capath => { optional => 1 },
77         cert => { optional => 1 },
78         certkey => { optional => 1 },
79     };
80 }
81
82 my $authenticate_user_ad = sub {
83     my ($config, $server, $username, $password) = @_;
84
85     my $default_port = $config->{secure} ? 636: 389;
86     my $port = $config->{port} ? $config->{port} : $default_port;
87     my $scheme = $config->{secure} ? 'ldaps' : 'ldap';
88     $server = "[$server]" if Net::IP::ip_is_ipv6($server);
89     my $conn_string = "$scheme://${server}:$port";
90
91     my %ad_args;
92     if ($config->{verify}) {
93         $ad_args{verify} = 'require';
94         if (defined(my $cert = $config->{cert})) {
95             $ad_args{clientcert} = $cert;
96         }
97         if (defined(my $key = $config->{certkey})) {
98             $ad_args{clientkey} = $key;
99         }
100         if (defined(my $capath = $config->{capath})) {
101             if (-d $capath) {
102                 $ad_args{capath} = $capath;
103             } else {
104                 $ad_args{cafile} = $capath;
105             }
106         }
107     } elsif (defined($config->{verify})) {
108         $ad_args{verify} = 'none';
109     }
110
111     my $ldap = Net::LDAP->new($conn_string, %ad_args) || die "$@\n";
112
113     $username = "$username\@$config->{domain}"
114         if $username !~ m/@/ && $config->{domain};
115
116     my $res = $ldap->bind($username, password => $password);
117
118     my $code = $res->code();
119     my $err = $res->error;
120
121     $ldap->unbind();
122
123     die "$err\n" if ($code);
124 };
125
126 sub authenticate_user {
127     my ($class, $config, $realm, $username, $password) = @_;
128
129     eval { &$authenticate_user_ad($config, $config->{server1}, $username, $password); };
130     my $err = $@;
131     return 1 if !$err;
132     die $err if !$config->{server2};
133     &$authenticate_user_ad($config, $config->{server2}, $username, $password);
134     return 1;
135 }
136
137 1;