]>
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
=> "Verify the server's SSL certificate",
47 description
=> "Path to the CA certificate store",
50 default => '/etc/ssl/certs',
53 description
=> "Path to the client certificate",
58 description
=> "Path to the client certificate key",
63 description
=> "LDAP filter for user sync.",
69 description
=> "Comma separated list of key=value pairs for specifying"
70 ." which LDAP attributes map to which PVE user field. For example,"
71 ." to map the LDAP attribute 'mail' to PVEs 'email', write "
72 ." 'email=mail'. By default, each PVE user field is represented "
73 ." by an LDAP attribute of the same name.",
76 pattern
=> '\w+=[^,]+(,\s*\w+=[^,]+)*',
79 description
=> "The objectclasses for users.",
81 default => 'inetorgperson, posixaccount, person, user',
82 format
=> 'ldap-simple-attr-list',
86 description
=> "LDAP base domain name for group sync. If not set, the"
87 ." base_dn will be used.",
89 pattern
=> '\w+=[^,]+(,\s*\w+=[^,]+)*',
94 description
=> "LDAP attribute representing a groups name. If not set"
95 ." or found, the first value of the DN will be used as name.",
97 format
=> 'ldap-simple-attr',
102 description
=> "LDAP filter for group sync.",
108 description
=> "The objectclasses for groups.",
110 default => 'groupOfNames, group, univentionGroup, ipausergroup',
111 format
=> 'ldap-simple-attr-list',
114 'sync-defaults-options' => {
115 description
=> "The default options for behavior of synchronizations.",
117 format
=> 'realm-sync-options',
126 server2
=> { optional
=> 1 },
128 bind_dn
=> { optional
=> 1 },
130 port
=> { optional
=> 1 },
131 secure
=> { optional
=> 1 },
132 sslversion
=> { optional
=> 1 },
133 default => { optional
=> 1 },
134 comment
=> { optional
=> 1 },
135 tfa
=> { optional
=> 1 },
136 verify
=> { optional
=> 1 },
137 capath
=> { optional
=> 1 },
138 cert
=> { optional
=> 1 },
139 certkey
=> { optional
=> 1 },
140 filter
=> { optional
=> 1 },
141 sync_attributes
=> { optional
=> 1 },
142 user_classes
=> { optional
=> 1 },
143 group_dn
=> { optional
=> 1 },
144 group_name_attr
=> { optional
=> 1 },
145 group_filter
=> { optional
=> 1 },
146 group_classes
=> { optional
=> 1 },
147 'sync-defaults-options' => { optional
=> 1 },
151 sub connect_and_bind
{
152 my ($class, $config, $realm) = @_;
154 my $servers = [$config->{server1
}];
155 push @$servers, $config->{server2
} if $config->{server2
};
157 my $default_port = $config->{secure
} ?
636: 389;
158 my $port = $config->{port
} // $default_port;
159 my $scheme = $config->{secure
} ?
'ldaps' : 'ldap';
162 if ($config->{verify
}) {
163 $ldap_args{verify
} = 'require';
164 $ldap_args{clientcert
} = $config->{cert
} if $config->{cert
};
165 $ldap_args{clientkey
} = $config->{certkey
} if $config->{certkey
};
166 if (defined(my $capath = $config->{capath
})) {
168 $ldap_args{capath
} = $capath;
170 $ldap_args{cafile
} = $capath;
174 $ldap_args{verify
} = 'none';
177 if ($config->{secure
}) {
178 $ldap_args{sslversion
} = $config->{sslversion
} || 'tlsv1_2';
181 my $ldap = PVE
::LDAP
::ldap_connect
($servers, $scheme, $port, \
%ldap_args);
186 if ($config->{bind_dn
}) {
187 $bind_dn = $config->{bind_dn
};
188 $bind_pass = PVE
::Tools
::file_read_firstline
("/etc/pve/priv/ldap/${realm}.pw");
189 die "missing password for realm $realm\n" if !defined($bind_pass);
192 PVE
::LDAP
::ldap_bind
($ldap, $bind_dn, $bind_pass);
194 if (!$config->{base_dn
}) {
195 my $root = $ldap->root_dse(attrs
=> [ 'defaultNamingContext' ]);
196 $config->{base_dn
} = $root->get_value('defaultNamingContext');
204 # 'username@realm' => {
205 # 'attr1' => 'value1',
206 # 'attr2' => 'value2',
212 # or in list context:
215 # 'username@realm' => {
216 # 'attr1' => 'value1',
217 # 'attr2' => 'value2',
223 # 'uid=username,dc=....' => 'username@realm',
227 # the map of dn->username is needed for group membership sync
229 my ($class, $config, $realm) = @_;
231 my $ldap = $class->connect_and_bind($config, $realm);
233 my $user_name_attr = $config->{user_attr
} // 'uid';
234 my $ldap_attribute_map = {
235 $user_name_attr => 'username',
238 firstname
=> 'firstname',
239 lastname
=> 'lastname',
241 comment
=> 'comment',
245 foreach my $attr (PVE
::Tools
::split_list
($config->{sync_attributes
})) {
246 my ($ours, $ldap) = ($attr =~ m/^\s*(\w+)=(.*)\s*$/);
247 $ldap_attribute_map->{$ldap} = $ours;
250 my $filter = $config->{filter
};
251 my $basedn = $config->{base_dn
};
253 $config->{user_classes
} //= 'inetorgperson, posixaccount, person, user';
254 my $classes = [PVE
::Tools
::split_list
($config->{user_classes
})];
256 my $users = PVE
::LDAP
::query_users
($ldap, $filter, [keys %$ldap_attribute_map], $basedn, $classes);
261 foreach my $user (@$users) {
262 my $user_attributes = $user->{attributes
};
263 my $userid = $user_attributes->{$user_name_attr}->[0];
264 my $username = "$userid\@$realm";
266 # we cannot sync usernames that do not meet our criteria
267 eval { PVE
::Auth
::Plugin
::verify_username
($username) };
273 $ret->{$username} = {};
275 foreach my $attr (keys %$user_attributes) {
276 if (my $ours = $ldap_attribute_map->{$attr}) {
277 $ret->{$username}->{$ours} = $user_attributes->{$attr}->[0];
282 my $dn = $user->{dn
};
283 $dnmap->{$dn} = $username;
287 return wantarray ?
($ret, $dnmap) : $ret;
290 # needs a map for dn -> username, we get this from the get_users call
291 # otherwise we cannot determine the group membership
293 my ($class, $config, $realm, $dnmap) = @_;
295 my $filter = $config->{group_filter
};
296 my $basedn = $config->{group_dn
} // $config->{base_dn
};
297 my $attr = $config->{group_name_attr
};
298 $config->{group_classes
} //= 'groupOfNames, group, univentionGroup, ipausergroup';
299 my $classes = [PVE
::Tools
::split_list
($config->{group_classes
})];
301 my $ldap = $class->connect_and_bind($config, $realm);
303 my $groups = PVE
::LDAP
::query_groups
($ldap, $basedn, $classes, $filter, $attr);
307 foreach my $group (@$groups) {
308 my $name = $group->{name
};
309 if (!$name && $group->{dn
} =~ m/^[^=]+=([^,]+),/){
310 $name = PVE
::Tools
::trim
($1);
315 # we cannot sync groups that do not meet our criteria
316 eval { PVE
::AccessControl
::verify_groupname
($name) };
322 $ret->{$name} = { users
=> {} };
323 foreach my $member (@{$group->{members
}}) {
324 if (my $user = $dnmap->{$member}) {
325 $ret->{$name}->{users
}->{$user} = 1;
334 sub authenticate_user
{
335 my ($class, $config, $realm, $username, $password) = @_;
337 my $ldap = $class->connect_and_bind($config, $realm);
339 my $user_dn = PVE
::LDAP
::get_user_dn
($ldap, $username, $config->{user_attr
}, $config->{base_dn
});
340 PVE
::LDAP
::auth_user_dn
($ldap, $user_dn, $password);