]>
git.proxmox.com Git - pve-access-control.git/blob - src/PVE/Auth/Plugin.pm
1 package PVE
::Auth
::Plugin
;
9 use PVE
::Cluster
qw(cfs_register_file cfs_read_file cfs_lock_file);
10 use PVE
::JSONSchema
qw(get_standard_option);
11 use PVE
::SectionConfig
;
14 use base
qw(PVE::SectionConfig);
16 my $domainconfigfile = "domains.cfg";
18 cfs_register_file
($domainconfigfile,
19 sub { __PACKAGE__-
>parse_config(@_); },
20 sub { __PACKAGE__-
>write_config(@_); });
22 sub lock_domain_config
{
23 my ($code, $errmsg) = @_;
25 cfs_lock_file
($domainconfigfile, undef, $code);
28 $errmsg ?
die "$errmsg: $err" : die $err;
32 our $realm_regex = qr/[A-Za-z][A-Za-z0-9\.\-_]+/;
33 our $user_regex = qr![^\s:/]+!;
35 PVE
::JSONSchema
::register_format
('pve-realm', \
&pve_verify_realm
);
36 sub pve_verify_realm
{
37 my ($realm, $noerr) = @_;
39 if ($realm !~ m/^${realm_regex}$/) {
40 return undef if $noerr;
41 die "value does not look like a valid realm\n";
46 PVE
::JSONSchema
::register_standard_option
('realm', {
47 description
=> "Authentication domain ID",
48 type
=> 'string', format
=> 'pve-realm',
52 my $remove_options = "(?:acl|properties|entry)";
54 PVE
::JSONSchema
::register_standard_option
('sync-scope', {
55 description
=> "Select what to sync.",
57 enum
=> [qw(users groups both)],
61 PVE
::JSONSchema
::register_standard_option
('sync-remove-vanished', {
62 description
=> "A semicolon-seperated list of things to remove when they or the user"
63 ." vanishes during a sync. The following values are possible: 'entry' removes the"
64 ." user/group when not returned from the sync. 'properties' removes the set"
65 ." properties on existing user/group that do not appear in the source (even custom ones)."
66 ." 'acl' removes acls when the user/group is not returned from the sync."
67 ." Instead of a list it also can be 'none' (the default).",
70 typetext
=> "([acl];[properties];[entry])|none",
71 pattern
=> "(?:(?:$remove_options\;)*$remove_options)|none",
75 my $realm_sync_options_desc = {
76 scope
=> get_standard_option
('sync-scope'),
77 'remove-vanished' => get_standard_option
('sync-remove-vanished'),
78 # TODO check/rewrite in pve7to8, and remove with 8.0
80 description
=> "DEPRECATED: use 'remove-vanished' instead. If set, uses the LDAP Directory as source of truth,"
81 ." deleting users or groups not returned from the sync and removing"
82 ." all locally modified properties of synced users. If not set,"
83 ." only syncs information which is present in the synced data, and does not"
84 ." delete or modify anything else.",
89 description
=> "Enable newly synced users immediately.",
95 description
=> "DEPRECATED: use 'remove-vanished' instead. Remove ACLs for users or"
96 ." groups which were removed from the config during a sync.",
101 PVE
::JSONSchema
::register_standard_option
('realm-sync-options', $realm_sync_options_desc);
102 PVE
::JSONSchema
::register_format
('realm-sync-options', $realm_sync_options_desc);
104 PVE
::JSONSchema
::register_format
('pve-userid', \
&verify_username
);
105 sub verify_username
{
106 my ($username, $noerr) = @_;
108 $username = '' if !$username;
109 my $len = length($username);
111 die "user name '$username' is too short\n" if !$noerr;
115 die "user name '$username' is too long ($len > 64)\n" if !$noerr;
119 # we only allow a limited set of characters
120 # colon is not allowed, because we store usernames in
121 # colon separated lists)!
122 # slash is not allowed because it is used as pve API delimiter
123 # also see "man useradd"
124 if ($username =~ m!^(${user_regex})\@(${realm_regex})$!) {
125 return wantarray ?
($username, $1, $2) : $username;
128 die "value '$username' does not look like a valid user name\n" if !$noerr;
133 PVE
::JSONSchema
::register_standard_option
('userid', {
134 description
=> "Full User ID, in the `name\@realm` format.",
135 type
=> 'string', format
=> 'pve-userid',
141 description
=> "The type of 2nd factor authentication.",
142 format_description
=> 'TFATYPE',
144 enum
=> [qw(yubico oath)],
147 description
=> "Yubico API ID.",
148 format_description
=> 'ID',
153 description
=> "Yubico API Key.",
154 format_description
=> 'KEY',
159 description
=> "Yubico API URL.",
160 format_description
=> 'URL',
165 description
=> "TOTP digits.",
166 format_description
=> 'COUNT',
168 minimum
=> 6, maximum
=> 8,
173 description
=> "TOTP time period.",
174 format_description
=> 'SECONDS',
182 PVE
::JSONSchema
::register_format
('pve-tfa-config', $tfa_format);
184 PVE
::JSONSchema
::register_standard_option
('tfa', {
185 description
=> "Use Two-factor authentication.",
186 type
=> 'string', format
=> 'pve-tfa-config',
191 sub parse_tfa_config
{
194 return PVE
::JSONSchema
::parse_property_string
($tfa_format, $data);
199 type
=> { description
=> "Realm type." },
200 realm
=> get_standard_option
('realm'),
208 sub parse_section_header
{
209 my ($class, $line) = @_;
211 if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
212 my ($type, $realm) = (lc($1), $2);
213 my $errmsg = undef; # set if you want to skip whole section
214 eval { pve_verify_realm
($realm); };
216 my $config = {}; # to return additional attributes
217 return ($type, $realm, $errmsg, $config);
223 my ($class, $filename, $raw) = @_;
225 my $cfg = $class->SUPER::parse_config
($filename, $raw);
228 foreach my $realm (keys %{$cfg->{ids
}}) {
229 my $data = $cfg->{ids
}->{$realm};
230 # make sure there is only one default marker
231 if ($data->{default}) {
233 delete $data->{default};
239 if ($data->{comment
}) {
240 $data->{comment
} = PVE
::Tools
::decode_text
($data->{comment
});
245 # add default domains
247 $cfg->{ids
}->{pve
}->{type
} = 'pve'; # force type
248 $cfg->{ids
}->{pve
}->{comment
} = "Proxmox VE authentication server"
249 if !$cfg->{ids
}->{pve
}->{comment
};
251 $cfg->{ids
}->{pam
}->{type
} = 'pam'; # force type
252 $cfg->{ids
}->{pam
}->{plugin
} = 'PVE::Auth::PAM';
253 $cfg->{ids
}->{pam
}->{comment
} = "Linux PAM standard authentication"
254 if !$cfg->{ids
}->{pam
}->{comment
};
260 my ($class, $filename, $cfg) = @_;
262 foreach my $realm (keys %{$cfg->{ids
}}) {
263 my $data = $cfg->{ids
}->{$realm};
264 if ($data->{comment
}) {
265 $data->{comment
} = PVE
::Tools
::encode_text
($data->{comment
});
269 $class->SUPER::write_config
($filename, $cfg);
272 sub authenticate_user
{
273 my ($class, $config, $realm, $username, $password) = @_;
279 my ($class, $config, $realm, $username, $password) = @_;
281 my $type = $class->type();
283 die "can't set password on auth type '$type'\n";
287 my ($class, $config, $realm, $username) = @_;
289 # do nothing by default
292 # called during addition of realm (before the new domain config got written)
293 # `password` is moved to %param to avoid writing it out to the config
294 # die to abort addition if there are (grave) problems
295 # NOTE: runs in a domain config *locked* context
297 my ($class, $realm, $config, %param) = @_;
298 # do nothing by default
301 # called during domain configuration update (before the updated domain config got
302 # written). `password` is moved to %param to avoid writing it out to the config
303 # die to abort the update if there are (grave) problems
304 # NOTE: runs in a domain config *locked* context
306 my ($class, $realm, $config, %param) = @_;
307 # do nothing by default
310 # called during deletion of realms (before the new domain config got written)
311 # and if the activate check on addition fails, to cleanup all storage traces
312 # which on_add_hook may have created.
313 # die to abort deletion if there are (very grave) problems
314 # NOTE: runs in a domain config *locked* context
316 my ($class, $realm, $config) = @_;
317 # do nothing by default
320 # called during addition and updates of realms (before the new domain config gets written)
321 # die to abort addition/update in case the connection/bind fails
322 # NOTE: runs in a domain config *locked* context
323 sub check_connection
{
324 my ($class, $realm, $config, %param) = @_;
325 # do nothing by default