]> git.proxmox.com Git - pve-access-control.git/blob - PVE/Auth/LDAP.pm
Auth/LDAP: add necessary options for syncing
[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 PVE::LDAP;
9 use base qw(PVE::Auth::Plugin);
10
11 sub type {
12 return 'ldap';
13 }
14
15 sub properties {
16 return {
17 base_dn => {
18 description => "LDAP base domain name",
19 type => 'string',
20 pattern => '\w+=[^,]+(,\s*\w+=[^,]+)*',
21 optional => 1,
22 maxLength => 256,
23 },
24 user_attr => {
25 description => "LDAP user attribute name",
26 type => 'string',
27 pattern => '\S{2,}',
28 optional => 1,
29 maxLength => 256,
30 },
31 bind_dn => {
32 description => "LDAP bind domain name",
33 type => 'string',
34 pattern => '\w+=[^,]+(,\s*\w+=[^,]+)*',
35 optional => 1,
36 maxLength => 256,
37 },
38 verify => {
39 description => "Verify the server's SSL certificate",
40 type => 'boolean',
41 optional => 1,
42 default => 0,
43 },
44 capath => {
45 description => "Path to the CA certificate store",
46 type => 'string',
47 optional => 1,
48 default => '/etc/ssl/certs',
49 },
50 cert => {
51 description => "Path to the client certificate",
52 type => 'string',
53 optional => 1,
54 },
55 certkey => {
56 description => "Path to the client certificate key",
57 type => 'string',
58 optional => 1,
59 },
60 filter => {
61 description => "LDAP filter for user sync.",
62 type => 'string',
63 optional => 1,
64 maxLength => 2048,
65 },
66 sync_attributes => {
67 description => "Comma separated list of key=value pairs for specifying"
68 ." which LDAP attributes map to which PVE user field. For example,"
69 ." to map the LDAP attribute 'mail' to PVEs 'email', write "
70 ." 'email=mail'. By default, each PVE user field is represented "
71 ." by an LDAP attribute of the same name.",
72 optional => 1,
73 type => 'string',
74 pattern => '\w+=[^,]+(,\s*\w+=[^,]+)*',
75 },
76 user_classes => {
77 description => "The objectclasses for users.",
78 type => 'string',
79 default => 'inetorgperson, posixaccount, person, user',
80 format => 'ldap-simple-attr-list',
81 optional => 1,
82 },
83 group_dn => {
84 description => "LDAP base domain name for group sync. If not set, the"
85 ." base_dn will be used.",
86 type => 'string',
87 pattern => '\w+=[^,]+(,\s*\w+=[^,]+)*',
88 optional => 1,
89 maxLength => 256,
90 },
91 group_name_attr => {
92 description => "LDAP attribute representing a groups name. If not set"
93 ." or found, the first value of the DN will be used as name.",
94 type => 'string',
95 format => 'ldap-simple-attr',
96 optional => 1,
97 maxLength => 256,
98 },
99 group_filter => {
100 description => "LDAP filter for group sync.",
101 type => 'string',
102 optional => 1,
103 maxLength => 2048,
104 },
105 group_classes => {
106 description => "The objectclasses for groups.",
107 type => 'string',
108 default => 'groupOfNames, group, univentionGroup, ipausergroup',
109 format => 'ldap-simple-attr-list',
110 optional => 1,
111 },
112 };
113 }
114
115 sub options {
116 return {
117 server1 => {},
118 server2 => { optional => 1 },
119 base_dn => {},
120 bind_dn => { optional => 1 },
121 user_attr => {},
122 port => { optional => 1 },
123 secure => { optional => 1 },
124 sslversion => { optional => 1 },
125 default => { optional => 1 },
126 comment => { optional => 1 },
127 tfa => { optional => 1 },
128 verify => { optional => 1 },
129 capath => { optional => 1 },
130 cert => { optional => 1 },
131 certkey => { optional => 1 },
132 filter => { optional => 1 },
133 sync_attributes => { optional => 1 },
134 user_classes => { optional => 1 },
135 group_dn => { optional => 1 },
136 group_name_attr => { optional => 1 },
137 group_filter => { optional => 1 },
138 group_classes => { optional => 1 },
139 };
140 }
141
142 sub connect_and_bind {
143 my ($class, $config, $realm) = @_;
144
145 my $servers = [$config->{server1}];
146 push @$servers, $config->{server2} if $config->{server2};
147
148 my $default_port = $config->{secure} ? 636: 389;
149 my $port = $config->{port} // $default_port;
150 my $scheme = $config->{secure} ? 'ldaps' : 'ldap';
151
152 my %ldap_args;
153 if ($config->{verify}) {
154 $ldap_args{verify} = 'require';
155 $ldap_args{clientcert} = $config->{cert} if $config->{cert};
156 $ldap_args{clientkey} = $config->{certkey} if $config->{certkey};
157 if (defined(my $capath = $config->{capath})) {
158 if (-d $capath) {
159 $ldap_args{capath} = $capath;
160 } else {
161 $ldap_args{cafile} = $capath;
162 }
163 }
164 } else {
165 $ldap_args{verify} = 'none';
166 }
167
168 if ($config->{secure}) {
169 $ldap_args{sslversion} = $config->{sslversion} || 'tlsv1_2';
170 }
171
172 my $ldap = PVE::LDAP::ldap_connect($servers, $scheme, $port, \%ldap_args);
173
174 my $bind_dn;
175 my $bind_pass;
176
177 if ($config->{bind_dn}) {
178 $bind_dn = $config->{bind_dn};
179 $bind_pass = PVE::Tools::file_read_firstline("/etc/pve/priv/ldap/${realm}.pw");
180 die "missing password for realm $realm\n" if !defined($bind_pass);
181 }
182
183 PVE::LDAP::ldap_bind($ldap, $bind_dn, $bind_pass);
184
185 if (!$config->{base_dn}) {
186 my $root = $ldap->root_dse(attrs => [ 'defaultNamingContext' ]);
187 $config->{base_dn} = $root->get_value('defaultNamingContext');
188 }
189
190 return $ldap;
191 }
192
193 sub authenticate_user {
194 my ($class, $config, $realm, $username, $password) = @_;
195
196 my $ldap = $class->connect_and_bind($config, $realm);
197
198 my $user_dn = PVE::LDAP::get_user_dn($ldap, $username, $config->{user_attr}, $config->{base_dn});
199 PVE::LDAP::auth_user_dn($ldap, $user_dn, $password);
200
201 $ldap->unbind();
202 return 1;
203 }
204
205 1;