]>
git.proxmox.com Git - pve-access-control.git/blob - PVE/Auth/LDAP.pm
1 package PVE
::Auth
::LDAP
;
11 use base
qw(PVE::Auth::Plugin);
20 description
=> "LDAP base domain name",
22 pattern
=> '\w+=[^,]+(,\s*\w+=[^,]+)*',
27 description
=> "LDAP user attribute name",
34 description
=> "LDAP bind domain name",
36 pattern
=> '\w+=[^,]+(,\s*\w+=[^,]+)*',
41 description
=> "LDAP bind password. Will be stored in '/etc/pve/priv/realm/<REALM>.pw'.",
46 description
=> "Verify the server's SSL certificate",
52 description
=> "Path to the CA certificate store",
55 default => '/etc/ssl/certs',
58 description
=> "Path to the client certificate",
63 description
=> "Path to the client certificate key",
68 description
=> "LDAP filter for user sync.",
74 description
=> "Comma separated list of key=value pairs for specifying"
75 ." which LDAP attributes map to which PVE user field. For example,"
76 ." to map the LDAP attribute 'mail' to PVEs 'email', write "
77 ." 'email=mail'. By default, each PVE user field is represented "
78 ." by an LDAP attribute of the same name.",
81 pattern
=> '\w+=[^,]+(,\s*\w+=[^,]+)*',
84 description
=> "The objectclasses for users.",
86 default => 'inetorgperson, posixaccount, person, user',
87 format
=> 'ldap-simple-attr-list',
91 description
=> "LDAP base domain name for group sync. If not set, the"
92 ." base_dn will be used.",
94 pattern
=> '\w+=[^,]+(,\s*\w+=[^,]+)*',
99 description
=> "LDAP attribute representing a groups name. If not set"
100 ." or found, the first value of the DN will be used as name.",
102 format
=> 'ldap-simple-attr',
107 description
=> "LDAP filter for group sync.",
113 description
=> "The objectclasses for groups.",
115 default => 'groupOfNames, group, univentionGroup, ipausergroup',
116 format
=> 'ldap-simple-attr-list',
119 'sync-defaults-options' => {
120 description
=> "The default options for behavior of synchronizations.",
122 format
=> 'realm-sync-options',
131 server2
=> { optional
=> 1 },
133 bind_dn
=> { optional
=> 1 },
134 password
=> { optional
=> 1 },
136 port
=> { optional
=> 1 },
137 secure
=> { optional
=> 1 },
138 sslversion
=> { optional
=> 1 },
139 default => { optional
=> 1 },
140 comment
=> { optional
=> 1 },
141 tfa
=> { optional
=> 1 },
142 verify
=> { optional
=> 1 },
143 capath
=> { optional
=> 1 },
144 cert
=> { optional
=> 1 },
145 certkey
=> { optional
=> 1 },
146 filter
=> { optional
=> 1 },
147 sync_attributes
=> { optional
=> 1 },
148 user_classes
=> { optional
=> 1 },
149 group_dn
=> { optional
=> 1 },
150 group_name_attr
=> { optional
=> 1 },
151 group_filter
=> { optional
=> 1 },
152 group_classes
=> { optional
=> 1 },
153 'sync-defaults-options' => { optional
=> 1 },
157 sub connect_and_bind
{
158 my ($class, $config, $realm) = @_;
160 my $servers = [$config->{server1
}];
161 push @$servers, $config->{server2
} if $config->{server2
};
163 my $default_port = $config->{secure
} ?
636: 389;
164 my $port = $config->{port
} // $default_port;
165 my $scheme = $config->{secure
} ?
'ldaps' : 'ldap';
168 if ($config->{verify
}) {
169 $ldap_args{verify
} = 'require';
170 $ldap_args{clientcert
} = $config->{cert
} if $config->{cert
};
171 $ldap_args{clientkey
} = $config->{certkey
} if $config->{certkey
};
172 if (defined(my $capath = $config->{capath
})) {
174 $ldap_args{capath
} = $capath;
176 $ldap_args{cafile
} = $capath;
180 $ldap_args{verify
} = 'none';
183 if ($config->{secure
}) {
184 $ldap_args{sslversion
} = $config->{sslversion
} || 'tlsv1_2';
187 my $ldap = PVE
::LDAP
::ldap_connect
($servers, $scheme, $port, \
%ldap_args);
192 if ($config->{bind_dn
}) {
193 $bind_dn = $config->{bind_dn
};
194 $bind_pass = ldap_get_credentials
($realm);
195 die "missing password for realm $realm\n" if !defined($bind_pass);
198 PVE
::LDAP
::ldap_bind
($ldap, $bind_dn, $bind_pass);
200 if (!$config->{base_dn
}) {
201 my $root = $ldap->root_dse(attrs
=> [ 'defaultNamingContext' ]);
202 $config->{base_dn
} = $root->get_value('defaultNamingContext');
210 # 'username@realm' => {
211 # 'attr1' => 'value1',
212 # 'attr2' => 'value2',
218 # or in list context:
221 # 'username@realm' => {
222 # 'attr1' => 'value1',
223 # 'attr2' => 'value2',
229 # 'uid=username,dc=....' => 'username@realm',
233 # the map of dn->username is needed for group membership sync
235 my ($class, $config, $realm) = @_;
237 my $ldap = $class->connect_and_bind($config, $realm);
239 my $user_name_attr = $config->{user_attr
} // 'uid';
240 my $ldap_attribute_map = {
241 $user_name_attr => 'username',
244 firstname
=> 'firstname',
245 lastname
=> 'lastname',
247 comment
=> 'comment',
251 foreach my $attr (PVE
::Tools
::split_list
($config->{sync_attributes
})) {
252 my ($ours, $ldap) = ($attr =~ m/^\s*(\w+)=(.*)\s*$/);
253 $ldap_attribute_map->{$ldap} = $ours;
256 my $filter = $config->{filter
};
257 my $basedn = $config->{base_dn
};
259 $config->{user_classes
} //= 'inetorgperson, posixaccount, person, user';
260 my $classes = [PVE
::Tools
::split_list
($config->{user_classes
})];
262 my $users = PVE
::LDAP
::query_users
($ldap, $filter, [keys %$ldap_attribute_map], $basedn, $classes);
267 foreach my $user (@$users) {
268 my $user_attributes = $user->{attributes
};
269 my $userid = $user_attributes->{$user_name_attr}->[0];
270 my $username = "$userid\@$realm";
272 # we cannot sync usernames that do not meet our criteria
273 eval { PVE
::Auth
::Plugin
::verify_username
($username) };
279 $ret->{$username} = {};
281 foreach my $attr (keys %$user_attributes) {
282 if (my $ours = $ldap_attribute_map->{$attr}) {
283 $ret->{$username}->{$ours} = $user_attributes->{$attr}->[0];
288 my $dn = $user->{dn
};
289 $dnmap->{$dn} = $username;
293 return wantarray ?
($ret, $dnmap) : $ret;
296 # needs a map for dn -> username, we get this from the get_users call
297 # otherwise we cannot determine the group membership
299 my ($class, $config, $realm, $dnmap) = @_;
301 my $filter = $config->{group_filter
};
302 my $basedn = $config->{group_dn
} // $config->{base_dn
};
303 my $attr = $config->{group_name_attr
};
304 $config->{group_classes
} //= 'groupOfNames, group, univentionGroup, ipausergroup';
305 my $classes = [PVE
::Tools
::split_list
($config->{group_classes
})];
307 my $ldap = $class->connect_and_bind($config, $realm);
309 my $groups = PVE
::LDAP
::query_groups
($ldap, $basedn, $classes, $filter, $attr);
313 foreach my $group (@$groups) {
314 my $name = $group->{name
};
315 if (!$name && $group->{dn
} =~ m/^[^=]+=([^,]+),/){
316 $name = PVE
::Tools
::trim
($1);
321 # we cannot sync groups that do not meet our criteria
322 eval { PVE
::AccessControl
::verify_groupname
($name) };
328 $ret->{$name} = { users
=> {} };
329 foreach my $member (@{$group->{members
}}) {
330 if (my $user = $dnmap->{$member}) {
331 $ret->{$name}->{users
}->{$user} = 1;
340 sub authenticate_user
{
341 my ($class, $config, $realm, $username, $password) = @_;
343 my $ldap = $class->connect_and_bind($config, $realm);
345 my $user_dn = PVE
::LDAP
::get_user_dn
($ldap, $username, $config->{user_attr
}, $config->{base_dn
});
346 PVE
::LDAP
::auth_user_dn
($ldap, $user_dn, $password);
352 my $ldap_pw_dir = "/etc/pve/priv/realm";
354 sub ldap_cred_file_name
{
356 return "${ldap_pw_dir}/${realmid}.pw";
362 my $cred_file = ldap_cred_file_name
($realmid);
365 } elsif (-e
"/etc/pve/priv/ldap/${realmid}.pw") {
366 # FIXME: remove fallback with 7.0 by doing a rename on upgrade from 6.x
367 return "/etc/pve/priv/ldap/${realmid}.pw";
373 sub ldap_set_credentials
{
374 my ($password, $realmid) = @_;
376 my $cred_file = ldap_cred_file_name
($realmid);
379 PVE
::Tools
::file_set_contents
($cred_file, $password);
384 sub ldap_get_credentials
{
387 if (my $cred_file = get_cred_file
($realmid)) {
388 return PVE
::Tools
::file_read_firstline
($cred_file);
393 sub ldap_delete_credentials
{
396 if (my $cred_file = get_cred_file
($realmid)) {
397 unlink($cred_file) or warn "removing LDAP credentials '$cred_file' failed: $!\n";
402 my ($class, $realm, $config, %param) = @_;
404 if (defined($param{password
})) {
405 ldap_set_credentials
($param{password
}, $realm);
407 ldap_delete_credentials
($realm);
412 my ($class, $realm, $config, %param) = @_;
414 return if !exists($param{password
});
416 if (defined($param{password
})) {
417 ldap_set_credentials
($param{password
}, $realm);
419 ldap_delete_credentials
($realm);
424 my ($class, $realm, $config) = @_;
426 ldap_delete_credentials
($realm);