]> git.proxmox.com Git - pve-access-control.git/blob - PVE/Auth/LDAP.pm
ldaps: support TLS 1.3 as SSL version
[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 sslversion => { optional => 1 },
74 default => { optional => 1 },
75 comment => { optional => 1 },
76 tfa => { optional => 1 },
77 verify => { optional => 1 },
78 capath => { optional => 1 },
79 cert => { optional => 1 },
80 certkey => { optional => 1 },
81 };
82 }
83
84 my $authenticate_user_ldap = sub {
85 my ($config, $server, $username, $password, $realm) = @_;
86
87 my $default_port = $config->{secure} ? 636: 389;
88 my $port = $config->{port} ? $config->{port} : $default_port;
89 my $scheme = $config->{secure} ? 'ldaps' : 'ldap';
90 $server = "[$server]" if Net::IP::ip_is_ipv6($server);
91 my $conn_string = "$scheme://${server}:$port";
92
93 my %ldap_args;
94 if ($config->{verify}) {
95 $ldap_args{verify} = 'require';
96 if (defined(my $cert = $config->{cert})) {
97 $ldap_args{clientcert} = $cert;
98 }
99 if (defined(my $key = $config->{certkey})) {
100 $ldap_args{clientkey} = $key;
101 }
102 if (defined(my $capath = $config->{capath})) {
103 if (-d $capath) {
104 $ldap_args{capath} = $capath;
105 } else {
106 $ldap_args{cafile} = $capath;
107 }
108 }
109 } else {
110 $ldap_args{verify} = 'none';
111 }
112
113 if ($config->{secure}) {
114 $ldap_args{sslversion} = $config->{sslversion} || 'tlsv1_2';
115 }
116
117 my $ldap = Net::LDAP->new($conn_string, %ldap_args) || die "$@\n";
118
119 if (my $bind_dn = $config->{bind_dn}) {
120 my $bind_pass = PVE::Tools::file_read_firstline("/etc/pve/priv/ldap/${realm}.pw");
121 die "missing password for realm $realm\n" if !defined($bind_pass);
122 my $res = $ldap->bind($bind_dn, password => $bind_pass);
123 my $code = $res->code();
124 my $err = $res->error;
125 die "failed to authenticate to ldap service: $err\n" if ($code);
126 }
127
128 my $search = $config->{user_attr} . "=" . $username;
129 my $result = $ldap->search( base => "$config->{base_dn}",
130 scope => "sub",
131 filter => "$search",
132 attrs => ['dn']
133 );
134 die "no entries returned\n" if !$result->entries;
135 my @entries = $result->entries;
136 my $res = $ldap->bind($entries[0]->dn, password => $password);
137
138 my $code = $res->code();
139 my $err = $res->error;
140
141 $ldap->unbind();
142
143 die "$err\n" if ($code);
144 };
145
146 sub authenticate_user {
147 my ($class, $config, $realm, $username, $password) = @_;
148
149 eval { &$authenticate_user_ldap($config, $config->{server1}, $username, $password, $realm); };
150 my $err = $@;
151 return 1 if !$err;
152 die $err if !$config->{server2};
153 &$authenticate_user_ldap($config, $config->{server2}, $username, $password, $realm);
154 }
155
156 1;