]> git.proxmox.com Git - pve-access-control.git/blob - PVE/Auth/LDAP.pm
bump version to 5.1-1
[pve-access-control.git] / PVE / Auth / LDAP.pm
1 package PVE::Auth::LDAP;
2
3 use strict;
4 use warnings;
5
6 use PVE::Tools;
7 use PVE::Auth::Plugin;
8 use Net::LDAP;
9 use Net::IP;
10 use base qw(PVE::Auth::Plugin);
11
12 sub type {
13 return 'ldap';
14 }
15
16 sub properties {
17 return {
18 base_dn => {
19 description => "LDAP base domain name",
20 type => 'string',
21 pattern => '\w+=[^,]+(,\s*\w+=[^,]+)*',
22 optional => 1,
23 maxLength => 256,
24 },
25 user_attr => {
26 description => "LDAP user attribute name",
27 type => 'string',
28 pattern => '\S{2,}',
29 optional => 1,
30 maxLength => 256,
31 },
32 bind_dn => {
33 description => "LDAP bind domain name",
34 type => 'string',
35 pattern => '\w+=[^,]+(,\s*\w+=[^,]+)*',
36 optional => 1,
37 maxLength => 256,
38 },
39 verify => {
40 description => "Verify the server's SSL certificate",
41 type => 'boolean',
42 optional => 1,
43 default => 0,
44 },
45 capath => {
46 description => "Path to the CA certificate store",
47 type => 'string',
48 optional => 1,
49 default => '/etc/ssl/certs',
50 },
51 cert => {
52 description => "Path to the client certificate",
53 type => 'string',
54 optional => 1,
55 },
56 certkey => {
57 description => "Path to the client certificate key",
58 type => 'string',
59 optional => 1,
60 },
61 };
62 }
63
64 sub options {
65 return {
66 server1 => {},
67 server2 => { optional => 1 },
68 base_dn => {},
69 bind_dn => { optional => 1 },
70 user_attr => {},
71 port => { optional => 1 },
72 secure => { optional => 1 },
73 default => { optional => 1 },
74 comment => { optional => 1 },
75 tfa => { optional => 1 },
76 verify => { optional => 1 },
77 capath => { optional => 1 },
78 cert => { optional => 1 },
79 certkey => { optional => 1 },
80 };
81 }
82
83 my $authenticate_user_ldap = sub {
84 my ($config, $server, $username, $password, $realm) = @_;
85
86 my $default_port = $config->{secure} ? 636: 389;
87 my $port = $config->{port} ? $config->{port} : $default_port;
88 my $scheme = $config->{secure} ? 'ldaps' : 'ldap';
89 $server = "[$server]" if Net::IP::ip_is_ipv6($server);
90 my $conn_string = "$scheme://${server}:$port";
91
92 my %ldap_args;
93 if ($config->{verify}) {
94 $ldap_args{verify} = 'require';
95 if (defined(my $cert = $config->{cert})) {
96 $ldap_args{clientcert} = $cert;
97 }
98 if (defined(my $key = $config->{certkey})) {
99 $ldap_args{clientkey} = $key;
100 }
101 if (defined(my $capath = $config->{capath})) {
102 if (-d $capath) {
103 $ldap_args{capath} = $capath;
104 } else {
105 $ldap_args{cafile} = $capath;
106 }
107 }
108 } else {
109 $ldap_args{verify} = 'none';
110 }
111
112 my $ldap = Net::LDAP->new($conn_string, %ldap_args) || die "$@\n";
113
114 if (my $bind_dn = $config->{bind_dn}) {
115 my $bind_pass = PVE::Tools::file_read_firstline("/etc/pve/priv/ldap/${realm}.pw");
116 die "missing password for realm $realm\n" if !defined($bind_pass);
117 my $res = $ldap->bind($bind_dn, password => $bind_pass);
118 my $code = $res->code();
119 my $err = $res->error;
120 die "failed to authenticate to ldap service: $err\n" if ($code);
121 }
122
123 my $search = $config->{user_attr} . "=" . $username;
124 my $result = $ldap->search( base => "$config->{base_dn}",
125 scope => "sub",
126 filter => "$search",
127 attrs => ['dn']
128 );
129 die "no entries returned\n" if !$result->entries;
130 my @entries = $result->entries;
131 my $res = $ldap->bind($entries[0]->dn, password => $password);
132
133 my $code = $res->code();
134 my $err = $res->error;
135
136 $ldap->unbind();
137
138 die "$err\n" if ($code);
139 };
140
141 sub authenticate_user {
142 my ($class, $config, $realm, $username, $password) = @_;
143
144 eval { &$authenticate_user_ldap($config, $config->{server1}, $username, $password, $realm); };
145 my $err = $@;
146 return 1 if !$err;
147 die $err if !$config->{server2};
148 &$authenticate_user_ldap($config, $config->{server2}, $username, $password, $realm);
149 }
150
151 1;