]> git.proxmox.com Git - pve-access-control.git/blame - src/PVE/Auth/Plugin.pm
bump version to 8.1.4
[pve-access-control.git] / src / PVE / Auth / Plugin.pm
CommitLineData
5bb4e06a
DM
1package PVE::Auth::Plugin;
2
3use strict;
4use warnings;
b49abe2d 5
5bb4e06a 6use Digest::SHA;
b49abe2d
TL
7use Encode;
8
5bb4e06a 9use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_lock_file);
b49abe2d
TL
10use PVE::JSONSchema qw(get_standard_option);
11use PVE::SectionConfig;
12use PVE::Tools;
5bb4e06a 13
5bb4e06a
DM
14use base qw(PVE::SectionConfig);
15
16my $domainconfigfile = "domains.cfg";
17
0a6e09fd 18cfs_register_file($domainconfigfile,
5bb4e06a
DM
19 sub { __PACKAGE__->parse_config(@_); },
20 sub { __PACKAGE__->write_config(@_); });
21
22sub lock_domain_config {
23 my ($code, $errmsg) = @_;
24
25 cfs_lock_file($domainconfigfile, undef, $code);
26 my $err = $@;
27 if ($err) {
28 $errmsg ? die "$errmsg: $err" : die $err;
29 }
30}
31
8e23f971
FG
32our $realm_regex = qr/[A-Za-z][A-Za-z0-9\.\-_]+/;
33our $user_regex = qr![^\s:/]+!;
5bb4e06a
DM
34
35PVE::JSONSchema::register_format('pve-realm', \&pve_verify_realm);
36sub pve_verify_realm {
37 my ($realm, $noerr) = @_;
0a6e09fd 38
5bb4e06a
DM
39 if ($realm !~ m/^${realm_regex}$/) {
40 return undef if $noerr;
0a6e09fd 41 die "value does not look like a valid realm\n";
5bb4e06a
DM
42 }
43 return $realm;
44}
45
46PVE::JSONSchema::register_standard_option('realm', {
47 description => "Authentication domain ID",
48 type => 'string', format => 'pve-realm',
49 maxLength => 32,
50});
51
98df2ebc
DC
52my $remove_options = "(?:acl|properties|entry)";
53
631b4874
DC
54PVE::JSONSchema::register_standard_option('sync-scope', {
55 description => "Select what to sync.",
56 type => 'string',
57 enum => [qw(users groups both)],
58 optional => '1',
59});
60
61PVE::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).",
68 type => 'string',
69 default => 'none',
70 typetext => "([acl];[properties];[entry])|none",
71 pattern => "(?:(?:$remove_options\;)*$remove_options)|none",
72 optional => '1',
73});
74
d29d2d4a 75my $realm_sync_options_desc = {
631b4874
DC
76 scope => get_standard_option('sync-scope'),
77 'remove-vanished' => get_standard_option('sync-remove-vanished'),
98df2ebc 78 # TODO check/rewrite in pve7to8, and remove with 8.0
d29d2d4a 79 full => {
98df2ebc
DC
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.",
d29d2d4a
TL
85 type => 'boolean',
86 optional => '1',
87 },
88 'enable-new' => {
89 description => "Enable newly synced users immediately.",
90 type => 'boolean',
91 default => '1',
92 optional => '1',
93 },
94 purge => {
98df2ebc
DC
95 description => "DEPRECATED: use 'remove-vanished' instead. Remove ACLs for users or"
96 ." groups which were removed from the config during a sync.",
d29d2d4a
TL
97 type => 'boolean',
98 optional => '1',
99 },
100};
101PVE::JSONSchema::register_standard_option('realm-sync-options', $realm_sync_options_desc);
102PVE::JSONSchema::register_format('realm-sync-options', $realm_sync_options_desc);
103
5bb4e06a
DM
104PVE::JSONSchema::register_format('pve-userid', \&verify_username);
105sub verify_username {
106 my ($username, $noerr) = @_;
107
108 $username = '' if !$username;
109 my $len = length($username);
110 if ($len < 3) {
111 die "user name '$username' is too short\n" if !$noerr;
112 return undef;
113 }
114 if ($len > 64) {
115 die "user name '$username' is too long ($len > 64)\n" if !$noerr;
116 return undef;
117 }
118
119 # we only allow a limited set of characters
0a6e09fd 120 # colon is not allowed, because we store usernames in
5bb4e06a
DM
121 # colon separated lists)!
122 # slash is not allowed because it is used as pve API delimiter
0a6e09fd 123 # also see "man useradd"
8e23f971 124 if ($username =~ m!^(${user_regex})\@(${realm_regex})$!) {
5bb4e06a
DM
125 return wantarray ? ($username, $1, $2) : $username;
126 }
127
128 die "value '$username' does not look like a valid user name\n" if !$noerr;
129
130 return undef;
131}
132
133PVE::JSONSchema::register_standard_option('userid', {
1ef6c8fd 134 description => "Full User ID, in the `name\@realm` format.",
5bb4e06a
DM
135 type => 'string', format => 'pve-userid',
136 maxLength => 64,
137});
138
9401be39
WB
139my $tfa_format = {
140 type => {
141 description => "The type of 2nd factor authentication.",
142 format_description => 'TFATYPE',
143 type => 'string',
144 enum => [qw(yubico oath)],
145 },
146 id => {
147 description => "Yubico API ID.",
148 format_description => 'ID',
149 type => 'string',
150 optional => 1,
151 },
152 key => {
153 description => "Yubico API Key.",
154 format_description => 'KEY',
155 type => 'string',
156 optional => 1,
157 },
158 url => {
159 description => "Yubico API URL.",
160 format_description => 'URL',
161 type => 'string',
162 optional => 1,
163 },
164 digits => {
165 description => "TOTP digits.",
166 format_description => 'COUNT',
167 type => 'integer',
168 minimum => 6, maximum => 8,
169 default => 6,
170 optional => 1,
171 },
172 step => {
173 description => "TOTP time period.",
174 format_description => 'SECONDS',
175 type => 'integer',
176 minimum => 10,
177 default => 30,
178 optional => 1,
179 },
180};
181
182PVE::JSONSchema::register_format('pve-tfa-config', $tfa_format);
96f8ebd6
DM
183
184PVE::JSONSchema::register_standard_option('tfa', {
185 description => "Use Two-factor authentication.",
186 type => 'string', format => 'pve-tfa-config',
187 optional => 1,
188 maxLength => 128,
189});
190
191sub parse_tfa_config {
192 my ($data) = @_;
193
9401be39 194 return PVE::JSONSchema::parse_property_string($tfa_format, $data);
96f8ebd6
DM
195}
196
5bb4e06a
DM
197my $defaultData = {
198 propertyList => {
199 type => { description => "Realm type." },
200 realm => get_standard_option('realm'),
201 },
202};
203
204sub private {
205 return $defaultData;
206}
207
208sub parse_section_header {
209 my ($class, $line) = @_;
210
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); };
215 $errmsg = $@ if $@;
216 my $config = {}; # to return additional attributes
217 return ($type, $realm, $errmsg, $config);
218 }
219 return undef;
220}
221
222sub parse_config {
223 my ($class, $filename, $raw) = @_;
224
225 my $cfg = $class->SUPER::parse_config($filename, $raw);
226
227 my $default;
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}) {
232 if ($default) {
233 delete $data->{default};
234 } else {
235 $default = $realm;
236 }
237 }
238
239 if ($data->{comment}) {
240 $data->{comment} = PVE::Tools::decode_text($data->{comment});
241 }
242
243 }
244
245 # add default domains
246
96f8ebd6
DM
247 $cfg->{ids}->{pve}->{type} = 'pve'; # force type
248 $cfg->{ids}->{pve}->{comment} = "Proxmox VE authentication server"
249 if !$cfg->{ids}->{pve}->{comment};
5bb4e06a 250
96f8ebd6
DM
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};
5bb4e06a
DM
255
256 return $cfg;
257};
258
259sub write_config {
260 my ($class, $filename, $cfg) = @_;
261
5bb4e06a
DM
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});
266 }
267 }
0a6e09fd 268
5bb4e06a
DM
269 $class->SUPER::write_config($filename, $cfg);
270}
271
272sub authenticate_user {
273 my ($class, $config, $realm, $username, $password) = @_;
274
275 die "overwrite me";
276}
277
278sub store_password {
279 my ($class, $config, $realm, $username, $password) = @_;
280
281 my $type = $class->type();
282
283 die "can't set password on auth type '$type'\n";
284}
285
286sub delete_user {
287 my ($class, $config, $realm, $username) = @_;
288
289 # do nothing by default
290}
291
89338e4d
TL
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
7d23b7ca 294# die to abort addition if there are (grave) problems
89338e4d
TL
295# NOTE: runs in a domain config *locked* context
296sub on_add_hook {
297 my ($class, $realm, $config, %param) = @_;
298 # do nothing by default
299}
300
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
305sub on_update_hook {
306 my ($class, $realm, $config, %param) = @_;
307 # do nothing by default
308}
309
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
d4397b51 314# NOTE: runs in a domain config *locked* context
89338e4d
TL
315sub on_delete_hook {
316 my ($class, $realm, $config) = @_;
317 # do nothing by default
318}
319
fbb1fa44
CH
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
d4397b51 322# NOTE: runs in a domain config *locked* context
fbb1fa44
CH
323sub check_connection {
324 my ($class, $realm, $config, %param) = @_;
325 # do nothing by default
326}
327
5bb4e06a 3281;